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"]
+}