commit 59d4a691d6e600198f948a43f3f92caf8cdc01be
Author: Redsandy <34872843+Redsandyg@users.noreply.github.com>
Date: Wed Aug 12 11:27:07 2026 +0300
Initialize Vyryi front-end project with React, Vite, and TypeScript. Add essential files including configuration, environment variables, and basic structure. Implement authentication, routing, and API interaction for a Telegram Mini App UI. Include README and styling for a cohesive user experience.
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..ad82eb2
--- /dev/null
+++ b/.env.example
@@ -0,0 +1 @@
+VITE_API_URL=http://127.0.0.1:8000
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7c3d27f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+.env
+.env.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/.oxlintrc.json b/.oxlintrc.json
new file mode 100644
index 0000000..6fa991d
--- /dev/null
+++ b/.oxlintrc.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
+ "plugins": ["react", "typescript", "oxc"],
+ "rules": {
+ "react/rules-of-hooks": "error",
+ "react/only-export-components": ["warn", { "allowConstantExport": true }]
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..06b016d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# Vyryi Front
+
+React (Vite + TypeScript) Telegram Mini App UI for **Vyryi**.
+
+## Stack
+
+- React 19 + React Router
+- Local auth via backend `POST /auth/dev-login`
+- Dark folk visual style matching the project logo
+
+## Screens
+
+1. `/` — logo + user agreement gate
+2. `/home` — balance, stub deposit, slot stubs
+3. `/games/:slug` — game placeholder
+
+## Quick start
+
+```bash
+cd vyryi-front
+npm install
+copy .env.example .env
+npm run dev
+```
+
+Open http://127.0.0.1:5173 — backend must be running on port 8000.
+
+## Env
+
+```
+VITE_API_URL=http://127.0.0.1:8000
+```
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..75ffd3d
--- /dev/null
+++ b/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ Vyryi
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..31d2450
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2076 @@
+{
+ "name": "vyryi-front",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "vyryi-front",
+ "version": "0.0.0",
+ "dependencies": {
+ "@react-three/drei": "^10.7.8",
+ "@react-three/fiber": "^9.7.0",
+ "@react-three/postprocessing": "^3.0.5",
+ "gsap": "^3.15.0",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "react-router-dom": "^7.18.2",
+ "three": "^0.185.1"
+ },
+ "devDependencies": {
+ "@types/node": "^24.13.3",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@types/three": "^0.185.4",
+ "@vitejs/plugin-react": "^6.0.4",
+ "oxlint": "^1.75.0",
+ "typescript": "~6.0.2",
+ "vite": "^8.2.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@dimforge/rapier3d-compat": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
+ "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@mediapipe/tasks-vision": {
+ "version": "0.10.17",
+ "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
+ "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@monogrid/gainmap-js": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
+ "integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
+ "license": "MIT",
+ "dependencies": {
+ "promise-worker-transferable": "^1.0.4"
+ },
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.143.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz",
+ "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@oxlint/binding-android-arm-eabi": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.78.0.tgz",
+ "integrity": "sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-android-arm64": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.78.0.tgz",
+ "integrity": "sha512-CDfxZgB61B7buRdY2FJoAYYPPXCZ1EoC1LKscnC5dg3kjobdxiconvAvvN1BmHyW4PyFT3jRLDag/BY/roSNBQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-darwin-arm64": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.78.0.tgz",
+ "integrity": "sha512-2Y2U9Ahrz+OO0Ej88f9SJYq51/jUBp1Mc7iZu0ukrbeeZ3gpRGfzIFnoqfHDY96xr0GEfNrPUBFEy0nN5aD7HA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-darwin-x64": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.78.0.tgz",
+ "integrity": "sha512-rpych6eJq6m9jDRypTEaPD1xysaEW5h9+xuxhGK/QhOg+/xaqPZrCrTNoIl/f3nEjuJeCEmstNDlrE9rJi/3/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-freebsd-x64": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.78.0.tgz",
+ "integrity": "sha512-IcMGrQT3QizkOESUJd5et+rOhVqSkNDfNik1cvrKDqIbzqx9KMtRswpFgkCuNTSwylCFLKhGUu8KmqY1ZnC0Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm-gnueabihf": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.78.0.tgz",
+ "integrity": "sha512-/uLdoJ0IXE6vo/0f0LKjinQAp+re+VMaCWaNT8ENIv2EOCkSsc8SGaflXAuW0Jua2dq5+GLVWm1NQK7P3UFSNQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm-musleabihf": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.78.0.tgz",
+ "integrity": "sha512-7xi4Wb/O8NRJhLoUXmDJMUVpNYvB5kefdhFU1Jb8rtae4QoXlTiLwI14X4YvAXVZLNZChP8m5qO9SQAlWQTbkQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm64-gnu": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.78.0.tgz",
+ "integrity": "sha512-4hFW0+fVXa3OIh1Y4A5SPkmvI4wuuBSrCVKzOyE7PTjhc7yEqZ1pmvEEeS5Lj/MaqvegFxXyF33N+6jkehxdyg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm64-musl": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.78.0.tgz",
+ "integrity": "sha512-oC0mvsgBJjlMijSDEhx9KuvR9zYeHXceA9MjbuXB1F8NSR78Yj2unOBrstEvTVaq+pko+kuue6DajC00eqvTdg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-ppc64-gnu": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.78.0.tgz",
+ "integrity": "sha512-XAllT5SUZS+ohjuZ3/5S0cwe0r7eboiuigeStCZ5DXRYx/2KVM2UvQXvAfyzXEimtQjAB7cDQ2YxDe2Zl2WNQQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-riscv64-gnu": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.78.0.tgz",
+ "integrity": "sha512-trucMER/0QtecoXvc1y/UVqE3kwJipDwrx4oHfj+nNm3dq2zjP44WT0CfHNDPM3G1DXIkx/gY6lAD21NSCZVhA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-riscv64-musl": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.78.0.tgz",
+ "integrity": "sha512-cm3O4F/HQbdzOUX5mKHqG5KDL6E5w0pnlZ+fbBy2rmLryPOowkuLagFHTopQsEIpjcaZoPOrL+BmmAytAG9HFg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-s390x-gnu": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.78.0.tgz",
+ "integrity": "sha512-33wRf6HqGNsybJ3qX4cGaQN2ODPxNmc1rMa0mrTmx3eFq1VzOnvQooi9bIGVYakW8a/wmqVx1mgsUm8R2xfTiw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-x64-gnu": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.78.0.tgz",
+ "integrity": "sha512-rRdISSYegj6VganMZ9tjRjijowfHJ09IZU01i0toBAqr6n5LEtwHq2IeS4FjW2RoskOHlb6efB26H5izYb3GEQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-x64-musl": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.78.0.tgz",
+ "integrity": "sha512-GmsP4rW0xTL6u5CVdcDsaN5Fbc7hBc382Wmar1kttbnwSEviM+rSINKOMQ+UQ6iH+AGwC+8gaAiwu134Tgh6Lg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-openharmony-arm64": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.78.0.tgz",
+ "integrity": "sha512-sy9yeYuADc8a+n4TLBayzMCZiHPW78DcIFVpOXTmdKHWQeM9xe5uzkqIIZmi326D5hY9XVwacipEB1p7tQjPAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-arm64-msvc": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.78.0.tgz",
+ "integrity": "sha512-rjc2hF1KfMi8fZj1X/m3AmnHbdsF3rL0v6KQg0Uc880Yb2khjz+3U14sfdZ7jWTpRnN1m1NQa/TT7uU9lJWPrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-ia32-msvc": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.78.0.tgz",
+ "integrity": "sha512-zcuXFVrEFHIafRfkCQT8w/Xe41o07ozl/vwHq7p94vB29xVzsB0sZGYORU1jhcYKv3Lr0J3HbJ2T4fHH5rWmvA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-x64-msvc": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.78.0.tgz",
+ "integrity": "sha512-Sb5ocmLSuYeOuXd+CFOToGKp/gjXUEWDnvIGwhnh8aq8wY4TMmEnKnvbogSW7RdMZv77JSARduS7/gv+khYEjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@react-three/drei": {
+ "version": "10.7.8",
+ "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.8.tgz",
+ "integrity": "sha512-rJXyuzLm2Xq0kafHuR47ajDGbOe/pEhzIr4m8E8zwzQs0iNjloFDqBwRhrXmP/w+onLeYyN3EYPFW/cwWK/4yA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mediapipe/tasks-vision": "0.10.17",
+ "@monogrid/gainmap-js": "^3.0.6",
+ "@use-gesture/react": "^10.3.1",
+ "camera-controls": "^3.1.0",
+ "cross-env": "^7.0.3",
+ "detect-gpu": "^5.0.56",
+ "glsl-noise": "^0.0.0",
+ "hls.js": "^1.5.17",
+ "maath": "^0.10.8",
+ "meshline": "^3.3.1",
+ "stats-gl": "^2.2.8",
+ "stats.js": "^0.17.0",
+ "suspend-react": "^0.1.3",
+ "three-mesh-bvh": "^0.8.3",
+ "three-stdlib": "^2.35.6",
+ "troika-three-text": "^0.52.4",
+ "tunnel-rat": "^0.1.2",
+ "use-sync-external-store": "^1.4.0",
+ "utility-types": "^3.11.0",
+ "zustand": "^5.0.1"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": "^9.0.0",
+ "react": "^19",
+ "react-dom": "^19",
+ "three": ">=0.159"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/fiber": {
+ "version": "9.7.0",
+ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.7.0.tgz",
+ "integrity": "sha512-EWm9FwcaOZQu/ExFW5rggoCMM1NJet5YbxVxKaOE+KSncrjU0Wx7017qSyGFvupviK89nMYGCWU3BIK4dI1clw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.17.8",
+ "@types/webxr": "*",
+ "base64-js": "^1.5.1",
+ "buffer": "^6.0.3",
+ "its-fine": "^2.0.0",
+ "react-use-measure": "^2.1.7",
+ "scheduler": "^0.27.0",
+ "suspend-react": "^0.1.3",
+ "use-sync-external-store": "^1.4.0",
+ "zustand": "^5.0.3"
+ },
+ "peerDependencies": {
+ "expo": ">=43.0",
+ "expo-asset": ">=8.4",
+ "expo-file-system": ">=11.0",
+ "expo-gl": ">=11.0",
+ "react": ">=19 <19.3",
+ "react-dom": ">=19 <19.3",
+ "react-native": ">=0.78",
+ "three": ">=0.156"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ },
+ "expo-asset": {
+ "optional": true
+ },
+ "expo-file-system": {
+ "optional": true
+ },
+ "expo-gl": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/postprocessing": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-3.0.5.tgz",
+ "integrity": "sha512-rqxoH8cjq51w5yZ7Re3WNPVVDdSsuVwEYA+Nl90/nFLv0MIxYGjMkqhc6akp5+VLNwZD2aqOd8KcwuZW7NeUKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "maath": "^0.10.8",
+ "n8ao": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": ">=9.7.0",
+ "postprocessing": "^6.36.0",
+ "react": "^19.2.0",
+ "three": ">= 0.182.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz",
+ "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz",
+ "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz",
+ "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz",
+ "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz",
+ "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz",
+ "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz",
+ "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz",
+ "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz",
+ "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz",
+ "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz",
+ "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz",
+ "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz",
+ "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz",
+ "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tweenjs/tween.js": {
+ "version": "23.1.3",
+ "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
+ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/draco3d": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
+ "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.13.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
+ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/@types/offscreencanvas": {
+ "version": "2019.7.3",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
+ "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
+ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/react-reconciler": {
+ "version": "0.28.9",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
+ "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/stats.js": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
+ "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/three": {
+ "version": "0.185.4",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.4.tgz",
+ "integrity": "sha512-gAsBIC07NIFrxjbf7tH2t71c38uulFfk/RFoC7FNBSjMRAQ8J1x/RBvusX0N5PJouaYFJawXQqfCQ0RKUx/1nA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@dimforge/rapier3d-compat": "~0.12.0",
+ "@tweenjs/tween.js": "~23.1.3",
+ "@types/stats.js": "*",
+ "@types/webxr": ">=0.5.17",
+ "fflate": "~0.8.2",
+ "meshoptimizer": "~1.1.1"
+ }
+ },
+ "node_modules/@types/webxr": {
+ "version": "0.5.24",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
+ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
+ "license": "MIT"
+ },
+ "node_modules/@use-gesture/core": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
+ "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
+ "license": "MIT"
+ },
+ "node_modules/@use-gesture/react": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
+ "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@use-gesture/core": "10.3.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz",
+ "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/camera-controls": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz",
+ "integrity": "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=22.0.0",
+ "npm": ">=10.5.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.126.1"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/detect-gpu": {
+ "version": "5.0.70",
+ "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
+ "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
+ "license": "MIT",
+ "dependencies": {
+ "webgl-constants": "^1.1.1"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/draco3d": {
+ "version": "1.5.7",
+ "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
+ "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
+ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
+ "license": "MIT"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/glsl-noise": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
+ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
+ "license": "MIT"
+ },
+ "node_modules/gsap": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
+ "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
+ "license": "Standard 'no charge' license: https://gsap.com/standard-license."
+ },
+ "node_modules/hls.js": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.18.tgz",
+ "integrity": "sha512-Rtovq9oJt9HWBqFJF9wZrZM7dRvdGHYNdMMv+wSDGDE2+FIybgs8RPJpR3kya1TZKBHVv2NOgLmn/M0MFjrQ3g==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/its-fine": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
+ "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.9"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/maath": {
+ "version": "0.10.8",
+ "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
+ "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/three": ">=0.134.0",
+ "three": ">=0.134.0"
+ }
+ },
+ "node_modules/meshline": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
+ "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.137"
+ }
+ },
+ "node_modules/meshoptimizer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz",
+ "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==",
+ "license": "MIT"
+ },
+ "node_modules/n8ao": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/n8ao/-/n8ao-2.0.1.tgz",
+ "integrity": "sha512-MoP1QKUdj4/3cKEnhFl917t0eQwWGER1l43ckz5mNOPyYWGRDGQbVoTOQz/VBvy01in6QVNG+AL6Xxx5AjZBYA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "postprocessing": ">=6.30.0",
+ "three": ">=0.137"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/oxlint": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.78.0.tgz",
+ "integrity": "sha512-QgQePuxIqKOzo1KSjG2EnITEeWvWnKAm77eq8nrMtf6AGoA+zyGc4PFYtDNJSD25g/ibOwfQ851hZ4/SPkMVoA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "oxlint": "bin/oxlint"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ },
+ "optionalDependencies": {
+ "@oxlint/binding-android-arm-eabi": "1.78.0",
+ "@oxlint/binding-android-arm64": "1.78.0",
+ "@oxlint/binding-darwin-arm64": "1.78.0",
+ "@oxlint/binding-darwin-x64": "1.78.0",
+ "@oxlint/binding-freebsd-x64": "1.78.0",
+ "@oxlint/binding-linux-arm-gnueabihf": "1.78.0",
+ "@oxlint/binding-linux-arm-musleabihf": "1.78.0",
+ "@oxlint/binding-linux-arm64-gnu": "1.78.0",
+ "@oxlint/binding-linux-arm64-musl": "1.78.0",
+ "@oxlint/binding-linux-ppc64-gnu": "1.78.0",
+ "@oxlint/binding-linux-riscv64-gnu": "1.78.0",
+ "@oxlint/binding-linux-riscv64-musl": "1.78.0",
+ "@oxlint/binding-linux-s390x-gnu": "1.78.0",
+ "@oxlint/binding-linux-x64-gnu": "1.78.0",
+ "@oxlint/binding-linux-x64-musl": "1.78.0",
+ "@oxlint/binding-openharmony-arm64": "1.78.0",
+ "@oxlint/binding-win32-arm64-msvc": "1.78.0",
+ "@oxlint/binding-win32-ia32-msvc": "1.78.0",
+ "@oxlint/binding-win32-x64-msvc": "1.78.0"
+ },
+ "peerDependencies": {
+ "oxlint-tsgolint": ">=7.0.2001",
+ "vite-plus": "*"
+ },
+ "peerDependenciesMeta": {
+ "oxlint-tsgolint": {
+ "optional": true
+ },
+ "vite-plus": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postprocessing": {
+ "version": "6.39.4",
+ "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.39.4.tgz",
+ "integrity": "sha512-oAS/PjAbc/xT3OzjUrGsorJ4J064XwhVD2t0OwKLP/E8QwDMUJ0oOv6ZI1SYMtLchv4g0ySEx+JcLy92+48vlA==",
+ "license": "Zlib",
+ "peer": true,
+ "peerDependencies": {
+ "three": ">= 0.168.0 < 0.186.0"
+ }
+ },
+ "node_modules/potpack": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
+ "license": "ISC"
+ },
+ "node_modules/promise-worker-transferable": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
+ "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "is-promise": "^2.1.0",
+ "lie": "^3.0.2"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
+ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.8"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz",
+ "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz",
+ "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/react-use-measure": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
+ "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.13",
+ "react-dom": ">=16.13"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz",
+ "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.143.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.2.3",
+ "@rolldown/binding-darwin-arm64": "1.2.3",
+ "@rolldown/binding-darwin-x64": "1.2.3",
+ "@rolldown/binding-freebsd-x64": "1.2.3",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.3",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.3",
+ "@rolldown/binding-linux-arm64-musl": "1.2.3",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.3",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.3",
+ "@rolldown/binding-linux-x64-gnu": "1.2.3",
+ "@rolldown/binding-linux-x64-musl": "1.2.3",
+ "@rolldown/binding-openharmony-arm64": "1.2.3",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.3",
+ "@rolldown/binding-win32-x64-msvc": "1.2.3"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stats-gl": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
+ "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/three": "*",
+ "three": "^0.170.0"
+ },
+ "peerDependencies": {
+ "@types/three": "*",
+ "three": "*"
+ }
+ },
+ "node_modules/stats-gl/node_modules/three": {
+ "version": "0.170.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
+ "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
+ "license": "MIT"
+ },
+ "node_modules/stats.js": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
+ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
+ "license": "MIT"
+ },
+ "node_modules/suspend-react": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
+ "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=17.0"
+ }
+ },
+ "node_modules/three": {
+ "version": "0.185.1",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz",
+ "integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/three-mesh-bvh": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz",
+ "integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
+ "node_modules/three-stdlib": {
+ "version": "2.36.1",
+ "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
+ "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/draco3d": "^1.4.0",
+ "@types/offscreencanvas": "^2019.6.4",
+ "@types/webxr": "^0.5.2",
+ "draco3d": "^1.4.1",
+ "fflate": "^0.6.9",
+ "potpack": "^1.0.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.128.0"
+ }
+ },
+ "node_modules/three-stdlib/node_modules/fflate": {
+ "version": "0.6.11",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.11.tgz",
+ "integrity": "sha512-3JyEFWGjFn7zHmoa9+zG1BmW7X2okcmAB+0Cnu9UFbVs/jCBnl2A8o065ZlXiw145K3eBM3uLuzrYXC0RK7eDg==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/troika-three-text": {
+ "version": "0.52.5",
+ "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.5.tgz",
+ "integrity": "sha512-Ry3jRhic9pzcY4JduSvRRyDmVOSqEW19gT4vtK+aCiPNVcDlmkxvGG0YbFd36RTDq1wExOupXnvNF/j1oiHHDA==",
+ "license": "MIT",
+ "dependencies": {
+ "bidi-js": "^1.0.2",
+ "troika-three-utils": "^0.52.5",
+ "troika-worker-utils": "^0.52.0",
+ "webgl-sdf-generator": "1.1.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-three-utils": {
+ "version": "0.52.5",
+ "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.5.tgz",
+ "integrity": "sha512-WsePbcX8RtfidRfsxK1eCZCjF81ZDzAKHH/evLs0hdV2wpoCb0vArGZHdzdOJrSS3k4zfdtbKDaBh8+phkrYnw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-worker-utils": {
+ "version": "0.52.0",
+ "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
+ "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
+ "license": "MIT"
+ },
+ "node_modules/tunnel-rat": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
+ "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
+ "license": "MIT",
+ "dependencies": {
+ "zustand": "^4.3.2"
+ }
+ },
+ "node_modules/tunnel-rat/node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/typescript": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/utility-types": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
+ "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
+ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.25",
+ "rolldown": "~1.2.1",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.4.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webgl-constants": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
+ "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
+ },
+ "node_modules/webgl-sdf-generator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
+ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.14",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz",
+ "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..aa33987
--- /dev/null
+++ b/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "vyryi-front",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "oxlint",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@react-three/drei": "^10.7.8",
+ "@react-three/fiber": "^9.7.0",
+ "@react-three/postprocessing": "^3.0.5",
+ "gsap": "^3.15.0",
+ "react": "^19.2.8",
+ "react-dom": "^19.2.8",
+ "react-router-dom": "^7.18.2",
+ "three": "^0.185.1"
+ },
+ "devDependencies": {
+ "@types/node": "^24.13.3",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@types/three": "^0.185.4",
+ "@vitejs/plugin-react": "^6.0.4",
+ "oxlint": "^1.75.0",
+ "typescript": "~6.0.2",
+ "vite": "^8.2.0"
+ }
+}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..6893eb1
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/icons.svg b/public/icons.svg
new file mode 100644
index 0000000..e952219
--- /dev/null
+++ b/public/icons.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/logo.png b/public/logo.png
new file mode 100644
index 0000000..b036856
Binary files /dev/null and b/public/logo.png differ
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..1c4f4dc
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,53 @@
+import type { ReactNode } from 'react'
+import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
+import { AuthProvider, useAuth } from './auth'
+import { PRIVACY_DOC, TERMS_DOC } from './legal'
+import { AgreementPage } from './pages/AgreementPage'
+import { GamePage } from './pages/GamePage'
+import { HomePage } from './pages/HomePage'
+import { LegalDocumentPage } from './pages/LegalDocumentPage'
+import { RootRedirect } from './pages/RootRedirect'
+import { GateTransitProvider } from './vfx/GateTransitProvider'
+
+function BootShell({ children }: { children: ReactNode }) {
+ const { loading } = useAuth()
+ if (loading) {
+ return (
+
+

+
Vyryi
+
+ )
+ }
+ return children
+}
+
+export default function App() {
+ return (
+
+
+
+
+
+ } />
+ } />
+ }
+ />
+
+ }
+ />
+ } />
+ } />
+ } />
+
+
+
+
+
+ )
+}
diff --git a/src/api.ts b/src/api.ts
new file mode 100644
index 0000000..ef54845
--- /dev/null
+++ b/src/api.ts
@@ -0,0 +1,83 @@
+import type { GameStub, TokenResponse, User, Wallet } from './types'
+
+const API_URL = import.meta.env.VITE_API_URL ?? 'http://127.0.0.1:8000'
+const TOKEN_KEY = 'vyryi_token'
+
+export function getToken(): string | null {
+ return localStorage.getItem(TOKEN_KEY)
+}
+
+export function setToken(token: string): void {
+ localStorage.setItem(TOKEN_KEY, token)
+}
+
+export function clearToken(): void {
+ localStorage.removeItem(TOKEN_KEY)
+}
+
+async function request(path: string, options: RequestInit = {}): Promise {
+ const headers = new Headers(options.headers)
+ headers.set('Content-Type', 'application/json')
+ const token = getToken()
+ if (token) {
+ headers.set('Authorization', `Bearer ${token}`)
+ }
+
+ const response = await fetch(`${API_URL}${path}`, {
+ ...options,
+ headers,
+ })
+
+ if (!response.ok) {
+ let detail = `Request failed (${response.status})`
+ try {
+ const body = (await response.json()) as { detail?: string }
+ if (body.detail) detail = body.detail
+ } catch {
+ /* ignore */
+ }
+ throw new Error(detail)
+ }
+
+ return (await response.json()) as T
+}
+
+export const api = {
+ async devLogin(payload?: {
+ telegram_id?: number
+ username?: string
+ first_name?: string
+ }): Promise {
+ return request('/auth/dev-login', {
+ method: 'POST',
+ body: JSON.stringify(payload ?? {}),
+ })
+ },
+
+ getMe(): Promise {
+ return request('/me')
+ },
+
+ acceptAgreement(): Promise {
+ return request('/me/agreement', { method: 'POST' })
+ },
+
+ getWallet(): Promise {
+ return request('/wallet')
+ },
+
+ deposit(amount: number): Promise {
+ return request('/wallet/deposit', {
+ method: 'POST',
+ body: JSON.stringify({ amount }),
+ })
+ },
+
+ listGames(): Promise {
+ return request('/games')
+ },
+
+ getGame(slug: string): Promise {
+ return request(`/games/${slug}`)
+ },
+}
diff --git a/src/assets/hero.png b/src/assets/hero.png
new file mode 100644
index 0000000..02251f4
Binary files /dev/null and b/src/assets/hero.png differ
diff --git a/src/auth.tsx b/src/auth.tsx
new file mode 100644
index 0000000..2f2a578
--- /dev/null
+++ b/src/auth.tsx
@@ -0,0 +1,83 @@
+import {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+ type ReactNode,
+} from 'react'
+import { api, clearToken, getToken, setToken } from './api'
+import type { User } from './types'
+
+type AuthState = {
+ user: User | null
+ loading: boolean
+ error: string | null
+ refresh: () => Promise
+ acceptAgreement: () => Promise
+ setUser: (user: User) => void
+}
+
+const AuthContext = createContext(null)
+
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ const bootstrap = useCallback(async () => {
+ setLoading(true)
+ setError(null)
+ try {
+ // Local MVP: no Telegram bot yet — always ensure a JWT via dev-login.
+ if (!getToken()) {
+ const token = await api.devLogin({
+ telegram_id: 100001,
+ username: 'dev_user',
+ first_name: 'Путник',
+ })
+ setToken(token.access_token)
+ }
+ const me = await api.getMe()
+ setUser(me)
+ } catch (err) {
+ clearToken()
+ setUser(null)
+ setError(err instanceof Error ? err.message : 'Не удалось войти')
+ } finally {
+ setLoading(false)
+ }
+ }, [])
+
+ useEffect(() => {
+ void bootstrap()
+ }, [bootstrap])
+
+ const acceptAgreement = useCallback(async () => {
+ const updated = await api.acceptAgreement()
+ setUser(updated)
+ }, [])
+
+ const value = useMemo(
+ () => ({
+ user,
+ loading,
+ error,
+ refresh: bootstrap,
+ acceptAgreement,
+ setUser,
+ }),
+ [user, loading, error, bootstrap, acceptAgreement],
+ )
+
+ return {children}
+}
+
+export function useAuth(): AuthState {
+ const ctx = useContext(AuthContext)
+ if (!ctx) {
+ throw new Error('useAuth must be used within AuthProvider')
+ }
+ return ctx
+}
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..13f3f7c
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,618 @@
+@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,500;0,600;0,700;1,500&family=Source+Sans+3:wght@400;500;600&display=swap');
+
+:root {
+ --bg-deep: #1a1b16;
+ --bg: #2f3027;
+ --bg-lift: #3a3b31;
+ --gold: #a68946;
+ --gold-soft: #c4a96a;
+ --burgundy: #8c2b2b;
+ --eye: #c8d97d;
+ --cream: #e8e0d0;
+ --muted: #9a9486;
+ --ink: #0f100d;
+ --radius: 10px;
+ --font-display: 'Cormorant Garamond', 'Palatino Linotype', serif;
+ --font-body: 'Source Sans 3', 'Segoe UI', sans-serif;
+ color: var(--cream);
+ background: var(--bg-deep);
+ font-family: var(--font-body);
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+ min-height: 100%;
+ margin: 0;
+}
+
+body {
+ min-height: 100vh;
+ background:
+ radial-gradient(ellipse 80% 50% at 50% -10%, rgba(166, 137, 70, 0.18), transparent 55%),
+ radial-gradient(ellipse 60% 40% at 80% 100%, rgba(140, 43, 43, 0.12), transparent 50%),
+ linear-gradient(165deg, var(--bg-deep) 0%, var(--bg) 45%, #25261f 100%);
+ background-attachment: fixed;
+}
+
+body::before {
+ content: '';
+ position: fixed;
+ inset: 0;
+ pointer-events: none;
+ opacity: 0.07;
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
+ z-index: 0;
+}
+
+#root {
+ position: relative;
+ z-index: 1;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+button,
+input {
+ font: inherit;
+}
+
+.brand {
+ margin: 0;
+ font-family: var(--font-display);
+ font-weight: 700;
+ font-size: clamp(2.8rem, 10vw, 4rem);
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--gold-soft);
+ text-shadow: 0 2px 24px rgba(166, 137, 70, 0.35);
+ animation: brand-in 0.9s ease-out both;
+}
+
+.brand--sm {
+ font-size: 1.35rem;
+ letter-spacing: 0.12em;
+ animation: none;
+}
+
+.boot {
+ min-height: 100vh;
+ display: grid;
+ place-content: center;
+ gap: 0.75rem;
+ text-align: center;
+}
+
+.boot__logo {
+ width: min(42vw, 180px);
+ margin: 0 auto;
+ animation: logo-rise 1.1s ease-out both;
+ filter: drop-shadow(0 8px 28px rgba(0, 0, 0, 0.55));
+}
+
+.gate {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem 1.25rem 3rem;
+ position: relative;
+ overflow: hidden;
+ transform-origin: center center;
+}
+
+.gate__glow {
+ position: absolute;
+ width: 280px;
+ height: 280px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(200, 217, 125, 0.16), transparent 70%);
+ top: 12%;
+ left: 50%;
+ transform: translateX(-50%);
+ animation: pulse-glow 4s ease-in-out infinite;
+ pointer-events: none;
+}
+
+.gate__content {
+ width: min(100%, 420px);
+ text-align: center;
+ position: relative;
+}
+
+.gate__logo {
+ width: min(58vw, 220px);
+ display: block;
+ margin: 0 auto 0.5rem;
+ animation: logo-rise 1.1s ease-out both;
+ filter: drop-shadow(0 10px 32px rgba(0, 0, 0, 0.6));
+}
+
+.gate__tagline {
+ margin: 0.35rem 0 1.75rem;
+ color: var(--muted);
+ font-size: 1.05rem;
+ font-style: italic;
+ font-family: var(--font-display);
+ animation: fade-up 1s 0.2s ease-out both;
+}
+
+.agreement {
+ text-align: left;
+ animation: fade-up 0.9s 0.35s ease-out both;
+}
+
+.agreement__check {
+ display: flex;
+ gap: 0.65rem;
+ align-items: flex-start;
+ margin-bottom: 1rem;
+ color: var(--cream);
+ font-size: 0.95rem;
+ cursor: pointer;
+}
+
+.agreement__check input {
+ margin-top: 0.2rem;
+ accent-color: var(--gold);
+}
+
+.legal-link {
+ color: var(--gold-soft);
+ text-decoration: underline;
+ text-underline-offset: 0.15em;
+}
+
+.legal-link:hover {
+ color: var(--eye);
+}
+
+.legal-page {
+ max-width: 560px;
+ margin: 0 auto;
+ padding: 1.25rem 1.15rem 2.5rem;
+}
+
+.legal-doc {
+ margin-top: 1.25rem;
+}
+
+.legal-doc h1 {
+ margin: 0 0 1rem;
+ font-family: var(--font-display);
+ font-size: clamp(1.6rem, 5vw, 2rem);
+ font-weight: 600;
+ color: var(--gold-soft);
+}
+
+.legal-doc p {
+ margin: 0 0 0.9rem;
+ color: var(--muted);
+ line-height: 1.55;
+ font-size: 0.98rem;
+}
+
+.legal-doc p:last-child {
+ margin-bottom: 0;
+}
+
+.btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ border: none;
+ border-radius: var(--radius);
+ padding: 0.85rem 1.1rem;
+ cursor: pointer;
+ transition: transform 0.15s ease, filter 0.15s ease, background 0.15s ease, opacity 0.15s ease;
+}
+
+.btn:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+
+.btn:not(:disabled):active {
+ transform: translateY(1px);
+}
+
+.btn--gold {
+ background: linear-gradient(180deg, var(--gold-soft), var(--gold));
+ color: var(--ink);
+ font-weight: 600;
+ letter-spacing: 0.04em;
+}
+
+.btn--gold:not(:disabled):hover {
+ filter: brightness(1.06);
+}
+
+.btn--sm {
+ width: auto;
+ padding: 0.45rem 0.85rem;
+ font-size: 0.85rem;
+}
+
+.btn--outline {
+ background: transparent;
+ border: 1px solid var(--gold);
+ color: var(--gold-soft);
+}
+
+.btn--outline:not(:disabled):hover {
+ background: rgba(166, 137, 70, 0.12);
+}
+
+.btn--ghost {
+ background: transparent;
+ color: var(--muted);
+ margin-top: 0.5rem;
+}
+
+.error {
+ color: #e8a0a0;
+ font-size: 0.9rem;
+ margin: 0 0 0.75rem;
+}
+
+.linkish {
+ display: inline;
+ margin-left: 0.5rem;
+ background: none;
+ border: none;
+ color: var(--eye);
+ cursor: pointer;
+ text-decoration: underline;
+ padding: 0;
+}
+
+.home {
+ max-width: 520px;
+ margin: 0 auto;
+ padding: 1.25rem 1.15rem 2.5rem;
+}
+
+.home__top {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.home__brand-row {
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+}
+
+.home__mark {
+ width: 42px;
+ height: 42px;
+ object-fit: contain;
+}
+
+.balance-panel {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ grid-template-areas:
+ 'label btn'
+ 'value btn';
+ gap: 0.15rem 1rem;
+ align-items: center;
+ padding: 0.9rem 1rem;
+ background: rgba(15, 16, 13, 0.4);
+ border-bottom: 1px solid rgba(166, 137, 70, 0.4);
+}
+
+.balance-panel__label {
+ grid-area: label;
+ color: var(--muted);
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.balance-panel__value {
+ grid-area: value;
+ font-family: var(--font-display);
+ font-size: 2rem;
+ font-weight: 600;
+ color: var(--eye);
+}
+
+.balance-panel .btn {
+ grid-area: btn;
+}
+
+.section-lead {
+ margin: 0 0 1rem;
+ color: var(--muted);
+ font-size: 0.95rem;
+}
+
+.games h2 {
+ margin: 0 0 0.35rem;
+ font-family: var(--font-display);
+ font-size: 1.75rem;
+ font-weight: 600;
+ color: var(--gold-soft);
+}
+
+.games__list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.game-link {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 0.25rem 0.75rem;
+ padding: 0.95rem 0.25rem 1rem;
+ border-bottom: 1px solid rgba(166, 137, 70, 0.22);
+ transition: background 0.2s ease, padding-left 0.2s ease;
+}
+
+.game-link:hover {
+ background: rgba(166, 137, 70, 0.06);
+ padding-left: 0.35rem;
+}
+
+.game-link__title {
+ font-family: var(--font-display);
+ font-size: 1.25rem;
+ font-weight: 600;
+}
+
+.game-link__status {
+ color: var(--burgundy);
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ align-self: center;
+}
+
+.game-link__desc {
+ grid-column: 1 / -1;
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.toast {
+ margin: 0 0 1rem;
+ color: var(--eye);
+ font-size: 0.9rem;
+}
+
+.modal {
+ position: fixed;
+ inset: 0;
+ background: rgba(10, 10, 8, 0.72);
+ display: grid;
+ place-items: center;
+ padding: 1.25rem;
+ z-index: 20;
+ animation: fade-in 0.2s ease;
+}
+
+.modal__panel {
+ width: min(100%, 360px);
+ padding: 1.35rem 1.2rem;
+ background: linear-gradient(180deg, var(--bg-lift), var(--bg));
+ border: 1px solid rgba(166, 137, 70, 0.4);
+ border-radius: var(--radius);
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
+}
+
+.modal__panel h3 {
+ margin: 0 0 0.35rem;
+ font-family: var(--font-display);
+ font-size: 1.5rem;
+ color: var(--gold-soft);
+}
+
+.deposit-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 0.5rem;
+ margin-bottom: 0.5rem;
+}
+
+.game-page {
+ max-width: 520px;
+ margin: 0 auto;
+ padding: 1.25rem 1.15rem 2.5rem;
+}
+
+.back-link {
+ color: var(--muted);
+ font-size: 0.9rem;
+}
+
+.back-link:hover {
+ color: var(--gold-soft);
+}
+
+.game-stub {
+ margin-top: 2rem;
+ text-align: center;
+}
+
+.game-stub__mark {
+ width: 120px;
+ margin-bottom: 1rem;
+ opacity: 0.9;
+ animation: logo-rise 0.8s ease-out both;
+}
+
+.game-stub h1 {
+ margin: 0 0 0.5rem;
+ font-family: var(--font-display);
+ font-size: 2rem;
+ color: var(--gold-soft);
+}
+
+.game-stub p {
+ color: var(--muted);
+ margin: 0 0 1rem;
+}
+
+.game-stub__badge {
+ display: inline-block;
+ color: var(--burgundy);
+ border: 1px solid rgba(140, 43, 43, 0.5);
+ padding: 0.35rem 0.85rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ font-size: 0.75rem;
+}
+
+@keyframes logo-rise {
+ from {
+ opacity: 0;
+ transform: translateY(18px) scale(0.96);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ }
+}
+
+@keyframes brand-in {
+ from {
+ opacity: 0;
+ letter-spacing: 0.2em;
+ }
+ to {
+ opacity: 1;
+ letter-spacing: 0.08em;
+ }
+}
+
+@keyframes fade-up {
+ from {
+ opacity: 0;
+ transform: translateY(12px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes pulse-glow {
+ 0%,
+ 100% {
+ opacity: 0.55;
+ transform: translateX(-50%) scale(1);
+ }
+ 50% {
+ opacity: 1;
+ transform: translateX(-50%) scale(1.08);
+ }
+}
+
+@media (min-width: 640px) {
+ .home__top {
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ }
+}
+
+/* Gate logo fly-in transit */
+html.gate-transit-lock,
+html.gate-transit-lock body {
+ overflow: hidden;
+ touch-action: none;
+}
+
+.gate-transit {
+ position: fixed;
+ inset: 0;
+ z-index: 9000;
+ pointer-events: none;
+ overflow: hidden;
+}
+
+.gate-transit__stage {
+ position: absolute;
+ inset: 0;
+ perspective: 1400px;
+ perspective-origin: 50% 46%;
+}
+
+.gate-transit__hero {
+ /* Hidden until JS pins it over .gate__logo — avoids first-frame size flash */
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 0;
+ height: 0;
+ opacity: 0;
+ visibility: hidden;
+ display: block;
+ margin: 0;
+ pointer-events: none;
+ user-select: none;
+ object-fit: contain;
+ filter: drop-shadow(0 10px 32px rgba(0, 0, 0, 0.6));
+}
+
+
+.gate-transit__veil {
+ position: absolute;
+ inset: 0;
+ background: #000;
+ opacity: 0;
+ z-index: 3;
+}
+
+.home--from-gate {
+ animation: home-from-void 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
+}
+
+@keyframes home-from-void {
+ from {
+ opacity: 0;
+ transform: translateY(18px) scale(0.985);
+ filter: blur(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ filter: blur(0);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .home--from-gate {
+ animation: fade-in 0.45s ease-out both;
+ }
+}
+
diff --git a/src/legal.ts b/src/legal.ts
new file mode 100644
index 0000000..73fb4c0
--- /dev/null
+++ b/src/legal.ts
@@ -0,0 +1,23 @@
+export const TERMS_DOC = {
+ title: 'Пользовательское соглашение',
+ paragraphs: [
+ 'Настоящее Пользовательское соглашение (далее — «Соглашение») регулирует отношения между пользователем и сервисом Vyryi (далее — «Сервис») при использовании Telegram Mini App Vyryi.',
+ 'Vyryi — развлекательное приложение. Баланс в Сервисе можно пополнять. В текущей версии пополнение реализовано в тестовом режиме (заглушка). Вывод средств недоступен и не предусмотрен.',
+ 'Игры и иные функции Сервиса носят развлекательный характер. Участвуя в них, пользователь подтверждает, что понимает правила и принимает невозможность вывода денежных средств.',
+ 'Сервис вправе изменять набор игр, интерфейс, условия начисления и отображения баланса, а также временно ограничивать доступ для технического обслуживания.',
+ 'Пользователь обязуется не пытаться обойти ограничения Сервиса, не использовать вредоносное ПО и не нарушать работу приложения.',
+ 'Продолжая использование Сервиса после принятия Соглашения, пользователь подтверждает, что ознакомился с его условиями и полностью их принимает.',
+ ],
+}
+
+export const PRIVACY_DOC = {
+ title: 'Политика обработки персональных данных',
+ paragraphs: [
+ 'Настоящий документ описывает, какие данные обрабатывает сервис Vyryi и для каких целей.',
+ 'В рамках работы приложения могут обрабатываться идентификатор Telegram, имя пользователя, отображаемое имя, данные о принятии соглашений, баланс и история пополнений.',
+ 'Данные используются для аутентификации, предоставления функций Сервиса, учёта баланса, исполнения пользовательских соглашений и обеспечения безопасности.',
+ 'Данные хранятся на серверах, необходимых для работы приложения, и не передаются третьим лицам, за исключением случаев, прямо предусмотренных законом или технически необходимых для работы инфраструктуры.',
+ 'Пользователь может прекратить использование Сервиса. Вопросы по обработке данных можно направить оператору Сервиса после появления публичных контактов поддержки.',
+ 'Используя Vyryi и принимая соответствующие отметки на стартовом экране, пользователь подтверждает согласие на обработку указанных данных в объёме, необходимом для работы приложения.',
+ ],
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..bef5202
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/src/pages/AgreementPage.tsx b/src/pages/AgreementPage.tsx
new file mode 100644
index 0000000..a0d7730
--- /dev/null
+++ b/src/pages/AgreementPage.tsx
@@ -0,0 +1,93 @@
+import { useState } from 'react'
+import { Link } from 'react-router-dom'
+import { useAuth } from '../auth'
+import { useGateTransit } from '../vfx/GateTransitProvider'
+
+export function AgreementPage() {
+ const { user, loading, error, acceptAgreement, refresh } = useAuth()
+ const { start, active } = useGateTransit()
+ const [termsAccepted, setTermsAccepted] = useState(false)
+ const [privacyAccepted, setPrivacyAccepted] = useState(false)
+ const [submitting, setSubmitting] = useState(false)
+ const [localError, setLocalError] = useState(null)
+
+ const canEnter = termsAccepted && privacyAccepted
+
+ async function onEnter() {
+ if (!canEnter || submitting || active) return
+ setSubmitting(true)
+ setLocalError(null)
+
+ const result = await start(async () => {
+ if (!user?.agreement_accepted) {
+ await acceptAgreement()
+ }
+ })
+
+ if (!result.ok) {
+ setLocalError(result.error ?? 'Ошибка')
+ setSubmitting(false)
+ }
+ }
+
+ return (
+
+
+
+

+
Vyryi
+
Врата, за которыми нет возврата.
+
+
+
+
+ )
+}
diff --git a/src/pages/GamePage.tsx b/src/pages/GamePage.tsx
new file mode 100644
index 0000000..9a07d46
--- /dev/null
+++ b/src/pages/GamePage.tsx
@@ -0,0 +1,41 @@
+import { useEffect, useState } from 'react'
+import { Link, Navigate, useParams } from 'react-router-dom'
+import { api } from '../api'
+import { useAuth } from '../auth'
+import type { GameStub } from '../types'
+
+export function GamePage() {
+ const { slug } = useParams<{ slug: string }>()
+ const { user, loading } = useAuth()
+ const [game, setGame] = useState(null)
+ const [error, setError] = useState(null)
+
+ useEffect(() => {
+ if (!slug) return
+ void api
+ .getGame(slug)
+ .then(setGame)
+ .catch((err: unknown) => setError(err instanceof Error ? err.message : 'Не найдено'))
+ }, [slug])
+
+ if (!loading && user && !user.agreement_accepted) {
+ return
+ }
+
+ return (
+
+
+ ← Назад
+
+ {error && {error}
}
+ {game && (
+
+
+ {game.title}
+ {game.description}
+ В разработке
+
+ )}
+
+ )
+}
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
new file mode 100644
index 0000000..16f67c5
--- /dev/null
+++ b/src/pages/HomePage.tsx
@@ -0,0 +1,118 @@
+import { useEffect, useState } from 'react'
+import { Link, Navigate } from 'react-router-dom'
+import { api } from '../api'
+import { useAuth } from '../auth'
+import type { GameStub } from '../types'
+import { GATE_FROM_KEY } from '../vfx/gateCaps'
+
+const DEPOSIT_AMOUNTS = [100, 500, 1000]
+
+export function HomePage() {
+ const { user, loading, setUser } = useAuth()
+ const [games, setGames] = useState([])
+ const [depositOpen, setDepositOpen] = useState(false)
+ const [busy, setBusy] = useState(false)
+ const [message, setMessage] = useState(null)
+ const [fromGate, setFromGate] = useState(false)
+
+ useEffect(() => {
+ void api.listGames().then(setGames).catch(() => setGames([]))
+ }, [])
+
+ useEffect(() => {
+ try {
+ if (sessionStorage.getItem(GATE_FROM_KEY) === '1') {
+ sessionStorage.removeItem(GATE_FROM_KEY)
+ setFromGate(true)
+ }
+ } catch {
+ /* ignore */
+ }
+ }, [])
+
+ if (!loading && user && !user.agreement_accepted) {
+ return
+ }
+
+ if (!loading && !user) {
+ return
+ }
+
+ async function deposit(amount: number) {
+ setBusy(true)
+ setMessage(null)
+ try {
+ const wallet = await api.deposit(amount)
+ if (user) {
+ setUser({ ...user, balance: wallet.balance })
+ }
+ setMessage(`Зачислено ${amount}`)
+ setDepositOpen(false)
+ } catch (err) {
+ setMessage(err instanceof Error ? err.message : 'Ошибка пополнения')
+ } finally {
+ setBusy(false)
+ }
+ }
+
+ return (
+
+
+
+ {message && {message}
}
+
+
+ Слоты
+ Врата ещё закрыты — пока только предвестники.
+
+ {games.map((game) => (
+ -
+
+ {game.title}
+ Скоро
+ {game.description}
+
+
+ ))}
+
+
+
+ {depositOpen && (
+
+
+
Пополнение
+
Тестовая заглушка. Вывод недоступен.
+
+ {DEPOSIT_AMOUNTS.map((amount) => (
+
+ ))}
+
+
+
+
+ )}
+
+ )
+}
diff --git a/src/pages/LegalDocumentPage.tsx b/src/pages/LegalDocumentPage.tsx
new file mode 100644
index 0000000..e7caa3a
--- /dev/null
+++ b/src/pages/LegalDocumentPage.tsx
@@ -0,0 +1,22 @@
+import { Link } from 'react-router-dom'
+
+type LegalDocumentPageProps = {
+ title: string
+ paragraphs: string[]
+}
+
+export function LegalDocumentPage({ title, paragraphs }: LegalDocumentPageProps) {
+ return (
+
+
+ ← Назад
+
+
+ {title}
+ {paragraphs.map((text, index) => (
+ {text}
+ ))}
+
+
+ )
+}
diff --git a/src/pages/RootRedirect.tsx b/src/pages/RootRedirect.tsx
new file mode 100644
index 0000000..8546d5b
--- /dev/null
+++ b/src/pages/RootRedirect.tsx
@@ -0,0 +1,12 @@
+import { Navigate } from 'react-router-dom'
+import { useAuth } from '../auth'
+
+/** `/` → home if agreement done, otherwise onboarding. */
+export function RootRedirect() {
+ const { user, loading } = useAuth()
+ if (loading) return null
+ if (user?.agreement_accepted) {
+ return
+ }
+ return
+}
diff --git a/src/types.ts b/src/types.ts
new file mode 100644
index 0000000..695fd8a
--- /dev/null
+++ b/src/types.ts
@@ -0,0 +1,33 @@
+export type User = {
+ id: number
+ telegram_id: number | null
+ username: string | null
+ first_name: string | null
+ balance: string
+ agreement_accepted: boolean
+ agreement_accepted_at: string | null
+ created_at: string
+}
+
+export type Deposit = {
+ id: number
+ amount: string
+ created_at: string
+}
+
+export type Wallet = {
+ balance: string
+ deposits: Deposit[]
+}
+
+export type GameStub = {
+ slug: string
+ title: string
+ description: string
+ status: string
+}
+
+export type TokenResponse = {
+ access_token: string
+ token_type: string
+}
diff --git a/src/vfx/GateTransitProvider.tsx b/src/vfx/GateTransitProvider.tsx
new file mode 100644
index 0000000..3a3d6a1
--- /dev/null
+++ b/src/vfx/GateTransitProvider.tsx
@@ -0,0 +1,323 @@
+import {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+ type ReactNode,
+} from 'react'
+import { createPortal } from 'react-dom'
+import { useNavigate } from 'react-router-dom'
+import gsap from 'gsap'
+import { GATE_FROM_KEY, prefersReducedMotion, type GateTransitPhase } from './gateCaps'
+import {
+ createLogoFlyTimeline,
+ createReducedLogoFlyTimeline,
+ type LogoFlyTargets,
+} from './gateTimeline'
+
+type StartResult = {
+ ok: boolean
+ error?: string
+}
+
+type GateTransitContextValue = {
+ phase: GateTransitPhase
+ active: boolean
+ start: (runAccept: () => Promise) => Promise
+}
+
+const GateTransitContext = createContext(null)
+
+export function useGateTransit() {
+ const ctx = useContext(GateTransitContext)
+ if (!ctx) {
+ throw new Error('useGateTransit must be used within GateTransitProvider')
+ }
+ return ctx
+}
+
+function collectUiFade(gate: HTMLElement, logo: HTMLElement | null): HTMLElement[] {
+ const nodes: HTMLElement[] = []
+ const glow = gate.querySelector('.gate__glow')
+ if (glow) nodes.push(glow)
+ const content = gate.querySelector('.gate__content')
+ if (content) {
+ for (const el of Array.from(content.children)) {
+ if (el instanceof HTMLElement && el !== logo) nodes.push(el)
+ }
+ }
+ return nodes
+}
+
+function placeHeroOverLogo(hero: HTMLElement, logo: HTMLElement) {
+ const rect = logo.getBoundingClientRect()
+ // Geometry only — keep invisible until the original logo is hidden in the same frame
+ gsap.set(hero, {
+ position: 'fixed',
+ top: rect.top,
+ left: rect.left,
+ width: rect.width,
+ height: rect.height,
+ margin: 0,
+ zIndex: 2,
+ opacity: 0,
+ visibility: 'hidden',
+ scale: 1,
+ rotation: 0,
+ z: 0,
+ filter: 'drop-shadow(0 10px 32px rgba(0, 0, 0, 0.6))',
+ objectFit: 'contain',
+ transformOrigin: '50% 46%',
+ transformPerspective: 1400,
+ force3D: true,
+ willChange: 'transform, filter, opacity',
+ })
+}
+
+function revealHeroOverLogo(hero: HTMLElement, logo: HTMLElement) {
+ gsap.set(logo, { opacity: 0 })
+ gsap.set(hero, { opacity: 1, visibility: 'visible' })
+}
+
+export function GateTransitProvider({ children }: { children: ReactNode }) {
+ const navigate = useNavigate()
+ const [phase, setPhase] = useState('idle')
+ const [showOverlay, setShowOverlay] = useState(false)
+
+ const veilRef = useRef(null)
+ const stageRef = useRef(null)
+ const heroRef = useRef(null)
+ const timelineRef = useRef(null)
+ const acceptPromiseRef = useRef | null>(null)
+ const acceptErrorRef = useRef(null)
+ const acceptOkRef = useRef(false)
+ const navigatedRef = useRef(false)
+ const resolveStartRef = useRef<((result: StartResult) => void) | null>(null)
+ const reducedRef = useRef(false)
+
+ const cleanupDomLocks = useCallback(() => {
+ document.documentElement.classList.remove('gate-transit-lock')
+ const gate = document.querySelector('.gate')
+ const logo = document.querySelector('.gate__logo')
+ const glow = document.querySelector('.gate__glow')
+ if (logo) gsap.set(logo, { clearProps: 'opacity' })
+ if (glow) gsap.set(glow, { clearProps: 'opacity,filter,transform' })
+ if (gate) {
+ gate.querySelectorAll('.gate__content > *').forEach((el) => {
+ gsap.set(el, { clearProps: 'opacity,filter,transform' })
+ })
+ }
+ }, [])
+
+ const finish = useCallback(
+ (result: StartResult) => {
+ timelineRef.current?.kill()
+ timelineRef.current = null
+ setShowOverlay(false)
+ setPhase(result.ok ? 'done' : 'error')
+ cleanupDomLocks()
+ resolveStartRef.current?.(result)
+ resolveStartRef.current = null
+ window.setTimeout(() => {
+ setPhase('idle')
+ acceptPromiseRef.current = null
+ acceptErrorRef.current = null
+ acceptOkRef.current = false
+ navigatedRef.current = false
+ }, 80)
+ },
+ [cleanupDomLocks],
+ )
+
+ const handleBlackout = useCallback(async () => {
+ setPhase('blackout')
+
+ const waitAccept = async () => {
+ if (acceptOkRef.current) return true
+ if (acceptErrorRef.current) return false
+ if (!acceptPromiseRef.current) return false
+ try {
+ await Promise.race([
+ acceptPromiseRef.current,
+ new Promise((_, reject) => {
+ window.setTimeout(() => reject(new Error('Слишком долго. Попробуйте ещё раз.')), 1500)
+ }),
+ ])
+ return acceptOkRef.current
+ } catch (err) {
+ if (!acceptErrorRef.current) {
+ acceptErrorRef.current = err instanceof Error ? err.message : 'Ошибка'
+ }
+ return false
+ }
+ }
+
+ const ok = await waitAccept()
+ if (!ok) {
+ finish({ ok: false, error: acceptErrorRef.current ?? 'Ошибка' })
+ return
+ }
+
+ if (!navigatedRef.current) {
+ navigatedRef.current = true
+ try {
+ sessionStorage.setItem(GATE_FROM_KEY, '1')
+ } catch {
+ /* ignore */
+ }
+ navigate('/home', { replace: true, state: { fromGate: true } })
+ }
+ }, [finish, navigate])
+
+ const handleRevealStart = useCallback(() => {
+ setPhase('revealing')
+ }, [])
+
+ const handleComplete = useCallback(() => {
+ finish({ ok: true })
+ }, [finish])
+
+ const startTimeline = useCallback(() => {
+ const logo = document.querySelector('.gate__logo')
+ const gate = document.querySelector('.gate')
+ const hero = heroRef.current
+ if (!hero || !logo || !gate) {
+ finish({ ok: false, error: 'Не удалось запустить переход' })
+ return
+ }
+
+ placeHeroOverLogo(hero, logo)
+
+ // One frame with hero invisible + correct box, then atomic swap with the page logo
+ requestAnimationFrame(() => {
+ if (timelineRef.current) return
+ placeHeroOverLogo(hero, logo)
+ revealHeroOverLogo(hero, logo)
+
+ const targets: LogoFlyTargets = {
+ hero,
+ uiFade: collectUiFade(gate, logo),
+ originalLogo: logo,
+ veil: veilRef.current,
+ stage: stageRef.current,
+ }
+
+ const handlers = {
+ onBlackout: () => {
+ void handleBlackout()
+ },
+ onRevealStart: handleRevealStart,
+ onComplete: handleComplete,
+ }
+
+ timelineRef.current = reducedRef.current
+ ? createReducedLogoFlyTimeline(targets, handlers)
+ : createLogoFlyTimeline(targets, handlers)
+ })
+ }, [finish, handleBlackout, handleComplete, handleRevealStart])
+
+ useEffect(() => {
+ if (phase !== 'playing' || !showOverlay) return
+
+ let cancelled = false
+ let tries = 0
+
+ const tick = () => {
+ if (cancelled || timelineRef.current) return
+ if (!heroRef.current || !document.querySelector('.gate__logo')) {
+ tries += 1
+ if (tries < 30) {
+ window.setTimeout(tick, 32)
+ return
+ }
+ finish({ ok: false, error: 'Не удалось запустить переход' })
+ return
+ }
+ startTimeline()
+ }
+
+ const id = window.setTimeout(tick, 0)
+ return () => {
+ cancelled = true
+ window.clearTimeout(id)
+ }
+ }, [phase, showOverlay, startTimeline, finish])
+
+ const start = useCallback(
+ (runAccept: () => Promise) => {
+ if (phase !== 'idle' && phase !== 'done' && phase !== 'error') {
+ return Promise.resolve({ ok: false, error: 'Переход уже идёт' })
+ }
+
+ reducedRef.current = prefersReducedMotion()
+ setShowOverlay(true)
+ setPhase('playing')
+ document.documentElement.classList.add('gate-transit-lock')
+
+ acceptOkRef.current = false
+ acceptErrorRef.current = null
+ navigatedRef.current = false
+ timelineRef.current = null
+
+ const acceptPromise = runAccept()
+ .then(() => {
+ acceptOkRef.current = true
+ })
+ .catch((err: unknown) => {
+ acceptErrorRef.current = err instanceof Error ? err.message : 'Ошибка'
+ throw err
+ })
+ acceptPromiseRef.current = acceptPromise
+ void acceptPromise.catch(() => undefined)
+
+ return new Promise((resolve) => {
+ resolveStartRef.current = resolve
+ })
+ },
+ [phase],
+ )
+
+ useEffect(() => {
+ return () => {
+ timelineRef.current?.kill()
+ cleanupDomLocks()
+ }
+ }, [cleanupDomLocks])
+
+ const value = useMemo(
+ () => ({
+ phase,
+ active: phase === 'playing' || phase === 'blackout' || phase === 'revealing',
+ start,
+ }),
+ [phase, start],
+ )
+
+ const overlayVisible =
+ showOverlay && (phase === 'playing' || phase === 'blackout' || phase === 'revealing')
+
+ return (
+
+ {children}
+ {overlayVisible &&
+ createPortal(
+
+
+

+
+
+
,
+ document.body,
+ )}
+
+ )
+}
diff --git a/src/vfx/gateCaps.ts b/src/vfx/gateCaps.ts
new file mode 100644
index 0000000..458f676
--- /dev/null
+++ b/src/vfx/gateCaps.ts
@@ -0,0 +1,14 @@
+export type GateTransitPhase =
+ | 'idle'
+ | 'playing'
+ | 'blackout'
+ | 'revealing'
+ | 'done'
+ | 'error'
+
+export const GATE_FROM_KEY = 'vyryi:from-gate'
+
+export function prefersReducedMotion(): boolean {
+ if (typeof window === 'undefined') return false
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches
+}
diff --git a/src/vfx/gateTimeline.ts b/src/vfx/gateTimeline.ts
new file mode 100644
index 0000000..81b72e0
--- /dev/null
+++ b/src/vfx/gateTimeline.ts
@@ -0,0 +1,130 @@
+import gsap from 'gsap'
+
+export type GateTimelineHandlers = {
+ onBlackout: () => void
+ onRevealStart: () => void
+ onComplete: () => void
+}
+
+export type LogoFlyTargets = {
+ hero: HTMLElement
+ uiFade: HTMLElement[]
+ originalLogo: HTMLElement | null
+ veil: HTMLElement | null
+ stage: HTMLElement | null
+}
+
+/** Continuously fly into logo.png (central black arch), then void → home. */
+export function createLogoFlyTimeline(targets: LogoFlyTargets, handlers: GateTimelineHandlers) {
+ const { hero, uiFade, veil, stage } = targets
+
+ gsap.set(hero, {
+ transformOrigin: '50% 46%',
+ transformPerspective: 1400,
+ force3D: true,
+ scale: 1,
+ z: 0,
+ rotation: 0,
+ filter: 'brightness(1) contrast(1) drop-shadow(0 10px 32px rgba(0, 0, 0, 0.6))',
+ })
+
+ if (stage) {
+ gsap.set(stage, { perspective: 1400 })
+ }
+
+ if (veil) {
+ gsap.set(veil, { opacity: 0 })
+ }
+
+ const tl = gsap.timeline({
+ onComplete: () => handlers.onComplete(),
+ })
+
+ if (uiFade.length) {
+ tl.to(
+ uiFade,
+ {
+ opacity: 0,
+ y: 18,
+ filter: 'blur(10px)',
+ duration: 0.4,
+ stagger: 0.04,
+ ease: 'power2.out',
+ },
+ 0,
+ )
+ }
+
+ // Dolly into the black arch — image stays on screen the whole time
+ tl.to(
+ hero,
+ {
+ scale: 26,
+ z: 280,
+ rotation: 0.4,
+ duration: 2.15,
+ ease: 'power3.in',
+ force3D: true,
+ },
+ 0.05,
+ )
+
+ tl.to(
+ hero,
+ {
+ filter:
+ 'brightness(0.35) contrast(1.25) drop-shadow(0 10px 32px rgba(0, 0, 0, 0.6))',
+ duration: 1.1,
+ ease: 'power2.in',
+ },
+ 1.1,
+ )
+
+ if (veil) {
+ tl.to(veil, { opacity: 1, duration: 0.55, ease: 'power2.in' }, 1.75)
+ }
+
+ tl.call(() => handlers.onBlackout(), undefined, 2.15)
+ tl.to({}, { duration: 0.2 }, 2.15)
+ tl.call(() => handlers.onRevealStart(), undefined, 2.35)
+
+ if (veil) {
+ tl.to(veil, { opacity: 0, duration: 0.85, ease: 'power2.out' }, 2.35)
+ }
+
+ tl.to(hero, { opacity: 0, duration: 0.35, ease: 'power1.in' }, 2.4)
+
+ return tl
+}
+
+export function createReducedLogoFlyTimeline(
+ targets: LogoFlyTargets,
+ handlers: GateTimelineHandlers,
+) {
+ const { hero, uiFade, veil } = targets
+
+ gsap.set(hero, { transformOrigin: '50% 46%', scale: 1 })
+ if (veil) gsap.set(veil, { opacity: 0 })
+
+ const tl = gsap.timeline({ onComplete: () => handlers.onComplete() })
+
+ if (uiFade.length) {
+ tl.to(uiFade, { opacity: 0, duration: 0.3, ease: 'power1.out' }, 0)
+ }
+
+ tl.to(hero, { scale: 14, duration: 0.85, ease: 'power2.in' }, 0)
+ if (veil) {
+ tl.to(veil, { opacity: 1, duration: 0.4, ease: 'power2.in' }, 0.45)
+ }
+
+ tl.call(() => handlers.onBlackout(), undefined, 0.85)
+ tl.to({}, { duration: 0.15 }, 0.85)
+ tl.call(() => handlers.onRevealStart(), undefined, 1.0)
+
+ if (veil) {
+ tl.to(veil, { opacity: 0, duration: 0.55, ease: 'power2.out' }, 1.0)
+ }
+ tl.to(hero, { opacity: 0, duration: 0.25 }, 1.0)
+
+ return tl
+}
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 0000000..6830b6f
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "es2023",
+ "lib": ["ES2023", "DOM"],
+ "module": "esnext",
+ "types": ["vite/client"],
+ "allowArbitraryExtensions": true,
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..8455dcb
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "es2023",
+ "lib": ["ES2023"],
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "module": "nodenext",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..8b0f57b
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})