diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md index 803314b..9162ffc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,26 @@ +<<<<<<< HEAD # rinuuri.dev +======= +# bun-react-template + +To install dependencies: + +```bash +bun install +``` + +To start a development server: + +```bash +bun dev +``` + +To run for production: + +```bash +bun start +``` + +This project was created using `bun init` in bun v1.2.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. +>>>>>>> d46ce56 (Initial commit) diff --git a/bun-env.d.ts b/bun-env.d.ts new file mode 100644 index 0000000..72f1c26 --- /dev/null +++ b/bun-env.d.ts @@ -0,0 +1,17 @@ +// Generated by `bun init` + +declare module "*.svg" { + /** + * A path to the SVG file + */ + const path: `${string}.svg`; + export = path; +} + +declare module "*.module.css" { + /** + * A record of class names to their corresponding CSS module classes + */ + const classes: { readonly [key: string]: string }; + export = classes; +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..d9266e8 --- /dev/null +++ b/bun.lock @@ -0,0 +1,41 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "bun-react-template", + "dependencies": { + "react": "^19", + "react-dom": "^19", + "three": "^0.176.0", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/react": "^19", + "@types/react-dom": "^19", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="], + + "@types/node": ["@types/node@22.15.24", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng=="], + + "@types/react": ["@types/react@19.1.6", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q=="], + + "@types/react-dom": ["@types/react-dom@19.1.5", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg=="], + + "bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], + + "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], + + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + + "three": ["three@0.176.0", "", {}, "sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + } +} diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..9819bf6 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[serve.static] +env = "BUN_PUBLIC_*" \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..8116b5f --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "bun-react-template", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "src/index.tsx", + "module": "src/index.tsx", + "scripts": { + "dev": "bun --hot src/index.tsx", + "build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'", + "start": "NODE_ENV=production bun src/index.tsx" + }, + "dependencies": { + "react": "^19", + "react-dom": "^19", + "three": "^0.176.0" + }, + "devDependencies": { + "@types/react": "^19", + "@types/react-dom": "^19", + "@types/bun": "latest" + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..9ee57a4 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,10 @@ +import Background from "./components/Background"; +import "./main.css"; + +export function App() { + return ( + + ); +} + +export default App; diff --git a/src/components/Background.tsx b/src/components/Background.tsx new file mode 100644 index 0000000..f7938a0 --- /dev/null +++ b/src/components/Background.tsx @@ -0,0 +1,17 @@ +import { useEffect, useRef } from "react"; +import { init } from "../scripts/background"; + +export default function Background() { + const canvasRef = useRef(null); + + useEffect(() => { + init(canvasRef.current); + }, []); + + return ( + <> +
+ + + ); +} \ No newline at end of file diff --git a/src/fonts/Nunito-Bold.ttf b/src/fonts/Nunito-Bold.ttf new file mode 100644 index 0000000..886134d Binary files /dev/null and b/src/fonts/Nunito-Bold.ttf differ diff --git a/src/frontend.tsx b/src/frontend.tsx new file mode 100644 index 0000000..446e60e --- /dev/null +++ b/src/frontend.tsx @@ -0,0 +1,26 @@ +/** + * This file is the entry point for the React app, it sets up the root + * element and renders the App component to the DOM. + * + * It is included in `src/index.html`. + */ + +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App"; + +const elem = document.getElementById("root")!; +const app = ( + + + +); + +if (import.meta.hot) { + // With hot module reloading, `import.meta.hot.data` is persisted. + const root = (import.meta.hot.data.root ??= createRoot(elem)); + root.render(app); +} else { + // The hot module reloading API is not available in production. + createRoot(elem).render(app); +} diff --git a/src/images/android.svg b/src/images/android.svg new file mode 100644 index 0000000..5753b64 --- /dev/null +++ b/src/images/android.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/debian.png b/src/images/debian.png new file mode 100644 index 0000000..a8d59d4 Binary files /dev/null and b/src/images/debian.png differ diff --git a/src/images/discord.svg b/src/images/discord.svg new file mode 100644 index 0000000..f6fb644 --- /dev/null +++ b/src/images/discord.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/dutch.jpg b/src/images/dutch.jpg new file mode 100644 index 0000000..cfa6f47 Binary files /dev/null and b/src/images/dutch.jpg differ diff --git a/src/images/email.svg b/src/images/email.svg new file mode 100644 index 0000000..a2bbecc --- /dev/null +++ b/src/images/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/english.png b/src/images/english.png new file mode 100644 index 0000000..41e0fe0 Binary files /dev/null and b/src/images/english.png differ diff --git a/src/images/favicon.ico b/src/images/favicon.ico new file mode 100644 index 0000000..0a18fb4 Binary files /dev/null and b/src/images/favicon.ico differ diff --git a/src/images/folia.webp b/src/images/folia.webp new file mode 100644 index 0000000..df99579 Binary files /dev/null and b/src/images/folia.webp differ diff --git a/src/images/github.svg b/src/images/github.svg new file mode 100644 index 0000000..b304405 --- /dev/null +++ b/src/images/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/golang.svg b/src/images/golang.svg new file mode 100644 index 0000000..0e0bab0 --- /dev/null +++ b/src/images/golang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/java.svg b/src/images/java.svg new file mode 100644 index 0000000..048d84f --- /dev/null +++ b/src/images/java.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/javascript.svg b/src/images/javascript.svg new file mode 100644 index 0000000..b20c63a --- /dev/null +++ b/src/images/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/jetpack-compose-icon.png b/src/images/jetpack-compose-icon.png new file mode 100644 index 0000000..b9a4708 Binary files /dev/null and b/src/images/jetpack-compose-icon.png differ diff --git a/src/images/jetpack-compose.png b/src/images/jetpack-compose.png new file mode 100644 index 0000000..b9a4708 Binary files /dev/null and b/src/images/jetpack-compose.png differ diff --git a/src/images/kotlin.svg b/src/images/kotlin.svg new file mode 100644 index 0000000..05c2515 --- /dev/null +++ b/src/images/kotlin.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/src/images/linkedin.svg b/src/images/linkedin.svg new file mode 100644 index 0000000..4ca11a6 --- /dev/null +++ b/src/images/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/papermc.png b/src/images/papermc.png new file mode 100644 index 0000000..854a4a8 Binary files /dev/null and b/src/images/papermc.png differ diff --git a/src/images/profile_picture.jpg b/src/images/profile_picture.jpg new file mode 100644 index 0000000..0746db7 Binary files /dev/null and b/src/images/profile_picture.jpg differ diff --git a/src/images/python.svg b/src/images/python.svg new file mode 100644 index 0000000..8c457c9 --- /dev/null +++ b/src/images/python.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/react.svg b/src/images/react.svg new file mode 100644 index 0000000..1ab815a --- /dev/null +++ b/src/images/react.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/images/russian.jpg b/src/images/russian.jpg new file mode 100644 index 0000000..c3744fc Binary files /dev/null and b/src/images/russian.jpg differ diff --git a/src/images/sqlite.svg b/src/images/sqlite.svg new file mode 100644 index 0000000..fe29bdd --- /dev/null +++ b/src/images/sqlite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/telegram.svg b/src/images/telegram.svg new file mode 100644 index 0000000..99b52de --- /dev/null +++ b/src/images/telegram.svg @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/src/images/youtube.svg b/src/images/youtube.svg new file mode 100644 index 0000000..6818f4e --- /dev/null +++ b/src/images/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..52f057e --- /dev/null +++ b/src/index.html @@ -0,0 +1,12 @@ + + + + + @Rinuuri - The dev + + + +
+ + + diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..b534dd4 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,41 @@ +import { serve } from "bun"; +import index from "./index.html"; + +const server = serve({ + routes: { + // Serve index.html for all unmatched routes. + "/*": index, + + "/api/hello": { + async GET(req) { + return Response.json({ + message: "Hello, world!111", + method: "GET", + }); + }, + async PUT(req) { + return Response.json({ + message: "Hello, world!", + method: "PUT", + }); + }, + }, + + "/api/hello/:name": async req => { + const name = req.params.name; + return Response.json({ + message: `Hello, ${name}!`, + }); + }, + }, + + development: process.env.NODE_ENV !== "production" && { + // Enable browser hot reloading in development + hmr: true, + + // Echo console logs from the browser to the server + console: true, + }, +}); + +console.log(`🚀 Server running at ${server.url}`); diff --git a/src/main.css b/src/main.css new file mode 100644 index 0000000..71c6a8d --- /dev/null +++ b/src/main.css @@ -0,0 +1,315 @@ +@font-face { + font-family: 'Host Grotesk'; + src: + url('fonts/Nunito-Bold.ttf') format('truetype') /* Safari, Android, iOS */ +} +@keyframes background-pan { + from { + background-position: 0% center; + } + to { + background-position: -200% center; + } +} +@keyframes bar { + 0% { + background-position: 0% center; + } + 33% { + background-position: -200% center; + } + 100% { + background-position: -200% center; + } +} +@keyframes float { + 0% { + transform: translateY(1.1svh) + } + 50% { + transform: translateY(0svh) + } + 100% { + transform: translateY(1.1svh) + } +} + +@keyframes float-rotate { + 0% { + transform: rotate(5deg) + } + 50% { + transform: rotate(-5deg) + } + 100% { + transform: rotate(5deg) + } +} + +*, html, body { + font-family: "Host Grotesk", serif; + font-optical-sizing: auto; + font-weight: 600; + font-style: normal; + padding: 0; + margin: 0; +} +html, body, #root{ + width:100%; + height:100%; +} +body { + background: black; +} +#background-renderer { + position: fixed; z-index: -1000; width: 100%; height: 100%; margin: 0; padding: 0; + filter: blur(0.2svh); +} +.circular-mask { + border-radius: 50%; + overflow: hidden; + width: 90px; + height: 90px; +} +#profile-picture-div { + position: absolute; padding: 10px; padding-left: 20px; z-index: 3; +} +#profile-picture-img { + transform-origin: 0% 0%; + transform: scale(0.15) translateX(-50%) translateY(-50%); + transition-duration: 0.5s; +} +#profile-picture-img:hover { + rotate: 90deg; +} +#language-div { + position: absolute; padding: 10px; padding-right: 20px; z-index: 3; +} +#language-renderer { + width: 100%; height: 100vh; display: block; margin: 0; padding: 0; cursor: grab; +} +#section-positioner { + position: absolute; top: 0; left: 50%; transform: translateX(-50%); z-index: 11; width: 920px; padding-top: 15px; +} +#projects { + position: absolute; top: 50vh; left: 20%; transform: translateX(-50%); z-index: 11; width: 900px; height: auto; display: grid; grid-template-columns: 50% 50%; left: 50%; width: 90%; +} +#footer { + background: linear-gradient(to bottom left, rgb(41, 11, 41), rgb(44, 18, 44)); + margin-bottom: 0px; + padding: 0px; + left: 30%; + position: absolute; + height: 50px; + z-index: 12; + align-self: center; + width: 40%; + padding-top: 10px; + overflow: hidden; + bottom: 0; + border-top-left-radius: 20px; + border-top-right-radius: 20px; + transition: all 0.3s ease-in-out; + transform-origin: center bottom; + transform: scale(1); +} +#footer:hover { + background: linear-gradient(to bottom left, rgb(41, 11, 41), rgb(48, 19, 48)); + transform: scale(101%); +} +.description { + scale: 0.85; transform-origin: left; +} +.profile-gradient { + background: linear-gradient(to right, rgb(230, 160, 56), rgb(225, 74, 180), rgb(189, 37, 216), rgb(62, 163, 218)); + width: 95px; height: 95px; +} +.gradient-overlay { + background: linear-gradient(to bottom right, rgba(0, 0, 0, 0.5), rgba(107, 29, 107, 0.3)); + background: -webkit-linear-gradient(to bottom right, rgba(0, 0, 0, 0.5), rgba(107, 29, 107, 0.3) 100%); + background: -moz-linear-gradient(to bottom right, rgba(0, 0, 0, 0.5), rgba(107, 29, 107, 0.3) 100%); + position: fixed; width: 100%; height: 100%; + z-index: 1000000; +} +.devider { + position: absolute; left: 60%; top: 0; height: 100%; width: 0.4vh; background-color: rgba(209, 209, 209, 0.1); + border-radius: 20%; +} +.section { + background: rgb(15, 15, 15); + background: linear-gradient(to bottom right, rgba(15,15,16,1), rgb(44, 18, 44)); + background: -webkit-linear-gradient(to bottom right, rgba(15,15,16,1), rgb(44, 18, 44)); + background: -moz-linear-gradient(to bottom right, rgba(15,15,16,1), rgb(44, 18, 44)); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#0f0f10",endColorstr="#152137",GradientType=1); + border-radius: 4vh; + height: 100%; + padding-top: 64px; + padding-left: 35px; + padding-right: 48px; + padding-bottom: 48px; + margin-bottom: 24px; + margin-right: 1%; + transition: all 0.4s ease-in-out; + transform-origin: center; + transform: scale(1); + position: relative; + overflow: hidden; +} +.long-desc { + width: 90%; + overflow-wrap: break-word; +} +.section-card { + padding-top: 5%; padding-bottom: 10%; + margin-right: 2%; + margin-left: 2%; + margin-bottom: 0px; +} +.section:after { + width: 1px; + height: 80px; + background: linear-gradient( + rgba(0,0,0,0), + rgb(150, 73, 238), + rgba(0,0,0,0) + ); + left: 0px; + position: absolute; + top: 60%; + opacity: 0%; + transition: all 0.15s ease-in-out; + content: ""; +} +.section:hover:after { + top: 20%; + opacity: 100%; +} +#projects .section { + height: 20vh; +} +.section:hover { + background: linear-gradient(to bottom right, rgb(16, 16, 16), rgb(48, 18, 48)); + transform: scale(101%); + box-shadow: #2702274f 0px 0px 4svh 0px; +} +.card { + width: 10%; +} +.section button { + color: white; + background-color: #e032e0; + border-radius: 120px; + height: 60px; + border: none; + width: 180px; + font-size: 22px; + transition: all 0.25s ease-in-out; +} +.progress-bar { + left: 10%; width: 90%; height: 1vh; + background: linear-gradient(to right, rgb(128, 0, 128), rgb(187, 75, 212), rgb(128, 0, 128)); + background-size: 200%; + border-radius: 1vh; + animation: bar 6s linear infinite; +} +.tech-name img { + width: 3.5vh; + height: 3.5vh; +} +.section p { + color: #CED7EB; + font-weight: 400; + font-size: 24px; + margin-bottom: 32px; +} +.tech-name { + font-size: x-large; + color: #d6c7e0; + height: 1px; + transition: all 0.25s ease-in-out; + transform: scale(1); + transform-origin: right; + cursor: default; +} +.tech-name:hover { + transform: scale(102%) translateX(-0.5vh); +} +h1,h2,h3,h4,h5 { + font-weight: 900; + font-size: 36px; + color: whitesmoke; + margin-bottom: 1vh; + width: max-content; +} +button:hover { + animation-play-state: running; + scale: 105%; +} +button { + background: linear-gradient(to right, rgb(73, 0, 80), rgb(126, 0, 138), rgb(73, 0, 80)); + background-size: 200%; + animation: background-pan 3s linear infinite; + animation-play-state: paused; + cursor: pointer; +} +.highlight { + animation: background-pan 3s linear infinite; + background: linear-gradient(to right, rgb(167, 0, 167), violet, rgb(228, 95, 217), rgb(167, 0, 167)); + background-size: 200%; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + white-space: nowrap; +} +a.icon { + transition: all 0.2s ease-in-out; + width: 20px; + height: 20px; + display: inline-block; + margin-right: 30px; + filter: invert(100%) brightness(40%); +} +a.icon:hover { + scale: 110%; + filter: invert(100%) brightness(60%); +} +#licence { + text-align: right; right: 5%; color: rgb(170, 170, 170); padding-right: 2%; +} +#contacts { + position: absolute; height: 50%; left: 3%; bottom: 10px; display: flex; +} +.other-tech { + position: relative;left: 15%; display: flex; align-items: center; flex-direction: column; +} +.gradient-overlay div { + position: absolute; + width: fit-content; height: fit-content; animation: float 5s ease-in-out infinite; +} +.gradient-overlay img { + width: 7vh; height: 7vh; transform-origin: 50% 50%; animation: float-rotate 6s ease-in-out infinite; + filter: blur(0.5svh); + transition-duration: 0.5s; +} +.gradient-overlay img:hover { + filter: none; + scale: 1.4; +} +.rotate-away { + transform-origin: 50% 0px 50vh; + transform: rotateY(-90deg); + transition: all 2s ease-in-out; + opacity: 0%; +} +.rotated { + transform: rotateY(90deg); + position: absolute; + height: 100%; + width: 100%; +} +.rotate-in { + transform-origin: 50% 0px 50vh; + transform: rotateY(0deg); + transition: all 2s ease-in-out; + opacity: 100%; +} \ No newline at end of file diff --git a/src/scripts/background.js b/src/scripts/background.js new file mode 100644 index 0000000..28d9371 --- /dev/null +++ b/src/scripts/background.js @@ -0,0 +1,58 @@ +import * as THREE from 'three'; +import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; +import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; +import {loadShader} from './common'; +import fragmentShaderPath from '../shaders/background.glsl' assert { type: 'string' }; + +export async function init(canvas) { + let lastTime = performance.now(); + let time = 0; + + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(0, window.innerWidth / window.innerHeight, 0.1, 1000); + const renderer = new THREE.WebGLRenderer({antialias: false, canvas: canvas}); + renderer.setSize(window.innerWidth/2, window.innerHeight); + const composer = new EffectComposer( renderer ); + + const uniforms = { + u_resolution: {value: new THREE.Vector2(window.innerWidth, window.innerHeight) + .multiplyScalar(window.devicePixelRatio) + }, + aspect: {value: window.innerWidth/ window.innerHeight}, + u_time_sin: {value: 0}, + u_time: {value: 0}, + u_mouse: {value: new THREE.Vector2(0.5, 0.5)}, + u_scale: {value: window.innerHeight/1000} + } + + const fragmentShader = await loadShader(fragmentShaderPath); + const postprocessingshader = new THREE.ShaderMaterial({ + fragmentShader: fragmentShader, + uniforms + }); + + composer.addPass( new ShaderPass(postprocessingshader)); + window.addEventListener('resize', function() { + const aspect = window.innerWidth/window.innerHeight; + postprocessingshader.uniforms.u_resolution.value = new THREE.Vector2(window.innerWidth, window.innerHeight) + .multiplyScalar(window.devicePixelRatio); + postprocessingshader.uniforms.aspect.value = aspect; + renderer.setSize(window.innerWidth, window.innerHeight); + }); + + window.addEventListener('mousemove', function(event) { + postprocessingshader.uniforms.u_mouse.value = new THREE.Vector2(1-event.clientX/window.innerWidth, event.clientY/window.innerHeight) + }); + renderer.setAnimationLoop( animate ); + + function animate() { + const currentTime = performance.now(); + const deltaTime = currentTime - lastTime; + lastTime = currentTime; + composer.render( scene, camera ); + time += 0.0015*deltaTime; + postprocessingshader.uniforms.u_time.value = time*0.01; + } + + animate(); +} \ No newline at end of file diff --git a/src/scripts/common.js b/src/scripts/common.js new file mode 100644 index 0000000..223a37e --- /dev/null +++ b/src/scripts/common.js @@ -0,0 +1,7 @@ +export async function loadShader(url) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to load shader: ${response.statusText}`); + } + return await response.text(); +} \ No newline at end of file diff --git a/src/scripts/lang.js b/src/scripts/lang.js new file mode 100644 index 0000000..ee2e0d5 --- /dev/null +++ b/src/scripts/lang.js @@ -0,0 +1,195 @@ +import * as THREE from 'three'; +import translations from '../translations.json' assert { type: 'json' }; +import engPath from '../images/english.png' +import nldPath from '../images/dutch.jpg' +import rusPath from '../images/russian.jpg' + +let language = "en"; + +function determineLanguage(){ + const userLang = localStorage.getItem("preferredLanguage") || navigator.language || navigator.userLanguage; + switch (userLang) { + case "ru": { + language = "ru"; + cube.rotation.y = 1.5707963267948966; + break; + } + case "nl": { + language = "nl"; + cube.rotation.y = 4.71238898038469; + } + } + if (language != "en") setLanguage(language); +} + +var elements = document.querySelectorAll('[ts]'); + +function setLanguage(l) { + language = l; + const arr = translations[language]; + elements.forEach(function(element) { + var key = element.getAttribute('ts'); + + if (arr.hasOwnProperty(key)) { + element.innerHTML = arr[key]; + } + }); +} + + +const fov = 75; +const aspect = 1; +const near = 0.1; +const far = 5; + +const canvas = document.querySelector('#language-renderer'); +const renderer = new THREE.WebGLRenderer({antialias: false, canvas}); +renderer.setSize( 90,90 ); +renderer.setClearColor( 0xffffff, 0); +const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); +camera.position.z = 1.4; + +const scene = new THREE.Scene(); + +const discardMaterial = new THREE.ShaderMaterial({ + vertexShader:` + void main() { + gl_Position = vec4(1.0); + }`, + fragmentShader: ` + void main() { + gl_FragColor = vec4(1.0); + discard; + } + ` +}); + +var geometry = new THREE.BoxGeometry(1, 1, 1); +var texture = new THREE.TextureLoader(); +var materials = []; +const dutch = texture.load(nldPath); +dutch.magFilter = THREE.NearestFilter; +dutch.minFilter = THREE.NearestFilter; +const english = texture.load(engPath); +english.magFilter = THREE.NearestFilter; +english.minFilter = THREE.NearestFilter; +const russian = texture.load(rusPath); +russian.magFilter = THREE.NearestFilter; +russian.minFilter = THREE.NearestFilter; +materials.push(new THREE.MeshPhongMaterial({map: dutch})); +materials.push(new THREE.MeshPhongMaterial({map: russian})); +materials.push(discardMaterial); +materials.push(discardMaterial); +materials.push(new THREE.MeshPhongMaterial({map: english})); +materials.push(new THREE.MeshPhongMaterial({map: english})); + +var cube = new THREE.Mesh(geometry, materials); + +determineLanguage(); + +scene.add(cube); + + +const ambientLight = new THREE.AmbientLight(0xffffff, 2.8); +scene.add(ambientLight); + +let isDragging = false; +let previousX; +let velocity = 0; +const friction = 0.4; +// Define the possible rotation positions +const snapPositions = [0, Math.PI / 2, Math.PI, Math.PI * 3 / 2]; + +// Store the current snapped position +let currentSnapPosition = 0; + +function startDrag(event) { + isDragging = true; + previousX = event.clientX || event.touches[0].clientX; + canvas.style.cursor = "grabbing"; +} + +function drag(event) { + if (isDragging) { + const currentX = event.clientX || event.touches[0].clientX; + velocity += (currentX - previousX) * 0.05; + previousX = currentX; + } +} + +function endDrag() { + isDragging = false; + canvas.style.cursor = "grab"; + snapCube(); +} + +function snapCube() { + // Normalize the cube's rotation to be within the range [0, 2 * PI) + let rotation = cube.rotation.y % (2 * Math.PI); + if (rotation < -Math.PI/4) { + rotation += 2 * Math.PI; + } else if (rotation > 5.49778718038469) { + rotation -= 2 * Math.PI; + } + + // Calculate the closest snap position + const closestSnapPosition = snapPositions.reduce((prev, curr) => { + return Math.abs(curr - rotation) < Math.abs(prev - rotation) ? curr : prev; + }, snapPositions[0]); + + // Interpolate to the closest snap position + const snapTime = 0.5; // seconds + const snapStart = rotation; + const snapEnd = closestSnapPosition; + const snapStartTime = performance.now(); + + function snapRender(time) { + const t = (time - snapStartTime) / (snapTime * 1000); + cube.rotation.y = snapStart + (snapEnd - snapStart) * t; + if (t < 1) { + requestAnimationFrame(snapRender); + } else { + cube.rotation.y = snapEnd; + currentSnapPosition = snapEnd; + let lang; + switch (snapEnd){ + case 1.5707963267948966: + lang = "ru"; + break; + case 0: + lang = "en"; + break; + case 3.141592653589793: + lang = "en"; + break; + case 4.71238898038469: + lang = "nl"; + break; + } + if (lang != language) { + setLanguage(lang); + localStorage.setItem("preferredLanguage",language); + } + } + } + requestAnimationFrame(snapRender); +} + +canvas.addEventListener('mousedown', startDrag); +canvas.addEventListener('touchstart', startDrag); +canvas.addEventListener('mousemove', drag); +canvas.addEventListener('touchmove', drag); +canvas.addEventListener("mouseleave", endDrag); +canvas.addEventListener('mouseup', endDrag); +canvas.addEventListener('touchend', endDrag); + +//cube.rotation.x = 0.5; +function render(time) { + renderer.render(scene, camera); + + velocity *= friction; + cube.rotation.y += velocity; + + requestAnimationFrame(render); +} +requestAnimationFrame(render); diff --git a/src/shaders/background.glsl b/src/shaders/background.glsl new file mode 100644 index 0000000..e1ce436 --- /dev/null +++ b/src/shaders/background.glsl @@ -0,0 +1,75 @@ +uniform vec2 u_resolution; +uniform float aspect; +uniform float u_time; +uniform float u_time_sin; +uniform vec2 u_mouse; +uniform float u_scale; + +// Worley noise implementation, credits to https://github.com/Erkaman + +vec4 permute(vec4 x) { + return mod((34.0 * x + 1.0) * x, 289.0); +} + +vec2 dist(vec2 x, vec2 y) { + return x * x + y * y; +} + +vec4 dist(vec4 x, vec4 y, vec4 z) { + return x * x + y * y + z * z; +} + +vec2 worley(vec3 P, float jitter) { + float K = 0.142857142857; + float Ko = 0.428571428571; + float K2 = 0.020408163265306; + float Kz = 0.166666666667; + float Kzo = 0.416666666667; + + vec3 Pi = mod(floor(P), 289.0); + vec3 Pf = fract(P); + vec4 Pfx = Pf.x + vec4(0.0, -1.0, 0.0, -1.0); + vec4 Pfy = Pf.y + vec4(0.0, 0.0, -1.0, -1.0); + vec4 p = permute(Pi.x + vec4(0.0, 1.0, 0.0, 1.0)); + p = permute(p + Pi.y + vec4(0.0, 0.0, 1.0, 1.0)); + vec4 p1 = permute(p + Pi.z); + vec4 p2 = permute(p + Pi.z + vec4(1.0)); + vec4 ox1 = fract(p1*K) - Ko; + vec4 oy1 = mod(floor(p1*K), 7.0)*K - Ko; + vec4 oz1 = floor(p1*K2)*Kz - Kzo; + vec4 ox2 = fract(p2*K) - Ko; + vec4 oy2 = mod(floor(p2*K), 7.0)*K - Ko; + vec4 oz2 = floor(p2*K2)*Kz - Kzo; + vec4 dx1 = Pfx + jitter*ox1; + vec4 dy1 = Pfy + jitter*oy1; + vec4 dz1 = Pf.z + jitter*oz1; + vec4 dx2 = Pfx + jitter*ox2; + vec4 dy2 = Pfy + jitter*oy2; + vec4 dz2 = Pf.z - 1.0 + jitter*oz2; + vec4 d1 = dist(dx1, dy1, dz1); + vec4 d2 = dist(dx2, dy2, dz2); + + vec4 d = min(d1,d2); + d2 = max(d1,d2); + d.xy = (d.x < d.y) ? d.xy : d.yx; + d.xz = (d.x < d.z) ? d.xz : d.zx; + d.xw = (d.x < d.w) ? d.xw : d.wx; + d.yzw = min(d.yzw, d2.yzw); + d.y = min(d.y, d.z); + d.y = min(d.y, d.w); + d.y = min(d.y, d2.x); + return sqrt(d.xy); +} + +void main() { + vec2 pixelPos = gl_FragCoord.xy / u_resolution; + pixelPos.x *= aspect; + float distanceIN = max(0.2-length(pixelPos - vec2((1.0-u_mouse.x)*aspect, 1.0-u_mouse.y)), 0.0); + pixelPos *= 10.0/u_scale; + pixelPos += u_time_sin; + float noise2 = worley(vec3(pixelPos, u_time+1.25), 1.0).x; + float noise1 = worley(vec3(pixelPos*1.1, u_time), 1.0).x; + float strengh = step(noise1, 0.07)*0.2+step(noise2, 0.04)*0.6; + strengh += distanceIN*(0.9-step(strengh, 0.01))*5.0; + gl_FragColor = vec4(strengh, strengh-0.25, strengh, 1.0); +} diff --git a/src/translations.json b/src/translations.json new file mode 100644 index 0000000..0e17bd7 --- /dev/null +++ b/src/translations.json @@ -0,0 +1,38 @@ +{ + "en": { + "hi": "Hi, I'm", + "hi_rest":", and I am
obsessed with development
since my childhood.", + "main_desctiption": "I'm a Java/Kotlin developer and just a trans girl :)", + "contact_me": "Contact me", + "licence": "Site source code license", + "tech_stack": "Programming languages", + "other_tech": "Other tech", + "devider": "
", + "projects": "Experience and projects", + "projects_description": "I have experience with multithreaded environments and network programming, as I've worked on both commertial projects and my own open source ones, click the botton bellow to explore them", + "explore_projects": "explore" + }, + "nl": { + "hi": "Hoi, ik heet", + "hi_rest":", en ik ben
geobsedeerd door ontwikkeling
sinds m'n jeugd.", + "main_desctiption": "Ik ben een Java/Kotlin ontwikkelster en net een trans meisje :)", + "licence":"Licentie voor sitebroncode", + "tech_stack": "Programmeertaalen", + "other_tech": "Anders technische", + "devider": "
", + "projects":"Ervaring en projecten", + "projects_description": "Ik heb ervaring met multithreaded omgevingen en netwerkprogrammering, aangezien ik aan zowel commerciële projecten als m'n eigen open source projecten heb gewerkt, klik op de onderste knop om ze te verkennen", + "explore_projects": "Verkennen" + }, + "ru":{ + "hi": "Привет, я", + "hi_rest":", и я
обожаю разработку
с самого детства.", + "main_desctiption": "Я Java/Kotlin разработчик, да и просто транс-девушка :)", + "contact_me": "Связаться", + "licence": "Лицензия исходного кода сайта", + "tech_stack": "Языки программирования", + "other_tech": "Остальной стэк", + "devider": "
", + "projects": "Опыт и проекты" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..83b2227 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "allowJs": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["dist", "node_modules"] +}