From 3e7b2beca1136a42700a7e1aebfe4c0fb2861a00 Mon Sep 17 00:00:00 2001 From: soryu Date: Sat, 15 Nov 2025 18:00:09 +0000 Subject: Initial commit --- frontend/README.md | 25 + frontend/index.html | 14 + frontend/package-lock.json | 1641 ++++++++ frontend/package.json | 27 + frontend/public/.gitkeep | 0 ...e8f18ac64522e410bad04e68_waifu2x_art_noise2.png | Bin 0 -> 166561 bytes frontend/public/a10-nerve-clips.svg | 26 + frontend/public/ascii/ascii-flagged-3.txt | 65 + frontend/public/ascii/ascii-flagged-7.txt | 15 + frontend/public/ascii/ascii1.txt | 40 + frontend/public/ascii/ascii2.txt | 26 + frontend/public/ascii/ascii3.txt | 25 + frontend/public/ascii/ascii4.txt | 33 + frontend/public/ascii/ascii5.txt | 30 + frontend/public/ascii/ascii6.txt | 32 + frontend/public/ascii/ascii7.txt | 28 + frontend/public/asuka-headset-logo.svg | 101 + frontend/public/background-animation.gif | Bin 0 -> 207908 bytes frontend/public/kaomoji.txt | 3 + frontend/public/logo/crane-logo-transparent.png | Bin 0 -> 142306 bytes frontend/public/logo/crane-logo.png | Bin 0 -> 425121 bytes frontend/public/nazrin-bg.jpg | Bin 0 -> 198804 bytes frontend/src/App.tsx | 19 + frontend/src/components/BottomBar.tsx | 19 + frontend/src/components/ChoiceMenu.tsx | 20 + frontend/src/components/CityscapeBackground.tsx | 310 ++ frontend/src/components/ConfigModal.tsx | 44 + frontend/src/components/DialogueBox.tsx | 15 + frontend/src/components/HeartLogo.tsx | 270 ++ frontend/src/components/LandingPage.tsx | 219 + frontend/src/components/LoadingScreen.tsx | 129 + frontend/src/components/OrigamiDragonLogo.tsx | 180 + frontend/src/components/TopBar.tsx | 24 + frontend/src/components/VNApp.tsx | 228 + frontend/src/components/VNInterface.tsx | 208 + frontend/src/components/VNViewport.tsx | 22 + frontend/src/main.tsx | 10 + frontend/src/services/ws.ts | 69 + frontend/src/stores/index.ts | 94 + frontend/src/styles/pc98.css | 4353 ++++++++++++++++++++ frontend/src/types.ts | 11 + frontend/tsconfig.json | 19 + frontend/tsconfig.node.json | 9 + frontend/tsconfig.node.tsbuildinfo | 1 + frontend/tsconfig.tsbuildinfo | 1 + frontend/vite-env.d.ts | 1 + frontend/vite.config.d.ts | 2 + frontend/vite.config.js | 6 + frontend/vite.config.ts | 7 + 49 files changed, 8421 insertions(+) create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/.gitkeep create mode 100644 frontend/public/__gaogao__56242cbde8f18ac64522e410bad04e68_waifu2x_art_noise2.png create mode 100644 frontend/public/a10-nerve-clips.svg create mode 100644 frontend/public/ascii/ascii-flagged-3.txt create mode 100644 frontend/public/ascii/ascii-flagged-7.txt create mode 100644 frontend/public/ascii/ascii1.txt create mode 100644 frontend/public/ascii/ascii2.txt create mode 100644 frontend/public/ascii/ascii3.txt create mode 100644 frontend/public/ascii/ascii4.txt create mode 100644 frontend/public/ascii/ascii5.txt create mode 100644 frontend/public/ascii/ascii6.txt create mode 100644 frontend/public/ascii/ascii7.txt create mode 100644 frontend/public/asuka-headset-logo.svg create mode 100644 frontend/public/background-animation.gif create mode 100644 frontend/public/kaomoji.txt create mode 100644 frontend/public/logo/crane-logo-transparent.png create mode 100644 frontend/public/logo/crane-logo.png create mode 100644 frontend/public/nazrin-bg.jpg create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/components/BottomBar.tsx create mode 100644 frontend/src/components/ChoiceMenu.tsx create mode 100644 frontend/src/components/CityscapeBackground.tsx create mode 100644 frontend/src/components/ConfigModal.tsx create mode 100644 frontend/src/components/DialogueBox.tsx create mode 100644 frontend/src/components/HeartLogo.tsx create mode 100644 frontend/src/components/LandingPage.tsx create mode 100644 frontend/src/components/LoadingScreen.tsx create mode 100644 frontend/src/components/OrigamiDragonLogo.tsx create mode 100644 frontend/src/components/TopBar.tsx create mode 100644 frontend/src/components/VNApp.tsx create mode 100644 frontend/src/components/VNInterface.tsx create mode 100644 frontend/src/components/VNViewport.tsx create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/services/ws.ts create mode 100644 frontend/src/stores/index.ts create mode 100644 frontend/src/styles/pc98.css create mode 100644 frontend/src/types.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/tsconfig.node.tsbuildinfo create mode 100644 frontend/tsconfig.tsbuildinfo create mode 100644 frontend/vite-env.d.ts create mode 100644 frontend/vite.config.d.ts create mode 100644 frontend/vite.config.js create mode 100644 frontend/vite.config.ts (limited to 'frontend') diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..1b07050 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,25 @@ +# PC-98 Visual Novel Skeleton (React + TypeScript + Vite) + +A responsive PC-98-style VN UI with CRT effects, dialogue box with nameplate, choice menu, and a WebSocket client stub for a Rust backend. + +Quick start +1. cd pc98-vn +2. npm install (or yarn / bun install) +3. npm run dev (or yarn dev / bun run dev) + +Optional: Add a background image at public/bg.jpg. If absent, the app still runs using the base gradients. + +WebSocket wiring +- Client URL is in src/services/ws.ts (default: ws://localhost:8080/ws). +- Client sends on choice: { type: 'user_choice', id, label }. +- Client expects messages like: + - { type: 'assistant', content: string } + - { type: 'choices', items: [{ id: string, label: string }, ...] } + - { type: 'bg', src: string } + - { type: 'name', value: string } + - { type: 'system', content: string } + +Notes +- Aspect ratio ~16:10 for PC-98 feel (similar to 640x400). CRT scanlines and glow included. +- Uses DotGothic16 as an optional pixel-like font from Google Fonts. +- Portrait overlays can be positioned inside VNViewport's ui-layer. diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..4bb8d23 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + PC-98 VN Skeleton + + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..d7c37c7 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1641 @@ +{ + "name": "pc98-vn", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pc98-vn", + "version": "0.0.1", + "dependencies": { + "@nanostores/react": "^1.0.0", + "@types/three": "^0.180.0", + "nanostores": "^1.0.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "three": "^0.180.0" + }, + "devDependencies": { + "@types/node": "^24.3.1", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.2", + "typescript": "^5.5.4", + "vite": "^5.4.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "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==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nanostores/react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@nanostores/react/-/react-1.0.0.tgz", + "integrity": "sha512-eDduyNy+lbQJMg6XxZ/YssQqF6b4OXMFEZMYKPJCCmBevp1lg0g+4ZRi94qGHirMtsNfAWKNwsjOhC+q1gvC+A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "engines": { + "node": "^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^1.0.0", + "react": ">=18.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "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==" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "24.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", + "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==", + "dev": true, + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.24", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz", + "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "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==" + }, + "node_modules/@types/three": { + "version": "0.180.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.180.0.tgz", + "integrity": "sha512-ykFtgCqNnY0IPvDro7h+9ZeLY+qjgUWv+qEvUt84grhenO60Hqd4hScHE7VTB9nOQ/3QM8lkbNE+4vKjEpUxKg==", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": "*", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.22.0" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.23.tgz", + "integrity": "sha512-GPe4AsfOSpqWd3xA/0gwoKod13ChcfV67trvxaW2krUbgb9gxQjnCx8zGshzMl8LSHZlNH5gQ8LNScsDuc7nGQ==" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.64", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.64.tgz", + "integrity": "sha512-84kRIAGV46LJTlJZWxShiOrNL30A+9KokD7RB3dRCIqODFjodS5tCD5yyiZ8kIReGVZSDfA3XkkwyyOIF6K62A==" + }, + "node_modules/browserslist": { + "version": "4.25.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz", + "integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001737", + "electron-to-chromium": "^1.5.211", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001737", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", + "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.211", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.211.tgz", + "integrity": "sha512-IGBvimJkotaLzFnwIVgW9/UD/AOJ2tByUmeOrtqBfACSbAw5b1G0XpvdaieKyc7ULmbwXVx+4e4Be8pOPBrYkw==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + }, + "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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/meshoptimizer": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz", + "integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanostores": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.0.1.tgz", + "integrity": "sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "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 + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.49.0", + "@rollup/rollup-android-arm64": "4.49.0", + "@rollup/rollup-darwin-arm64": "4.49.0", + "@rollup/rollup-darwin-x64": "4.49.0", + "@rollup/rollup-freebsd-arm64": "4.49.0", + "@rollup/rollup-freebsd-x64": "4.49.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", + "@rollup/rollup-linux-arm64-gnu": "4.49.0", + "@rollup/rollup-linux-arm64-musl": "4.49.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-musl": "4.49.0", + "@rollup/rollup-linux-s390x-gnu": "4.49.0", + "@rollup/rollup-linux-x64-gnu": "4.49.0", + "@rollup/rollup-linux-x64-musl": "4.49.0", + "@rollup/rollup-win32-arm64-msvc": "4.49.0", + "@rollup/rollup-win32-ia32-msvc": "4.49.0", + "@rollup/rollup-win32-x64-msvc": "4.49.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/three": { + "version": "0.180.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.180.0.tgz", + "integrity": "sha512-o+qycAMZrh+TsE01GqWUxUIKR1AL0S8pq7zDkYOQw8GqfX8b8VoCKYUoHbhiX5j+7hr8XsuHDVU6+gkQJQKg9w==" + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..53e4c2c --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "pc98-vn", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@nanostores/react": "^1.0.0", + "@types/three": "^0.180.0", + "nanostores": "^1.0.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "three": "^0.180.0" + }, + "devDependencies": { + "@types/node": "^24.3.1", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.2", + "typescript": "^5.5.4", + "vite": "^5.4.0" + } +} diff --git a/frontend/public/.gitkeep b/frontend/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/public/__gaogao__56242cbde8f18ac64522e410bad04e68_waifu2x_art_noise2.png b/frontend/public/__gaogao__56242cbde8f18ac64522e410bad04e68_waifu2x_art_noise2.png new file mode 100644 index 0000000..cce12f1 Binary files /dev/null and b/frontend/public/__gaogao__56242cbde8f18ac64522e410bad04e68_waifu2x_art_noise2.png differ diff --git a/frontend/public/a10-nerve-clips.svg b/frontend/public/a10-nerve-clips.svg new file mode 100644 index 0000000..d651857 --- /dev/null +++ b/frontend/public/a10-nerve-clips.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/ascii/ascii-flagged-3.txt b/frontend/public/ascii/ascii-flagged-3.txt new file mode 100644 index 0000000..854f25d --- /dev/null +++ b/frontend/public/ascii/ascii-flagged-3.txt @@ -0,0 +1,65 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠊⠁⠈⢘⣒⣤⠠⠰⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠐⠀⢀⠀⠀⢐⡩⡽⣿⣟⡄⠉⣻⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢌⣴⡴⠊⣁⢔⣈⣰⡜⢉⣿⣿⡻⣤⣹⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣲⡿⢋⣤⣞⣵⣿⡟⣡⣔⣯⣿⣧⣯⣷⣿⣿⣿⣿⣿⣦⠐⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣮⣞⣵⢿⣿⣿⣿⣷⣿⣿⣿⡿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠐⢯⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⢡⠉⠜⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡸⣾⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠂⠀⢈⣿⣷⣿⠀⣿⢻⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⣿⣟⣻⣿⣿⣿⣿⣿⣿⣏⣀⠀⠀⠀⠸⣿⢿⡶⢀⡟⠈⣼⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⢃⣿⣭⣿⣿⣿⣿⣿⣿⡏⢍⣙⠛⠷⠦⡄⢻⣼⠋⣼⣁⣀⣹⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡎⢸⣿⣿⣿⣿⣿⣽⣿⠏⠻⠿⢿⣟⢦⢀⠀⠀⠉⠀⣉⣉⠉⢻⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⣸⣿⣿⣿⣿⣿⣿⡟⡈⠀⠀⠀⠀⢀⢮⠀⠀⠀⠚⡛⠃⠿⣾⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⢳⣿⣿⣿⣿⣷⣿⣿⠱⠀⠁⠀⠀⠀⣨⠆⠃⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⢿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠃⢞⣿⣿⣿⣿⣿⣿⣟⠠⠁⠀⠀⠀⠘⠣⣼⠀⢀⠀⠀⠀⠀⠀⣼⣿⣿⣿⡻⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⣼⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠈⢷⣤⡤⢤⣠⣀⣀⠨⠀⠀⣰⣿⣿⣿⠏⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠀⢼⡟⣽⣿⣿⣿⣿⣿⣿⣿⣆⢀⠀⠈⠱⠡⠔⠨⠎⠀⠀⣀⣼⣿⣿⣿⣻⠜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠀⢠⣿⢱⣿⣿⣿⣿⣿⣿⣿⣿⡜⠛⣧⡀⠀⠀⠀⠀⢀⢠⣾⣿⣿⣿⣿⣿⣯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢼⣥⡿⣼⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠉⠒⠀⠐⠀⠀⢸⢿⣿⣿⣿⣿⣿⡤⠀⠀⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠸⣼⠇⢸⣿⣿⣿⣿⣿⣿⢾⣯⡀⠀⠀⠀⠀⠀⠀⠀⠈⢸⣿⣟⣻⢟⣫⠥⠧⠤⠤⡀⠁⠐⠄⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡒⡀⠈⠻⡗⢹⣿⣿⣿⣿⣿⣿⣿⡇⣷⠀⠀⠀⠀⠀⣀⣀⣄⠾⠿⣷⠚⠉⠀⠘⡄⠣⠈⠔⠂⠀⠄⠀⠱⡄⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠈⠀⠀⢠⣤⡁⠉⢿⠿⣿⣿⣿⣿⣿⡟⠉⠂⠀⡐⠢⠁⠀⠀⠀⠀⠀⢿⡆⠀⠀⠐⠀⠁⠀⠀⠀⠀⠀⠀⠀⠘⠆⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠔⢁⡠⠤⠄⠂⠉⢫⡏⢷⣶⣷⣿⣿⣿⣿⣿⣟⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢫⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢘⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⢊⡖⠉⠀⠀⠀⠀⠀⢈⢲⠾⢿⣿⣿⣿⣿⣿⢶⡚⢁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠈⣿⠠⠁⠀⠀⠀⠀⡀⠆⣤⠚⡉⢿⡿⢿⠿⠟⡉⠔⠠⠈⠀⠀⠀⠀⠀⢀⠠⠈⠄⡀⢀⠀⢿⣇⠀⠀⠀⠀⠀⠄⠀⠀⠀⠀⠀⠀⠘⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⣏⠆⡀⠀⠀⢠⢰⡡⢂⠁⠠⢉⣿⠇⠆⡘⢠⠀⢂⠀⠀⠀⠀⠀⢀⡐⢢⠁⢎⡐⠄⠂⠀⠸⣿⣆⠀⠀⠀⠀⠈⠆⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢰⣏⠒⠠⢄⠣⣞⡟⡔⣂⠒⣀⢾⣿⢈⠱⣀⠃⠜⡀⠢⢀⠀⠀⢀⠲⣘⠢⡉⠤⠐⡈⠄⠁⠀⢸⣿⣄⠀⠀⠀⠀⠰⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⣬⢧⠉⡒⣌⠳⣽⣟⣺⡴⠖⢫⠿⠇⡈⠐⠠⢈⠂⡐⠁⠂⠐⣀⢸⡳⢌⡁⠆⠁⢂⠀⠀⠀⠀⠈⣿⡿⣷⣄⠀⠀⠣⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢸⣃⠆⡱⢎⡽⣺⣿⠋⢀⠠⢻⣠⠀⠈⠡⠄⠀⠄⠀⠀⠈⠀⢈⣿⢆⢃⡐⠈⠀⠀⠀⠀⠀⠀⠀⣿⣏⣿⡝⣷⡀⠀⠁⠀⠀⠀⠀⠀⠇⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢰⡇⢎⠵⣋⢶⡹⠁⠠⡀⢠⣻⣖⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠎⡠⢀⠀⠀⠀⠀⠀⠀⠀⢠⣿⢺⡵⣛⠼⣋⢦⠀⠐⡀⠀⠀⠀⠄⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠸⡝⡬⢳⡍⡞⠁⣤⣇⣶⣷⣿⣿⡖⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠆⠡⠀⠀⠀⠀⠀⠀⠀⠀⢸⡧⣗⢮⡱⢫⡜⢦⢣⠀⠄⠀⠀⢀⠃⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⣟⡼⡱⢎⢇⣾⣿⣿⣿⣟⣧⣿⣷⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⢎⠡⠀⠀⠀⠀⠀⠀⠀⢀⣿⢫⡜⢎⡱⢣⠜⣢⢃⢆⠀⠀⠀⢰⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⢹⢶⡙⣾⣮⣿⣿⣿⣻⡾⣽⣛⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡇⢂⠀⠀⠀⠀⠀⠀⢀⣾⢣⠣⡜⣌⠲⢡⠚⡄⢎⢸⠀⠀⠀⡜⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠨⡷⡹⣌⣿⣿⣿⣿⣷⣻⢧⡛⡼⢿⣧⡀⠀⠀⠀⠀⠀⠀⠀⢸⣇⠂⠀⠀⠀⠀⠀⢀⣼⢏⡲⢱⠘⡄⠣⢸⠇⡈⠔⡸⠀⠀⢀⠃⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⢷⣙⢦⣹⣿⣿⣿⣾⡽⢮⡕⡣⢍⣿⣧⡀⠀⠀⠀⠀⠀⠀⡌⠹⣧⡀⠀⠀⠀⢠⡾⣋⠎⡴⢁⠊⠄⡁⡾⠀⠐⢠⠁⠀⠀⣨⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣭⠖⡌⢿⣿⣿⣿⡽⣧⡛⡴⢃⠆⡋⡿⣦⡀⠀⠀⠀⡐⠀⠀⠀⠓⣄⣠⣼⣿⡳⣍⠚⠤⡁⠌⣀⠞⠀⢀⠜⠁⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⢌⠳⣈⠻⣟⣿⣿⣳⡽⣱⠍⡎⡔⡰⢨⢽⣷⣤⡼⠶⠖⠻⠛⠛⠋⠋⠺⣙⠻⠮⣏⣶⣼⣾⠭⠴⠒⠁⠀⠀⠀⠀⠘⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣎⡓⠄⡂⠌⢻⣽⣟⣿⣷⡿⣴⣣⣷⠿⢛⠩⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⢸⡠⠀⠀⠀⡘⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢷⡉⢆⠡⢊⠦⣽⣾⣻⣟⡿⡻⡕⡒⢌⡁⠂⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡘⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡵⢂⠑⢪⣽⣳⣿⣵⢾⣱⠓⣬⡱⢆⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠁⠀⠀⢀⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⠦⢌⢣⡟⣿⣿⣿⣯⣇⠋⢶⡹⢀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠀⠀⢀⠎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣆⠥⡚⡽⢯⡿⣷⣏⠞⡈⢗⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠃⢀⠎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢧⡱⣩⠟⣽⣻⣞⣯⠀⠍⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠪⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠐⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡛⣜⣳⣟⡦⣃⠐⡀⠆⡌⠀⢤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠁⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⣬⢋⡟⢶⢁⢂⠘⡴⣈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠰⣀⠀⠀⠀⠀⠀⠀⠀⠀ +⠌⡀⠀⡁⠀⢀⠀⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⢼⡻⣌⠢⠀⠎⡵⢂⠡⠀⠀⠀⠀⠀⠈⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢢⠀⠀⠀⠀⠀⠀⠀ +⠂⠄⠀⡀⠄⠀⢀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⡈⣷⣫⢕⠢⠄⡁⠎⡵⡉⠄⠀⠀⠀⠀⠀⢳⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠀ +⠌⠠⠐⠀⠀⠌⠀⠀⠀⠀⠐⠀⠀⠁⠀⠀⠀⠠⠁⣾⠵⡊⢅⠂⠄⠓⡬⢑⠠⠀⠀⠀⠀⠀⠈⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⡀⠀⠀⠀⠀⠀ +⢂⠁⠠⢈⠀⠐⠈⢀⠠⠀⠀⠀⡀⠀⠀⠀⠀⠃⣼⣏⠳⠉⡄⠌⡀⠣⡘⠄⢂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡿⠳⡀⠀⠀⠀⠀ +⠂⠌⢀⠂⡀⢂⠠⠀⠀⡀⠐⠀⠀⠀⠀⠀⠀⢼⡷⢎⠆⠁⠀⢂⠁⢂⠱⠈⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠿⠁⠀⢣⠀⠀⠀⠀ +⠡⢈⠀⢂⠐⢀⠀⠁⠄⠀⠀⠄⠀⠈⠀⠀⢀⠹⣿⡆⠌⠀⠀⠈⢂⠄⠂⠡⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⢤⣎⠗⠋⠀⠀⠀⠀⠇⠀⠀⠀ +⣁⠂⡐⢀⠂⠄⢈⠀⠄⠈⡀⠄⠐⠀⠀⠀⡌⣠⠻⢿⣷⣦⣀⠀⠂⠠⠀⡁⠂⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠤⡺⠭⠚⠁⠀⠀⠀⠀⠀⠀⠀⠡⡀⠀⠀ +⠄⡂⠐⡀⢂⠈⠠⢀⠂⠐⡀⠐⠈⡀⠀⠀⣳⣯⣛⠤⡈⠙⠛⠻⠷⣶⣶⣤⣁⣂⡀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣠⣴⠲⣋⡔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢡⠀⠀ +⢂⠅⢂⠐⠠⢈⠐⡀⠈⠄⡐⠈⠠⠀⠀⢀⣼⣳⢭⠲⣁⠂⠄⠀⠀⠀⠀⠙⢿⣿⣻⢿⡳⣖⠶⣶⣛⣿⣙⠳⣩⢡⢢⣱⠎⢀⡠⠆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⠀ +⡌⢂⠄⠌⡐⡀⠂⠄⠡⠀⠄⠡⠀⠁⠀⢀⢹⣧⢫⠱⣀⠊⢀⠀⠀⠀⠀⠤⡈⠹⢯⣿⣱⢏⡾⣰⡙⢦⢣⠓⡔⢢⣳⢃⡾⢋⠔⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡄⠀ +⠆⡡⢈⠐⠠⢀⠡⠈⠄⠡⠈⠄⡁⠈⠀⠸⢸⣎⠧⢣⠐⡈⠀⠀⠀⠀⠀⠀⠈⠐⢌⡹⣟⣮⡳⣥⢫⢇⡏⠞⣌⣿⢷⠫⠜⡠⠈⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀ +⠜⣀⠂⠌⡐⢀⠂⠡⠈⠄⡁⢂⠄⠡⠀⡇⡿⡼⣙⠦⡑⠄⡁⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⣟⣿⣖⢯⣾⣭⣿⣾⢛⡌⢣⠘⢀⠁⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀ +⡱⢀⠌⡐⢀⠂⠌⡠⢁⠂⠄⡁⢂⠐⡀⡇⣿⡱⣍⠲⣁⠂⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡿⠿⢿⡟⡬⢃⠜⠠⠈⠄⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀ +⡔⠡⠂⢌⠠⢈⠐⡀⠂⠌⡐⠠⢁⠂⠄⢻⣯⢳⡍⡖⢄⠃⡈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣧⢠⡿⣘⠱⡈⠌⠄⠡⢈⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠇⠀ +⢌⡑⠈⡄⠂⢄⠂⠄⡁⠆⠠⡁⠢⢈⠐⢸⣯⢳⡝⡜⡠⠒⡀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣸⢧⢡⠃⡜⠠⠈⠔⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⠀⠀ +⠆⡌⢡⠠⠉⡄⠨⠄⡁⢂⠡⠀⡅⠂⠌⣈⣿⢳⡞⡥⡑⢂⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⢎⠢⡑⡀⠂⡍⠰⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡎⠀⠀ +⢣⠘⡀⢆⠡⢂⢁⠂⠔⠠⡁⢡⠀⢃⠐⡀⣿⡳⡞⣥⠃⡌⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⢎⠱⡐⣀⠣⢌⢁⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠁⠀⠀ +⢣⡑⢌⠂⡅⢢⠈⠔⡈⠔⡠⠁⢌⠐⡐⢀⢻⣵⠻⡴⡩⢄⠡⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣌⠣⡐⢄⡊⠔⢂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⠀⠀ +⢣⡘⢄⠣⢌⠰⣈⢂⠱⢈⠔⠡⣈⠐⡈⢄⠈⣯⣟⣱⡑⣂⠂⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣎⠱⣈⠦⡑⠊⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡘⠀⠀⠀⠀ +⢣⡘⢄⢊⡄⢣⠐⡌⢄⠣⢈⠥⢀⠒⠠⡈⠄⢻⣜⢧⠳⡄⢃⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣇⠳⣄⢣⠱⡉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠂⠀⠀⠀⠀ +⠣⡜⡐⢢⠘⡄⢣⠘⡄⢊⠔⡈⠆⡘⢠⠐⡡⠈⣿⣎⠷⡘⠄⢂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣏⠶⣌⢣⠑⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡂⠀⠀⠀⠀⠀ +⣓⢤⠃⡥⠚⣄⠃⢎⠰⡁⢎⠰⢡⠘⣀⠒⠤⠑⣸⢮⢏⡵⢈⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣏⡞⡌⢆⠡⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀⠀⠀⠄ diff --git a/frontend/public/ascii/ascii-flagged-7.txt b/frontend/public/ascii/ascii-flagged-7.txt new file mode 100644 index 0000000..256ff6e --- /dev/null +++ b/frontend/public/ascii/ascii-flagged-7.txt @@ -0,0 +1,15 @@ +⣤⠀⣰⣿⠋⠀⡇⠀⠀⠀⠀⢸⠁⠀⠀⠈⠛⣗⠢⠤⠊⠉⠣⡀⡈⡇⠀⠀⠉⠣ +⢰⣾⣿⠟⠀⠀⠇⠀⠀⠀⠀⡘⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠈⢅⠘⢄⠀⢀⡀ +⠀⠉⡍⡇⠀⠀⣴⠀⠀⠀⠁⡇⠀⠀⠀⠀⠀⢸⡀⢄⣒⡠⠤⠤⠀⣣⡀⡩⢝⢕ +⠀⠀⣿⠁⠀⠀⢿⡀⠀⠀⠀⡇⠀⢀⡠⢄⣒⠌⠟⠉⠀⠉⠉⠉⢹⣿⣵⣾⢿⣥ +⠀⠀⢸⡶⢄⣸⡽⠁⠀⠀⠀⡧⣲⢵⣮⠗⠁⠀⠀⠀⠀⠀⠀⠀⢸⢁⣢⣜⡼⠟ +⠀⠀⠈⡿⠵⠶⢭⣄⣒⣒⣚⣿⣿⣿⠁⡞⠁⠀⢀⠎⡅⠀⠀⢠⠋⠉⠀⠀⠀⠀ +⠀⠀⠀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⡋⡧⠃⠈⠀⠊⠀⠧⣀⠀⡌⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠸⠀⠀⡤⡀⠀⢠⢤⠀⠀⢇⠀⠙⠁⠄⠘⠓⠀⠀⠹⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠇⠄⠃⣇⠀⢸⠉⠤⢜⢸⠀⠀⠀⠀⠀⠀⠀⢀⠃⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢠⠀⢠⠆⠀⠠⠶⠀⢸⢸⠀⠀⠀⠀⠀⠀⠀⠌⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠈⡄⠀⠀⠀⠀⠀⠀⠌⡨⠀⠀⠀⠀⠀⠀⠸⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⡇⡆⠀⠀⠀⠀⠀⢀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠈⡄⠀⠀⠀⠀⠀⡇⠇⠀⠀⠀⠀⠀⡘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⢀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⡆⣧⠀⠀⠀⠀⠈⡇⠀⠀⠀ diff --git a/frontend/public/ascii/ascii1.txt b/frontend/public/ascii/ascii1.txt new file mode 100644 index 0000000..279dc9e --- /dev/null +++ b/frontend/public/ascii/ascii1.txt @@ -0,0 +1,40 @@ + define) (e g 0)( + e h 0) ( e w + set!) ( e l lambda + )( e @ display ); i + (e t magnitude )(e(! . + x ) (@"" )) (e % + modulo )(e m make-rectangular ) (e + j 00 ) (e b begin + ) (e c 0)( + e ? map ) ( e a; () + apply ) (e ( r x ) ( do (( + z x (+(* z z ) x ) ) ( i 0 (+ i + 1 ) ) )( ( or ( >( t z ) 2) (> + i 26 )) i ) ) )( !) (e n (b let*) + ) (e : ( * 5 7 997 97 ) )( e d( + current-time) ) ( e k* ( * 89 991 73 3 )) + ( ! :) (e ($ ) ( list ( m g j )( m g c )( + m h j) ( m h c )( m (/(+ g h) 2 )( / + ( + j c )2))) ) (e (_ x y)(*( - x y) + ( - x y)))(e ~ (* 120))( e ( & x + ) (integer->char ( if(> x 25) ( + * 8 4)(+ 65 x))) ) (e (u + )( n((f(?(l (p)( r p) ; + ) ($)))(y (/(a + f )5 + )) )(>(/(a +(?( l (x ) + (_ x y))f) )5 )~ ) ) + )( e ( v k ) (k c + j 24 ( l( y) ( b( k + g h 79( l( x)(@ (& ( r( m x + y) ) ))) )( !) ( @ "\n") + )))) (e (k u v s f) ( do ( (; + i 00 (+ i 1 ))) ( ( >= i i s) )( + f(+ u (/ (*( - v u)i)(+ s -1 )) ) )) )( e( + q i)( +( *(b( w d (% ( +( * k* d) 1 ) + : ))(/ d : ))( - 1 i) ) i)) (e(o) (n ( + ( s(q .1) ) )( w j( q(- 00 1 ) ) )( w c + (+ j s ))(w g(q( - 01))) ( w h( + g + ( * s 1.5) )) ) ) ( o)( do ( + )(( u )(v k) ) ( + o) ) diff --git a/frontend/public/ascii/ascii2.txt b/frontend/public/ascii/ascii2.txt new file mode 100644 index 0000000..4d81e61 --- /dev/null +++ b/frontend/public/ascii/ascii2.txt @@ -0,0 +1,26 @@ +⠀⢀⣒⠒⠆⠤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⢠⡛⠛⠻⣷⣶⣦⣬⣕⡒⠤⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⡿⢿⣿⣿⣿⣿⣿⡿⠿⠿⣿⣳⠖⢋⣩⣭⣿⣶⡤⠶⠶⢶⣒⣲⢶⣉⣐⣒⣒⣒⢤⡀⠀⠀⠀⠀⠀⠀⠀ +⣿⠀⠉⣩⣭⣽⣶⣾⣿⢿⡏⢁⣴⠿⠛⠉⠁⠀⠀⠀⠀⠀⠀⠉⠙⠲⢭⣯⣟⡿⣷⣘⠢⡀⠀⠀⠀⠀⠀ +⠹⣷⣿⣿⣿⣿⣿⢟⣵⠋⢠⡾⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣾⣦⣾⣢⠀⠀⠀⠀ +⠀⠹⣿⣿⣿⡿⣳⣿⠃⠀⣼⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣿⣿⣿⠟⠀⠀⠀⠀ +⠀⠀⠹⣿⣿⣵⣿⠃⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣷⡄⠀⠀⠀⠀⠀ +⠀⠀⠀⠈⠛⣯⡇⠛⣽⣦⣿⠀⠀⠀⠀⢀⠔⠙⣄⠀⠀⠀⠀⠀⠀⣠⠳⡀⠀⠀⠀⠀⢿⡵⡀⠀⠀⠀⠀ +⠀⠀⠀⠀⣸⣿⣿⣿⠿⢿⠟⠀⠀⠀⢀⡏⠀⠀⠘⡄⠀⠀⠀⠀⢠⠃⠀⠹⡄⠀⠀⠀⠸⣿⣷⡀⠀⠀⠀ +⠀⠀⠀⢰⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⢸⠒⠤⢤⣀⣘⣆⠀⠀⠀⡏⢀⣀⡠⢷⠀⠀⠀⠀⣿⡿⠃⠀⠀⠀ +⠀⠀⠀⠸⣿⣿⠟⢹⣥⠀⠀⠀⠀⠀⣸⣀⣀⣤⣀⣀⠈⠳⢤⡀⡇⣀⣠⣄⣸⡆⠀⠀⠀⡏⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠁⠁⠀⢸⢟⡄⠀⠀⠀⠀⣿⣾⣿⣿⣿⣿⠁⠀⠈⠙⠙⣯⣿⣿⣿⡇⠀⠀⢠⠃⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠇⢨⢞⢆⠀⠀⠀⡿⣿⣿⣿⣿⡏⠀⠀⠀⠀⠀⣿⣿⣿⡿⡇⠀⣠⢟⡄⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⡼⠀⢈⡏⢎⠳⣄⠀⡇⠙⠛⠟⠛⠀⠀⠀⠀⠀⠀⠘⠻⠛⢱⢃⡜⡝⠈⠚⡄⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠘⣅⠁⢸⣋⠈⢣⡈⢷⠇⠀⠀⠀⠀⠀⣄⠀⠀⢀⡄⠀⠀⣠⣼⢯⣴⠇⣀⡀⢸⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠈⠳⡌⠛⣶⣆⣷⣿⣦⣄⣀⠀⠀⠀⠈⠉⠉⢉⣀⣤⡞⢛⣄⡀⢀⡨⢗⡦⠎⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠪⣿⠁⠀⠐⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣏⠉⠁⢸⠀⠀⠀⠄⠙⡆⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠚⡉⢳⡄⠡⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣏⠁⣠⣧⣤⣄⣀⡀⡰⠁⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢀⠔⠉⠀⠀⠀⠀⢀⣧⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣅⡀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢸⠆⠀⠀⠀⣀⣼⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠟⠋⠁⣠⠖⠒⠒⠛⢿⣆⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠑⠤⠴⠞⢋⣵⣿⢿⣿⣿⣿⣿⣿⣿⠗⣀⠀⠀⠀⠀⠀⢰⠇⠀⠀⠀⠀⢀⡼⣶⣤⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠟⢛⣿⠀⠙⠲⠽⠛⠛⠵⠞⠉⠙⠳⢦⣀⣀⡞⠀⠀⠀⠀⡠⠋⠐⠣⠮⡁⠀ +⠀⠀⠀⠀⠀⠀⠀⢠⣎⡀⢀⣾⠇⢀⣠⡶⢶⠞⠋⠉⠉⠒⢄⡀⠉⠈⠉⠀⠀⠀⣠⣾⠀⠀⠀⠀⠀⢸⡀ +⠀⠀⠀⠀⠀⠀⠀⠘⣦⡀⠘⢁⡴⢟⣯⣞⢉⠀⠀⠀⠀⠀⠀⢹⠶⠤⠤⡤⢖⣿⡋⢇⠀⠀⠀⠀⠀⢸⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠵⠗⠺⠟⠖⢈⡣⡄⠀⠀⠀⠀⢀⣼⡤⣬⣽⠾⠋⠉⠑⠺⠧⣀⣤⣤⡠⠟⠃ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠷⠶⠦⠶⠞⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ \ No newline at end of file diff --git a/frontend/public/ascii/ascii3.txt b/frontend/public/ascii/ascii3.txt new file mode 100644 index 0000000..a84762f --- /dev/null +++ b/frontend/public/ascii/ascii3.txt @@ -0,0 +1,25 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⢟⣵⣶⣿⣿⠉⢍⢠⡈⠻⠶⣬⡙⢷⣮⠙⠿⣿⣷⣶⣄⠀⠀⢀⣠⢖⡡⠖⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠉⠒⠤⠄⣈⡑⠢⠤⢀⡴⠛⣵⣿⣿⠟⠉⢹⡶⠈⣌⢻⡄⠀⠘⣿⣷⣹⣷⣄⠹⣿⠛⢻⣷⣶⡯⠶⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⣹⠟⢲⡞⠁⣼⣿⣿⠏⠀⢠⢸⣇⡀⣿⡎⢿⣆⠸⡌⠻⣿⣿⣿⣧⠈⢷⡀⡿⣝⣧⠀⠈⢷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢠⡟⢀⡞⠀⣼⣿⡿⢃⠀⣠⠏⣼⣿⡇⢿⣧⢘⣿⣿⣧⠀⠙⠿⣿⣿⣷⡈⢷⡀⠘⣞⢧⡀⠘⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⣸⣷⡾⢡⢤⣯⣿⢣⢎⣾⡟⢠⣿⡿⣿⢸⡏⢿⣿⣿⣿⣷⡀⠘⣿⣿⣿⣷⡈⢷⡀⠘⣯⣳⣦⢹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⣿⣿⣧⡟⣟⣾⣯⡟⣼⢻⢃⣿⡿⠁⠸⡌⣿⡀⠻⣟⣿⣿⣿⣄⠈⢿⣿⣿⣿⣌⢿⣄⠘⢷⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⣿⣿⣿⢹⣿⣿⢿⢱⡇⢸⣿⡿⠁⠀⣀⣷⣿⣧⠀⠙⢦⡙⠻⣿⣷⣦⣻⣿⣿⣿⣎⣿⣦⠀⢻⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢸⣿⢧⣿⣿⡿⠈⢸⢇⣿⣿⣡⠔⠊⠁⠀⢳⣿⡆⠀⠀⠙⢦⡈⠙⠻⢿⣿⣿⣿⣿⣿⣿⣧⡀⠹⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢸⣿⣼⣿⣿⣯⢇⣿⣸⣿⠁⠀⢀⠀⣀⠀⠀⠻⣿⡄⠀⠀⠀⢛⣽⢧⣤⣌⡿⣿⣿⣿⣿⣿⣿⣆⠘⢿⣄⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⣼⣿⣿⣿⣿⢿⣿⡇⣸⠃⢀⣾⣿⣿⣦⡀⠀⠀⠙⢿⡄⠀⠀⢼⣅⣼⣿⣿⡿⣯⣽⣿⢻⣿⣿⣿⣷⡀⠙⢦⡀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⣿⣿⣿⣿⣿⢸⣿⡇⣿⢙⣿⣏⣠⣿⣿⡇⠀⠀⠀⠈⠻⣆⠀⠸⡿⢿⣿⣿⠇⢹⠿⣿⡈⣿⣿⣿⣿⣿⣷⣾⣷⣄⠀⠀⠀⠀ +⠀⠀⠀⢸⣿⣿⣿⣿⣿⠸⣿⣷⣿⣿⡇⢿⠿⢿⡟⡋⠀⠀⠀⠀⠀⠈⠣⡀⠑⢤⣤⣶⠀⠀⠀⣿⡇⣿⣿⣿⣿⢹⡟⠿⣿⣿⣷⣤⡀⠀ +⠀⠀⢠⣿⣿⠏⣿⣿⣿⢀⣿⡏⣿⠉⠛⠊⠳⣤⣶⠇⠀⠀⠀⠀⠀⠀⠀⠈⠓⠀⠀⠀⠀⠀⠀⣿⣧⣿⣿⣿⣿⣷⣷⡀⠀⠈⠉⠉⠙⠓ +⢀⣴⣿⣿⠃⠀⣿⣿⣿⢸⣿⠐⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⢻⣿⣿⣿⡏⠿⣿⣿⣧⡀⠀⠀⠀⠀⠀ +⠋⣸⡿⠁⠀⠀⣿⣿⣿⣿⣿⡄⢸⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠇⢸⣿⣿⣿⣿⣦⣤⠟⠋⠉⠒⢦⡀⠀⠀ +⡴⠏⠀⠀⠀⠀⣿⡿⠋⢹⣿⣇⠘⣇⠙⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠋⠀⣿⣿⣿⣿⠛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⡴⠁⠀⠀⢸⣿⣿⡄⢹⡄⠀⠑⠢⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡤⠊⠀⠀⢰⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⡼⠀⠀⠀⠀⠈⣿⠻⣿⣟⣿⣄⠀⠀⠀⠉⢲⡤⢄⡀⠀⠀⠀⡠⠞⡟⠀⣠⠀⠀⣾⡿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠘⡄⠈⠻⣿⣿⠓⠤⠀⠀⠀⠙⠦⣈⠉⠒⠉⢀⡞⢀⠜⠁⠀⣸⠟⣠⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⣿⠀⠀⢂⠀⠀⠀⠘⠆⠀⠀⠉⠓⣄⠀⠀⠀⠀⠀⠀⠉⢲⣤⣞⡊⠁⠀⠀⠚⠁⢰⢻⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⢰⣿⣇⠀⠈⢆⢀⠀⠀⠀⠀⠀⠀⠀⠈⢢⡀⠀⠀⠀⠀⢠⠋⠘⡄⠈⠢⡀⠀⠀⢀⠇⢸⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⣾⣿⡟⣆⠀⠀⢠⣧⠀⠀⠀⠀⠀⠀⠀⠀⠑⠢⡀⠀⢠⠃⠰⠲⡜⡆⠀⠈⠢⣠⠎⠀⣿⡟⠀⠀⠀⠀⠀⠀⣠⠀⠀⠀⡄⠀⠀ +⠀⠀⠀⣿⣿⠁⣿⣦⠀⠀⠙⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠃⠀⠘⠂⠃⠸⠀⠀⠀⠀⠀⢸⣿⡇⠀⠀⠀⠀⠀⢠⠃⠀⠀⠈⣇⠀⠀ +⠀⠀⠀⣿⣿⠀⣿⣿⣷⡄⠀⠈⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⠀⡾⠀⠀⠀⢠⣿⠀⠀ +⠀⠀⠀⣿⣿⠀⢻⣿⣿⣿⣦⣄⠀⡳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡿⠀⠀⠀⠀⢀⡜⢁⠄⠀⠀⢸⣿⠀⠀ diff --git a/frontend/public/ascii/ascii4.txt b/frontend/public/ascii/ascii4.txt new file mode 100644 index 0000000..d90f854 --- /dev/null +++ b/frontend/public/ascii/ascii4.txt @@ -0,0 +1,33 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⢃⡴⠋⠀⠀⠀⣀⣠⡤⠔⠒⠋⠁⠀⠀⠀⠐⠒⠚⠛⠻⢭⣗⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⡞⠀⣀⡴⠒⠉⠁⠀⠀⠀⠀⠀⠀⣰⠖⠒⠦⣄⡀⠀⠀⠀⠀⠉⠙⠢⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⡴⠎⠁⠀⠀⠀⠀⠀⠀⠀⠀⣀⡴⠋⠀⠀⠀⠀⠉⠳⣄⠀⠀⠀⠀⠀⠀⠙⠲⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢀⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⠞⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢀⡞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⡾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡆⠀⠀⠀⠀⠳⣄⠀⠀⠀⠹⣎⠳⢤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⣰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢣⠀⣀⠀⠀⠀⠈⠳⣄⠀⠀⠈⠳⣄⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⢤⠀⠀⠀⠀⠀⠀⠀⠸⡇⠈⢢⡀⠀⢦⠀⠈⠳⣄⡀⠀⠈⠳⢄⡹⣆⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀ +⠀⠐⢲⠗⢤⣄⡀⠀⠀⠀⠀⣀⣠⠤⣤⣏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠈⡇⠀⠀⠀⠀⠀⠀⠀⣧⠀⠀⠳⡄⠈⢣⠀⠀⠀⠙⠢⣄⡀⠀⠙⠛⢷⡤⣄⣀⡀⠀⠀⣠⡷⠀⠀⠀ +⠀⢀⣼⠐⢻⣿⠟⢷⡀⠀⠚⠋⠉⠉⠳⡟⢷⡀⠀⠀⠀⠀⠀⠀⠀⢠⡇⠀⢸⡄⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⢹⡄⠈⢳⡀⠀⠀⠀⠀⢯⣓⠲⣤⣬⣷⡀⠈⠉⣩⡽⠋⠁⠀⠀⠀ +⣰⠊⡿⣠⢿⡿⣆⢀⢷⡀⠀⠀⠀⠀⣰⠇⠀⣧⠀⠀⠀⠀⠀⠀⠀⡾⠀⠀⠈⣇⠀⢀⠀⠀⠀⠀⢸⠀⠀⢀⣀⣻⡀⠀⢳⡄⠀⠀⠀⠀⢿⡆⣷⠀⠘⡏⠉⠉⠀⠀⠀⠀⠀⠀⠀ +⠁⢼⡿⠁⠀⠙⢿⢷⣹⡟⠲⠶⠖⠛⠁⠀⣰⠇⠀⠀⠀⠀⡶⠀⣸⠁⠀⠀⢀⣸⠀⠈⣇⠀⠀⠀⢸⠀⠀⠀⠀⠈⢧⠀⠀⠹⣄⠘⣄⠀⠈⣧⠸⡀⠀⢹⡄⠀⠀⠀⢀⡀⠀⠀⠀ +⠀⢸⡇⢷⠀⣴⢻⠂⡇⠙⠦⠤⣀⡤⠤⢾⡁⠀⠀⠀⠀⣼⠁⣰⣃⠤⠖⠛⠋⢸⡀⠀⢹⡀⠀⠀⢸⠀⠀⠀⠀⠀⠘⣆⠀⢸⣌⠢⡘⢆⠀⠸⣆⡇⠀⢸⡇⠀⣠⠔⠋⠀⠀⠀⠀ +⠀⢸⡇⠈⣷⠁⢸⡇⢻⡄⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⢰⠋⣰⠛⠁⠀⠀⠀⠀⠘⡇⠀⢸⡇⠀⠀⡾⠀⠀⠀⠀⠀⠀⢻⡀⢸⣽⡆⠘⣯⣧⠀⢻⡇⠀⢨⣧⠞⠁⠀⠀⠀⠀⠀⠀ +⠀⢸⢠⡞⠙⣆⢸⠃⠈⢷⠀⠀⠀⠀⠀⠀⣧⠀⠀⢠⣏⡼⠃⠀⠀⠀⠀⠀⠀⠀⣷⠀⡟⣧⠀⣸⠃⠀⠀⣀⣀⡀⠀⠀⣇⡸⣿⣿⠀⡏⠻⣇⠀⡇⢀⡼⠃⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⣼⢸⡀⠀⣨⣿⠂⠀⠈⢧⠀⠀⠀⠀⠀⢹⡀⣠⣿⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡼⠀⢿⣠⠃⢀⣾⣿⣷⡶⢷⡶⠀⣸⣧⡏⡏⡆⢸⠀⢸⡄⡵⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⡇⠈⠳⡼⠁⢸⠀⠀⠀⠈⢧⠀⠀⠀⠀⠀⢟⠃⠁⠀⠀⠀⢀⡠⣤⣖⣺⣤⠄⠋⠀⠀⡟⠁⠀⠈⠙⠉⠉⠁⠀⠀⠀⠟⣿⠀⣇⡇⡸⠀⢾⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⢠⠃⣴⠊⠳⣄⣸⠀⠀⠀⠀⠈⢳⡀⠀⢀⠀⠘⡇⠀⠀⣠⣶⣿⡿⠿⠛⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⠇⠀⣿⡴⠃⣴⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠄ +⡞⢀⡇⠀⠀⣨⢿⠀⠀⠀⠀⣠⡴⠋⠀⠈⠳⣄⠈⠢⣌⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡆⠀⣯⡤⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠄⠊⠀⠈ +⠀⢸⡇⣤⠊⠀⣾⠀⠀⠀⠘⢻⣿⠑⠲⣆⠀⠘⠳⣄⠈⠓⢦⣄⠠⠄⢦⣴⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⠤⠤⠼⠿⠗⠚⠉⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠖⠉⠀⠀⠀⠀⠀ +⠀⢸⡟⠀⠳⠄⡇⠀⠀⠀⠀⠈⢾⡆⢀⡸⣦⠀⠀⠈⠻⡗⠒⠚⠋⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⢠⡞⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡖⠋⠀⠀⠀⣰⠋⠀⠀⠀ +⢠⢾⠁⠀⠀⠀⡇⠀⠀⠀⠀⠀⢠⠙⠲⠭⣍⡷⠀⠀⠀⠹⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⢦⡀⣠⠞⠁⠀⠀⠀⠀ +⠁⠸⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⣸⠀⠀⠀⠀⢀⣀⡀⠀⠀⠹⣆⠀⣀⣀⠀⠀⠀⠀⠀⠀⠀⡴⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣄⠀⠀⠀⠀⠀⠀⠀⠀⠛⠁⠀⠀⠀⠀⠀⠀ +⠀⠆⠀⠀⠀⢸⠁⠀⠀⠀⠀⠀⣿⠀⠀⢠⠶⠭⠥⠭⣧⠀⠀⢻⣆⠀⠉⠛⠛⠲⣶⣒⠒⡾⠁⠀⠀⢸⣓⡶⠀⠀⢰⡿⠳⣶⣿⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⢀⠀⠀⠀⢀⡟⠀⠀⠀⠀⠀⠀⡏⠀⠀⡏⠀⠀⠀⠀⠈⢧⡀⠀⢿⣦⣄⡀⠀⠀⠙⣿⡠⡇⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⠶⠾⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⡸⠀⠀⠀⡼⠁⠀⠀⠀⠀⠀⢸⠁⠀⢠⠇⠀⠀⠀⠀⠀⠀⢳⡀⢸⣿⠳⢟⡦⣄⠀⢳⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⢀⣴⡾⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠁⠀⠀⣰⠃⠀⠀⠀⠀⠀⢀⣿⠀⢀⡾⠀⢄⣀⠀⠀⠀⠀⠀⢷⡀⡏⡇⠀⠻⣆⡙⡿⢼⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⣧⣤⡄⠀⠀⠀⣾⣯⣾⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⣰⠃⠀⠀⠀⠀⠀⠀⡾⢹⢀⡞⠀⠀⠀⠈⠉⠓⢤⣀⠀⠀⣿⣿⡇⠀⠀⠈⠛⢦⡜⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠛⠷⠀⠀⠈⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⢠⡏⠀⠀⠀⠀⠀⢀⡼⠁⣾⠟⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⢦⣿⣿⠇⠀⠀⠀⠀⢨⠃⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆⠀⠀ +⠠⠫⠁⠀⠀⠀⠀⢀⡞⠁⣸⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⢿⠿⢤⡀⠀⠀⠀⢸⠀⢳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⠁⠀⠀ +⢁⠆⠀⠀⠀⠀⣠⠎⠀⣰⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⢤⣠⠶⠋⠉⠀⠀⠙⠦⡄⣀⡼⠀⠘⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠁⠀⠀⢀⠂⠀⠀⠀ +⡞⠀⠀⠀⣀⠜⠁⢀⡴⢉⡇⠀⠀⠀⠀⠀⠀⠀⠘⢇⡘⣧⣄⣀⣀⣀⡤⠴⠚⠛⠁⣀⠘⢦⡙⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⣠⠂⠀⠀⠀⠀ diff --git a/frontend/public/ascii/ascii5.txt b/frontend/public/ascii/ascii5.txt new file mode 100644 index 0000000..44b442b --- /dev/null +++ b/frontend/public/ascii/ascii5.txt @@ -0,0 +1,30 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⡤⠞⠋⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⢸⣿⡿⠋⠉⢻⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣀⣀⣠⠴⠛⣁⡤⠔⠀⠈⠛⠉⠀⢰⣶⣶⣤⠄⠀⠀⠈⣿⣿⣦⣴⣾⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⢠⠞⣡⣴⣾⣿⣿⢿⣋⡥⠴⠚⠉⠀⠀⠀⠀⢀⣀⣀⣀⣙⣋⡁⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢀⣿⣼⣿⣿⡿⠟⠋⠁⠀⠀⠀⢀⣀⡤⠴⠞⠛⠉⠉⠁⠀⠀⢈⡉⠛⠒⠦⢤⣀⠈⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢸⣿⣿⡿⠋⠀⣀⣀⣠⡤⠖⠛⠉⢀⣤⠀⠠⠀⠀⠀⠀⠀⠀⠀⠙⢆⠀⠀⠀⠀⠙⠷⣄⠀⠉⠉⠉⠉⠉⠀⣸⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⣼⣿⣿⢳⣿⣿⣿⣿⠋⠀⠀⠀⣠⠋⠀⣠⠇⠀⠀⠀⠀⢰⠀⠀⢰⡀⢣⡀⠀⠀⠀⠘⢮⠳⣄⠀⠀⠀⠀⠀⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢹⣿⣿⡾⣿⣿⣿⠋⠀⠀⠀⡴⠁⠀⡰⠃⠀⠀⠀⠀⠀⢸⠀⠀⠀⢣⠀⢳⠀⠀⢀⣀⣀⡆⠘⢧⠀⠀⠀⢠⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢀⣿⣿⣿⣧⢿⣿⠃⠀⠀⠀⡼⠁⠀⢠⠃⠀⡆⠀⠀⠀⠀⠸⠀⡇⠀⠈⡆⠈⢇⠀⢹⣿⣿⣷⣶⣮⣷⡀⠀⢸⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⢀⣞⣿⣿⣿⣿⣾⠇⠀⠀⠀⢸⠁⠀⢀⡎⠀⢰⠁⠀⠀⠀⠀⡀⠀⡇⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣧⠈⢧⠀⣸⣿⣿⣿⣿⣿⣷⣤⠎⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⢠⣯⣿⣿⣿⣿⣿⢿⠀⠀⠀⠀⡇⠀⠀⢸⠁⠀⡿⠀⠀⠀⠀⠀⡇⢀⡇⠀⠀⢰⡀⠘⣿⣿⣿⣿⣿⣿⣿⠀⠀⢳⣿⣿⣿⣿⣿⣿⣿⡞⣦⠈⠁⠀⠀⠀⠀⠀ +⠀⣠⣿⣿⣿⣿⣿⣿⠃⡜⠀⡆⠀⠀⡇⠀⢀⡇⠀⢀⡇⠀⠀⠀⠀⢸⠀⢸⣇⠉⠉⠲⡧⣄⠙⢿⣿⣿⣿⠟⡇⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⠘⡇⡄⠀⠀⠀⠀⠀ +⢰⣿⣿⣿⣿⣿⣿⠃⠘⡇⢰⢧⠀⠀⡇⠀⠈⡇⠀⢸⠀⠀⠀⠀⢀⠇⠀⡿⢸⠀⠀⠀⣇⠈⠳⡄⠀⠀⠀⠀⢹⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣇⡿⡇⠀⠀⠀⠀⠀ +⢹⣿⣿⣿⣿⡿⠁⠀⠀⡇⢸⢸⠀⢠⡇⠀⢸⡇⠀⢸⡇⢰⠀⢰⠈⠀⢠⠃⠈⡆⠀⠀⣿⠀⠀⡇⠀⠀⠀⠀⢸⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠇⠀⠀⠀⠀⠀ +⠈⢻⣿⣿⡿⢁⠀⠀⠀⡇⠘⡇⠀⠘⡇⠀⢸⣇⠀⠘⣷⢸⠀⢸⠀⠀⢸⣸⣖⣛⣢⣀⠁⠇⠀⡕⡀⠀⠀⠀⠘⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⢹⡟⡠⠋⠀⠀⠀⣇⠃⡇⠀⠈⣷⠀⠸⣹⡆⠀⠙⣼⡆⠈⣇⠀⠸⠛⠛⢻⣿⣿⣿⣶⣤⣧⠃⠀⠀⠀⢸⠀⠀⠀⢹⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⢠⡟⡴⠁⠀⠀⡠⢸⣿⠀⡇⠀⠀⣿⡀⠠⠧⠽⠄⠀⠀⢿⠀⠹⠧⠀⠀⠀⣤⣴⡿⣿⣿⠘⡿⠀⠀⢀⠀⠸⠀⠀⠀⠀⢻⣿⡿⣯⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⣰ +⢀⡏⣼⠁⢀⠀⡼⠁⢸⢸⠀⣷⠀⡀⣯⢷⠟⠛⠛⠓⠂⠀⠀⠀⠀⠀⠀⠀⠀⢻⣟⣛⣿⠏⢰⡇⠀⠀⢸⠀⢠⡆⠀⠀⠀⠈⠻⡿⠋⡗⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ +⣼⣾⡏⠀⡾⡼⠁⠀⣿⢸⡇⢻⡄⢰⢹⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⠃⡄⠀⠈⠀⢸⡆⠀⠀⠀⡄⢀⡇⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ +⣷⢻⡇⠀⣷⠃⢀⡜⢹⡄⣷⣨⠟⠛⠉⠉⢢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⢸⠀⠃⠀⠀⠀⢸⢿⠀⠀⠀⢀⣸⡧⢰⠇⠀⠀⣀⡀⠀⠀⠀⠀⠀⠿ +⣿⠀⢧⠸⣿⢀⡞⠀⠀⣳⠟⠁⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡼⢀⣾⠁⠀⠀⡇⠀⣾⣿⣆⠀⠀⣿⣿⠁⣾⠐⣻⡿⠁⠀⠀⠀⠀⠀⠀⠀ +⠈⠃⠈⠧⣿⣾⠁⣠⣿⡇⠀⠀⠀⠀⠀⠀⠀⠸⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⠟⠋⠘⠀⠀⠀⡇⢀⠇⡼⠹⡄⢠⣿⢁⠀⣿⠈⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⣸⠟⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⡇⠉⢙⣶⣤⣀⣀⣠⣴⣶⠿⠋⢸⠀⠀⠀⠀⠀⢸⠁⣸⡰⠃⡴⠻⣼⠘⡟⠀⡏⢸⠁⠀⠀⠀⡀⠀⠀⠀⠀⠀ +⠀⠀⠀⢀⡾⠁⣰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣦⡿⠾⣿⣼⡏⠉⠻⡁⠀⠀⠘⢦⡀⢀⡆⢀⡎⢰⠏⢠⠞⠁⠀⠘⡿⢁⣸⣅⢸⠀⠀⠀⠀⠚⠓⢆⠀⠀⠀ +⠀⠀⠀⢀⣧⣰⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣽⠛⢦⣿⢿⣇⠀⠀⠀⠀⠀⠀⣠⠞⠋⢃⡞⢡⢧⡔⠁⡠⠀⢀⢼⡷⠋⠁⣝⣎⣄⠀⠀⠀⠀⠀⠀⠱⡄⠀ +⠀⠀⠀⠚⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣄⠀⠠⢻⠇⣰⠋⠹⣾⡛⠲⢦⣤⡤⠒⠋⠁⢀⣴⠚⠤⠚⠈⣳⠞⠀⢀⡴⠋⠀⠀⠀⠙⣽⢻⣆⠀⠀⠀⠀⠀⠀⠹⠀ +⠀⠀⠀⣰⠟⡜⠳⣦⣤⣤⣤⡤⠔⠒⠋⠉⠁⠀⠀⢾⣟⠁⠀⠀⠈⠙⢶⡤⣤⣤⣤⠖⠚⠉⠁⠀⢀⣄⡾⠋⢁⡴⢻⠀⠀⠀⠀⠀⠀⢸⡀⠱⠱⣄⠀⠀⠀⠀⠈⡄ +⠀⠀⠀⣏⠀⡟⢦⣽⠻⠃⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠟⠀⠀⠀⠀⢠⠞⠛⢺⡀⠀⠀⠀⠀⠀⢠⣿⠋⠀⡰⠋⢀⡞⠀⠀⠀⠀⠀⠀⢸⠀⠀⠘⠇⠱⡀⠀⠀⠀⠰ +⠀⠀⣠⢿⡀⣸⠀⠙⠳⢦⣤⣄⣀⣀⣀⣤⣴⠞⠋⠀⠀⠀⠀⠀⠀⠘⣦⣤⠞⡀⠀⠀⠀⠀⠀⣼⠟⢀⡞⠁⠀⠸⣇⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⢰⡯⠤⠗⠛⠀⠀⠀⠀⠀⠠⠀⠀⣇⣴⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣟⠜⠁⠀⠀⠀⠀⢠⢯⣠⠏⠀⠀⠀⠀⠘⣆⠀⠀⠀⠀⠀⣼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣽⡃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⠋⠀⠀⠀⠀⠀⢀⣿⡴⡏⠀⠀⠀⠀⠀⠀⢸⡄⠀⠀⠀⣠⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/frontend/public/ascii/ascii6.txt b/frontend/public/ascii/ascii6.txt new file mode 100644 index 0000000..5e2c68e --- /dev/null +++ b/frontend/public/ascii/ascii6.txt @@ -0,0 +1,32 @@ +⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣷⢟⣭⣴⣾⣿⣿⠉⠭⠩⣤⡉⠻⠷⣦⣌⡙⠻⣶⣮⠙⠿⣿⣿⣳⣜⢦⡀⠀⠀⠀⠀⣀⡴⠋⣠⠔⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠒⢤⣄⡀⠀⠑⠢⢤⣀⠀⠀⠀⣠⣾⢟⣵⣿⣿⣿⡿⠋⢿⣆⣠⢓⡘⢿⡆⠀⠈⢹⣿⣶⡝⢿⣧⡀⠘⢷⣝⢮⢷⡕⣄⣀⡴⢚⣩⣶⡋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠉⠑⠒⠢⣤⣌⣉⠒⣾⠋⢀⣾⣿⣿⡿⠃⠀⠀⠸⡏⠀⢸⣷⠘⣿⡄⠀⢲⡝⢿⣿⣦⣻⣿⣦⡈⢿⣦⠀⠹⣮⢳⣾⠋⠉⠉⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⢠⣿⠋⢉⡿⠁⢀⣾⣿⣿⡿⠃⠀⢀⣼⢠⣿⣠⢸⣿⡇⢸⣿⣆⣘⢧⠈⠻⣿⣿⣿⣿⣷⡀⠙⣧⠘⣏⢧⡙⣦⡀⠀⠸⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⣾⡇⠀⣼⠁⠀⣾⣿⣿⠿⢁⠀⢀⡼⠁⣼⣿⣿⡈⣿⣷⡀⣹⣿⣾⣷⡀⠀⣘⠿⢿⣿⣿⣷⡄⠙⣧⠈⠈⢻⡜⢷⡀⠀⢹⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢠⣏⡶⣼⠇⣠⣸⢏⣿⡿⢠⡏⡼⣿⠃⢰⣿⣿⢿⡇⢿⡟⢷⣷⣿⣿⣷⣿⣆⠀⠐⣿⣻⣿⣿⣿⣄⠸⣧⡀⠀⢻⣎⢷⣤⡀⢻⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢸⣿⣿⡏⣰⢣⡟⣼⣿⢃⡏⣼⣷⡏⢠⣿⣿⡇⠈⣧⠸⣿⡀⢻⣯⢿⣿⣿⣿⣧⠀⠈⢿⣿⣿⣿⣿⣆⠙⣷⡄⠀⠹⣧⡻⣷⡜⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢸⣿⣿⢸⡿⣼⣹⣿⡏⡾⢱⡟⢸⡇⣾⣿⡟⠀⠀⠹⡆⣿⣇⠀⠙⢷⡝⢿⣿⣿⣷⣤⡀⠻⣿⣿⣿⣿⣧⡙⣿⣄⠀⠹⣷⣻⣇⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢸⣿⣧⣿⢇⣿⣿⣿⢱⠃⣾⠁⣸⣿⣿⡟⠀⠀⢀⣠⢿⡸⣿⡄⠀⠈⠻⣦⠉⠻⢿⣿⣿⣧⣽⣿⣿⣿⣿⣷⡘⣿⣧⡀⠘⢿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠈⣿⣿⡏⣼⣿⣿⡿⢸⠀⣿⢀⣿⣿⡏⣠⡤⠚⠉⠀⠀⢳⣽⣷⡀⠀⠀⠈⠳⢄⠀⠉⠛⢿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣷⡄⠈⢻⣿⣅⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⣿⡟⣸⣿⣿⣿⢇⡇⣰⡇⣿⣿⡟⠉⠁⠀⠀⠀⠀⠀⠈⢻⣿⣧⠀⠀⠀⠀⠀⡵⢒⣦⣖⡊⠙⠻⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠹⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢀⣿⢳⣿⣿⣿⣿⢸⣃⣿⠇⣿⡟⠀⢀⣠⢴⣂⣐⠦⠄⠀⠀⠹⣿⣧⠀⠀⠀⠀⢰⣿⠛⢻⣿⣿⣷⣮⡿⢿⣯⣿⣿⣿⣿⣿⣷⣄⠈⠻⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⣸⢧⣿⣿⣿⣿⡿⣼⣿⣿⢀⣿⠀⣀⣼⣾⠿⣿⣿⣶⡄⠀⠀⠀⠈⢿⣧⠀⠀⠀⠚⣷⣤⣾⣿⣿⣷⠹⣿⣦⣿⣯⢹⣿⣿⣿⣿⣿⣧⣀⠈⢻⣄⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⢸⣿⢉⣿⢿⣅⣠⣿⣿⣿⡅⠀⠀⠀⠀⠀⠙⢷⡀⠀⠀⣿⠿⣿⣿⣿⣿⠀⣸⡿⢿⣿⡀⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣧⣀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⡇⡯⣿⣿⢸⣿⣿⡏⢸⣿⣿⣯⣿⠟⠷⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠘⢦⣐⣀⡢⢆⠀⠉⠀⢸⣿⡇⣿⣿⣿⣿⣿⡛⣿⢿⣿⣿⣿⣿⣷⣄⡀⠀⠀ +⠀⠀⠀⢀⣾⣿⣿⢿⣿⣿⣿⡇⠀⣿⢿⢿⡿⠿⠷⣄⠻⣄⣉⣉⣰⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠙⠛⠛⠁⠀⠀⠀⢞⣿⡆⣿⣿⣿⣿⣿⣧⡹⡆⠈⠉⠛⠛⠿⠿⠿⣦⣄ +⠀⠀⢠⣾⣿⣿⠏⢸⣿⣿⣿⡷⢸⣿⠘⢸⣿⠀⠀⠀⠀⠚⠛⠛⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣧⣿⣿⣿⣿⣿⣿⣷⣻⡄⠀⠀⠀⠀⠀⠀⠀⠈ +⢀⣴⢟⣿⣿⠋⠀⢸⣿⣿⣿⡇⣸⣿⡆⠩⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⡏⣿⢾⣿⣿⣿⡌⠻⢿⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀ +⠋⢁⣾⡿⠁⠀⠀⢸⣿⣿⣿⣿⣿⣿⣇⠀⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡞⠀⣿⢸⣿⣿⣿⣿⣦⣤⣴⠟⠋⠉⠙⠲⣄⠀⠀⠀⠀ +⢀⣾⠋⠀⠀⠀⠀⢸⣿⣿⡿⠟⣿⣿⣿⠀⢸⡏⠳⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠞⠀⢠⣧⣿⣿⣿⡿⠿⠿⠋⠁⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀ +⠋⠁⠀⠀⠀⠀⠀⢸⠟⠋⠀⠀⣿⣿⣿⣇⠀⣷⡀⠈⠳⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠀⠀⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⡼⠋⠀⠀⠀⠀⢹⣿⣿⣿⡄⣸⣷⡀⠀⠀⠙⠲⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠞⠁⠀⠀⠀⢠⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢰⠇⠀⠀⠀⠀⠀⠀⣿⡈⢿⣿⣏⢸⣷⡄⠀⠀⠀⠀⠈⠻⣖⠤⢄⣀⠀⠀⠀⠀⣠⠖⢫⡏⠀⢀⡔⠀⠀⣸⣿⠟⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠸⣧⠀⠙⢿⣿⣿⣝⠶⣄⡀⠀⠀⠀⠈⠳⣄⡈⠉⠒⠒⠋⠁⢠⠏⢀⡴⠋⠀⠀⣰⡿⠋⣼⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢸⡄⠀⠀⢠⠀⠀⠀⠀⠘⢆⠀⠀⠉⠛⠿⢆⡈⠉⠀⠀⠀⠀⠀⠈⠙⠲⢤⣀⠀⣰⣋⡴⠚⠀⠀⠀⡴⠛⠁⣸⢹⣿⣻⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⣾⣇⠀⠀⠘⣆⠀⠀⠀⠀⠈⠃⠀⠀⠀⠀⠀⠻⡄⠀⠀⠀⠀⠀⠀⠀⠀⢀⡽⢷⠓⠢⣄⠀⠀⠀⠀⠀⠀⢰⠇⣾⣟⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢸⣿⣿⡆⠀⠀⠘⠆⠲⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⢠⠟⠀⠈⢧⠀⠈⠳⣄⠀⠀⠀⢠⠇⠀⣿⣻⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⣾⣿⣿⢹⡆⠀⠀⠀⢦⢳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⢀⡎⠀⡔⠲⡌⢳⡀⠀⠈⠳⣄⣰⠏⠀⢸⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⣀⡄⠀⠀⠀⡄⠀⠀⠀ +⠀⠀⠀⠀⣿⣿⡟⢸⣿⣦⠀⠀⠀⠳⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠦⠟⠀⠀⢧⣀⡼⠀⡇⠀⠀⠀⠀⠁⠀⢀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⢰⠏⠀⠀⠀⠘⣿⠀⠀⠀ +⠀⠀⠀⢸⣿⣿⡇⠘⣿⣿⣷⡀⠀⠀⠈⢳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⢸⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⡏⠀⠀⠀⠀⠀⣿⠀⠀⠀ +⠀⠀⠀⠸⣿⣿⠃⠀⣿⣿⣿⣿⣦⡀⠀⠀⠳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⣸⣿⣿⠀⠀⠀⠀⠀⠀⠀⢰⠏⠀⠀⠀⠀⢸⣿⡇⠀⠀ +⠀⠀⠀⠀⣿⣿⠀⠀⢿⣿⣿⣿⣿⡟⣦⡀⢠⡹⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⢀⡰⠃⣠⠆⠀⠀⠀⣸⢻⡇⠀⠀ diff --git a/frontend/public/ascii/ascii7.txt b/frontend/public/ascii/ascii7.txt new file mode 100644 index 0000000..e3f323b --- /dev/null +++ b/frontend/public/ascii/ascii7.txt @@ -0,0 +1,28 @@ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠊⠀⠀⠀⠀⡴⠂⠀⠀⠀⠀⢦⠀⠀⠀⠀⠙⠢⡀⠀⢀⠈⠱⡀⠀⠀⠀⠘⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠞⠀⠀⠀⠀⠀⡸⠁⠀⠀⢀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠘⢆⠀⠉⠂⡘⣦⠀⠀⠀⠀⠱⡄⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⢠⠋⠀⠀⢠⠄⠀⢠⠇⠀⠀⠀⢸⠀⠀⠀⠀⢧⠀⠀⠀⠀⠀⠀⢣⠀⠀⢙⣿⣇⠀⠀⠀⠀⠘⡄⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢀⠏⠀⠀⠀⡏⠀⠀⢸⠀⠀⠀⠀⠘⡄⠀⠀⠀⠘⣆⠀⠀⠀⠀⠀⠀⢣⠀⢄⠈⢿⡇⠀⠀⠀⠀⣸⡆⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⡞⡘⠀⠀⢰⠃⠀⠀⡞⠀⠀⠀⠀⠀⢳⠀⠀⠀⠀⣿⡄⠀⠀⠀⠀⠀⠈⡆⠀⠑⣾⣿⠀⠀⢀⣼⠟⣧⠀⠀⠀⠀⠀ +⠼⠀⠀⠀⠀⠀⢰⠃⡇⠀⠀⡾⡄⠀⠀⣷⠀⠀⠀⠀⠀⢸⢧⠀⠀⠀⣹⢿⠀⠀⠀⠀⠀⠀⢹⠈⠢⣼⣾⣇⣴⠛⠁⠀⢹⠀⠀⠀⠀⠀ +⠀⠀⢢⠀⠀⠀⢸⢸⠁⠀⠀⠃⡧⢄⣰⡿⡄⠀⠀⠀⠀⢨⣀⠷⣖⠉⢹⠀⣧⠀⠀⠀⠀⠀⠸⡆⠢⣀⣿⡟⢿⣤⣀⣀⣸⡆⠀⠀⠀⠀ +⠉⠒⢤⣧⠀⠀⢺⢸⠀⠀⢈⡆⢳⠀⢘⡟⢻⡂⠀⠀⠀⢹⠀⢀⣼⡷⣟⡛⠻⣶⣤⡀⠀⠀⠀⡿⣀⣸⠟⠷⡀⠙⠻⠿⣿⠃⠀⠀⠀⠀ +⠀⠀⠀⠉⠓⠀⢸⢸⡄⠀⠘⡇⢸⣷⣾⢟⣲⠷⣤⡀⠀⢸⡆⠉⢸⠃⠀⢻⡄⢯⡿⠀⠀⢠⣴⠃⢹⠁⢠⡄⠘⣆⠀⠀⢿⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⣀⡀⢸⣿⡇⠀⠀⢱⠈⣿⣁⡏⣈⣧⠈⠙⢤⡐⢇⠀⠸⣎⠛⣹⡇⢺⠁⠀⠀⣼⣿⢠⠃⢠⠟⡋⢠⠟⡅⠀⣼⠀⠀⠀⠀⠀ +⠀⠀⠀⢸⣿⠁⠀⣿⣿⣄⠀⠀⢧⢹⡈⢧⣉⡿⠀⠀⠀⠉⠺⠄⠀⠈⠛⠉⠀⠈⡇⠀⢀⣿⠏⢀⣀⣸⡞⢀⠇⠀⢡⠀⡇⠀⠀⠀⠀⠀ +⣤⠶⢻⡿⠿⠆⠀⠸⡇⢻⣧⠀⠈⣷⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⢀⡾⠇⠀⠉⠀⢀⣠⠜⠀⠀⠸⡄⡇⠀⠀⠀⠀⠀ +⠀⢀⣸⠃⠀⠀⠀⠀⠘⠀⠻⣧⡀⢹⡇⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⢹⠏⠀⠀⣨⢛⣿⣿⣿⠀⠀⠀⠸⣇⡀⠀⠀⠀⠀⠀ +⠾⢋⣥⣶⠟⠀⠀⠀⠀⠀⠀⠈⠋⠛⢷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠛⠋⠀⢀⡴⠁⢸⣿⣿⣿⡄⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀ +⠀⠉⠡⡿⣶⣶⣶⠂⠀⠀⠀⠀⠀⠀⠀⠑⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠏⠀⠀⢸⣿⣿⢹⣇⠀⠀⠀⢹⡇⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠙⢿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣱⣦⣶⠿⠛⠋⠉⠀⠀⠀⠀⢀⣴⡿⠁⠀⠀⠀⢸⣿⣿⣿⣿⡄⠀⠀⠘⣧⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⡤⠤⢤⣀⣔⣶⠤⠔⣚⠭⢻⠘⡏⢱⣦⣄⣀⣀⣠⣴⣾⣿⠏⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣧⠀⠀⠀⢻⡄⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⣰⠗⢀⣉⣉⠹⠿⣾⡍⠀⠀⠈⡖⣷⠈⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⢀⡴⠊⠁⠀⠀⠉⠙⢦⡀⠀⠈⢇⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⣾⢋⡠⠋⡁⠀⠀⡠⠟⠁⠀⠀⢀⣱⣿⣧⣸⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⢠⠋⠀⠀⠀⠀⢠⠀⠀⠀⠱⡄⠀⠸⡄⠀⠀⠀ +⠀⠀⠀⠀⠀⡇⠀⠀⠈⢀⠔⡏⠀⠀⡤⠊⠉⠀⠀⢸⠋⠉⢉⠹⡿⠋⢇⠀⠀⠀⠀⢀⠇⠀⠀⠀⠀⠀⠀⣆⠀⠀⠀⠹⡀⠀⢣⠀⠀⠀ +⠀⠀⠀⠀⠀⠇⠀⠀⠀⢸⢰⠃⠀⡜⠀⠀⠀⢠⠀⠙⣤⠞⢡⠞⠀⢀⡼⠀⠀⣀⡠⢾⠀⠀⠀⠀⠀⠀⠀⡉⠀⠀⠀⠀⢣⠀⠘⡆⠀⠀ +⠀⠀⠀⠀⠀⢸⠀⠀⠀⢸⣿⠀⢸⠁⠑⢄⠀⢸⢀⠔⠁⣰⠁⠀⠀⠘⠃⠜⠁⠀⢀⡆⠀⠀⢀⡠⠒⠉⠁⠉⠑⢄⠀⠀⠈⡆⠀⢹⠀⠀ +⠀⠀⠀⠀⠀⣠⡆⠀⠀⠼⣿⣧⡇⠀⠀⠀⠑⡷⠋⢀⡜⠉⠦⣀⡀⠀⣀⡠⠔⠊⠉⡇⡠⠖⠁⠀⠀⠀⠀⠀⠀⠈⢢⠀⠀⢱⠀⠀⣇⠀ +⠀⠀⠀⠀⣼⣷⡇⠀⢀⠜⣡⣿⠃⠀⠀⢀⡜⠁⢠⠎⠀⠀⠀⠈⢉⡉⠁⠀⠀⠀⢸⡇⠀⢀⠆⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⢸⠀⢇⠸⡀ +⠀⠀⠀⠘⣧⡉⠓⠒⢁⠜⠩⣇⢀⠀⣠⠎⠀⣠⠃⠀⠀⠰⢀⠔⠁⠀⠀⠀⠀⠀⡟⡇⢀⡌⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢸⠀⢸⠀⡇ +⠀⠀⠀⡴⣇⠈⠁⠀⠉⢀⠀⡼⠠⡶⠉⠉⠈⠉⠉⠑⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚⠁⡇⡜⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⣿⡄⠀⡇⢸ +⠀⠀⠀⢦⡈⣉⡡⠖⠊⠁⠀⣑⢠⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⢠⢛⣷⠀⡇⠘ diff --git a/frontend/public/asuka-headset-logo.svg b/frontend/public/asuka-headset-logo.svg new file mode 100644 index 0000000..f8ba4c0 --- /dev/null +++ b/frontend/public/asuka-headset-logo.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/background-animation.gif b/frontend/public/background-animation.gif new file mode 100644 index 0000000..f59418c Binary files /dev/null and b/frontend/public/background-animation.gif differ diff --git a/frontend/public/kaomoji.txt b/frontend/public/kaomoji.txt new file mode 100644 index 0000000..572027f --- /dev/null +++ b/frontend/public/kaomoji.txt @@ -0,0 +1,3 @@ +ᗜ˰ᗜ +ᗜ▵ᗜ +ᗜˬᗜ diff --git a/frontend/public/logo/crane-logo-transparent.png b/frontend/public/logo/crane-logo-transparent.png new file mode 100644 index 0000000..9ea5fbd Binary files /dev/null and b/frontend/public/logo/crane-logo-transparent.png differ diff --git a/frontend/public/logo/crane-logo.png b/frontend/public/logo/crane-logo.png new file mode 100644 index 0000000..0978467 Binary files /dev/null and b/frontend/public/logo/crane-logo.png differ diff --git a/frontend/public/nazrin-bg.jpg b/frontend/public/nazrin-bg.jpg new file mode 100644 index 0000000..ba42485 Binary files /dev/null and b/frontend/public/nazrin-bg.jpg differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..0a0e008 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { useStore } from '@nanostores/react' +import { LandingPage } from './components/LandingPage' +import { VNInterface } from './components/VNInterface' +import { isLoggedInStore, login, logout } from './stores' + +export default function App() { + const isLoggedIn = useStore(isLoggedInStore) + + return ( + <> + {isLoggedIn ? ( + + ) : ( + + )} + + ) +} diff --git a/frontend/src/components/BottomBar.tsx b/frontend/src/components/BottomBar.tsx new file mode 100644 index 0000000..b9f19ed --- /dev/null +++ b/frontend/src/components/BottomBar.tsx @@ -0,0 +1,19 @@ +import React from 'react' + +type Props = { + onSkip?: () => void + onAuto?: () => void + onLog?: () => void + location?: string +} + +export const BottomBar: React.FC = ({ onSkip, onAuto, onLog, location = 'Tokyo' }) => { + return ( +
+ + + +
+
+ ) +} diff --git a/frontend/src/components/ChoiceMenu.tsx b/frontend/src/components/ChoiceMenu.tsx new file mode 100644 index 0000000..0de86f6 --- /dev/null +++ b/frontend/src/components/ChoiceMenu.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { Choice } from '../types' + +type Props = { + choices: Choice[] + onSelect: (id: string) => void +} + +export const ChoiceMenu: React.FC = ({ choices, onSelect }) => { + if (!choices.length) return null + return ( +
+ {choices.map((c) => ( + + ))} +
+ ) +} diff --git a/frontend/src/components/CityscapeBackground.tsx b/frontend/src/components/CityscapeBackground.tsx new file mode 100644 index 0000000..82a679d --- /dev/null +++ b/frontend/src/components/CityscapeBackground.tsx @@ -0,0 +1,310 @@ +import React, { useRef, useEffect } from 'react' +import * as THREE from 'three' + +interface CityscapeBackgroundProps { + className?: string +} + +export function CityscapeBackground({ className }: CityscapeBackgroundProps) { + const canvasRef = useRef(null) + const sceneRef = useRef() + const rendererRef = useRef() + const cameraRef = useRef() + const animationIdRef = useRef() + const speedLinesRef = useRef() + const mouseRef = useRef(new THREE.Vector2()) + const startTimeRef = useRef(Date.now()) + + useEffect(() => { + if (!canvasRef.current) { + console.log('CityscapeBackground: Canvas ref not available') + return + } + + console.log('FuturistBackground: Initializing 3D futurist scene') + + // Scene setup with futurist art movement atmosphere + const scene = new THREE.Scene() + scene.background = new THREE.Color(0x000000) // Black background + scene.fog = new THREE.Fog(0x000000, 30, 120) // Black fog for depth + + // Camera setup for isometric view + const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200) + camera.position.set(30, 25, 30) + camera.lookAt(0, 0, 0) + + // Renderer setup + const renderer = new THREE.WebGLRenderer({ + canvas: canvasRef.current, + antialias: false, // Disable for better performance + alpha: false // Disable alpha for opaque background + }) + renderer.setSize(window.innerWidth, window.innerHeight) + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)) // Limit pixel ratio for performance + + // Store references + sceneRef.current = scene + rendererRef.current = renderer + cameraRef.current = camera + + // Futurist art movement lighting - dramatic contrasts inspired by paintings + const ambientLight = new THREE.AmbientLight(0x1a1a1a, 0.4) // Warm grey ambient + scene.add(ambientLight) + + // Bold primary light - warm like industrial sunlight + const primaryLight = new THREE.DirectionalLight(0xffaa33, 1.8) // Golden orange + primaryLight.position.set(-25, 35, 20) + scene.add(primaryLight) + + // Dynamic red light for energy and motion + const dynamicLight = new THREE.DirectionalLight(0xdd2222, 1.2) // Bold red + dynamicLight.position.set(20, 10, -15) + scene.add(dynamicLight) + + // Contrasting blue for depth and drama + const contrastLight = new THREE.PointLight(0x2244bb, 0.8, 50) // Deep blue + contrastLight.position.set(-30, 5, 0) + scene.add(contrastLight) + + // Yellow accent for vibrant highlights + const accentLight = new THREE.PointLight(0xdddd22, 0.7, 40) // Yellow + accentLight.position.set(25, 20, -5) + scene.add(accentLight) + + // Green industrial light + const industrialLight = new THREE.PointLight(0x22aa22, 0.6, 45) // Industrial green + industrialLight.position.set(0, -5, 30) + scene.add(industrialLight) + + // Create speed lines + createSpeedLines() + + console.log('FuturistBackground: Dynamic futurist art created, starting animation') + + // Reset start time + startTimeRef.current = Date.now() + + // Animation loop + const animate = () => { + animationIdRef.current = requestAnimationFrame(animate) + + const time = Date.now() * 0.001 // Convert to seconds for smoother animation + + // Animate speed lines + animateSpeedLines(time) + + // Apply interactive effects + applyFuturistInteractions() + + // Dramatic camera movement inspired by futurist dynamism + const cameraSpeed = time * 0.2 + camera.position.x = 20 + Math.sin(cameraSpeed * 1.3) * 12 + Math.cos(cameraSpeed * 0.7) * 6 + camera.position.y = 15 + Math.cos(cameraSpeed * 0.9) * 8 + Math.sin(cameraSpeed * 1.1) * 4 + camera.position.z = 25 + Math.sin(cameraSpeed * 0.6) * 15 + Math.cos(cameraSpeed * 1.4) * 8 + + // Dynamic look-at point for more dramatic perspective shifts + const lookAtTarget = new THREE.Vector3( + Math.sin(cameraSpeed * 0.8) * 5, + Math.cos(cameraSpeed * 0.5) * 3, + Math.sin(cameraSpeed * 1.2) * 8 + ) + camera.lookAt(lookAtTarget) + + renderer.render(scene, camera) + } + + animate() + + // Mouse tracking for interactive effects + const handleMouseMove = (event: MouseEvent) => { + // Convert screen coordinates to normalized device coordinates (-1 to +1) + mouseRef.current.set( + (event.clientX / window.innerWidth) * 2 - 1, + -(event.clientY / window.innerHeight) * 2 + 1 + ) + } + + // Handle window resize + const handleResize = () => { + if (camera && renderer) { + camera.aspect = window.innerWidth / window.innerHeight + camera.updateProjectionMatrix() + renderer.setSize(window.innerWidth, window.innerHeight) + } + } + + window.addEventListener('mousemove', handleMouseMove) + window.addEventListener('resize', handleResize) + + // Cleanup + return () => { + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current) + } + window.removeEventListener('mousemove', handleMouseMove) + window.removeEventListener('resize', handleResize) + + // Dispose of Three.js objects + scene.clear() + renderer.dispose() + } + }, []) + + const applyFuturistInteractions = () => { + const camera = cameraRef.current! + const raycaster = new THREE.Raycaster() + + // Convert mouse coordinates to world position + raycaster.setFromCamera(mouseRef.current, camera) + + // Interactive speed line distortion + if (speedLinesRef.current) { + const positions = speedLinesRef.current.geometry.attributes.position.array as Float32Array + + for (let i = 0; i < positions.length; i += 6) { // Step by 6 for line pairs + const linePos = new THREE.Vector3(positions[i], positions[i + 1], positions[i + 2]) + const mouseWorldPos = new THREE.Vector3() + raycaster.ray.at(linePos.z, mouseWorldPos) + + const distance = linePos.distanceTo(mouseWorldPos) + const distortRadius = 15 + + if (distance < distortRadius) { + const distortStrength = (distortRadius - distance) / distortRadius + + // Create dramatic wave distortion around mouse + const waveX = Math.sin(Date.now() * 0.02 + i) * distortStrength * 2.0 + const waveY = Math.cos(Date.now() * 0.025 + i) * distortStrength * 1.5 + + positions[i] += waveX + positions[i + 1] += waveY + positions[i + 3] += waveX * 0.8 // End point follows with slight lag + positions[i + 4] += waveY * 0.8 + } + } + + speedLinesRef.current.geometry.attributes.position.needsUpdate = true + } + } + + + const createSpeedLines = () => { + const scene = sceneRef.current! + + // Create sharp, dramatic speed lines + const motionVertices = [] + const motionColors = [] + + // High-contrast speed colors + const speedColors = [ + { r: 1.0, g: 1.0, b: 1.0 }, // Brilliant white + { r: 1.0, g: 0.1, b: 0.1 }, // Sharp red + { r: 1.0, g: 0.8, b: 0.0 }, // Electric yellow + { r: 0.0, g: 1.0, b: 1.0 }, // Cyan + { r: 1.0, g: 0.0, b: 1.0 }, // Magenta + ] + + // Create 300 sharp speed lines for intense motion + for (let i = 0; i < 300; i++) { + // Start point - spread across space + const startX = (Math.random() - 0.5) * 100 + const startY = (Math.random() - 0.5) * 40 + const startZ = Math.random() * -200 + + // End point - long streaks suggesting extreme speed + const endX = startX + (Math.random() - 0.5) * 20 + const endY = startY + (Math.random() - 0.5) * 8 + const endZ = startZ + 20 + Math.random() * 40 // Longer streaks + + motionVertices.push(startX, startY, startZ) + motionVertices.push(endX, endY, endZ) + + // High-contrast colors for sharp appearance + const colorIndex = Math.floor(Math.random() * speedColors.length) + const color = speedColors[colorIndex] + + motionColors.push(color.r, color.g, color.b) + motionColors.push(color.r, color.g, color.b) + } + + const motionGeometry = new THREE.BufferGeometry() + motionGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(motionVertices), 3)) + motionGeometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(motionColors), 3)) + + const motionMaterial = new THREE.LineBasicMaterial({ + vertexColors: true, + transparent: true, + opacity: 0.9, + linewidth: 2 + }) + + const speedLines = new THREE.LineSegments(motionGeometry, motionMaterial) + speedLinesRef.current = speedLines + scene.add(speedLines) + } + + + + + + + const animateSpeedLines = (time: number) => { + if (!speedLinesRef.current) return + + const positions = speedLinesRef.current.geometry.attributes.position.array as Float32Array + + for (let i = 0; i < positions.length; i += 6) { // Step by 6 since we have line pairs + // Extreme speed effect - lines blazing toward camera + positions[i + 2] += 6.0 + Math.sin(time * 2 + i) * 3.0 // High variable speed + positions[i + 5] += 6.0 + Math.sin(time * 2 + i) * 3.0 // End point matches + + // Intense lateral movement for speed blur + const lateralMotion = Math.sin(time * 1.5 + i * 0.15) * 0.8 + positions[i] += lateralMotion + positions[i + 3] += lateralMotion + + // Sharp vertical oscillation for dynamic energy + const verticalMotion = Math.cos(time * 2.2 + i * 0.12) * 0.6 + positions[i + 1] += verticalMotion + positions[i + 4] += verticalMotion + + // Reset lines that have blazed past camera + if (positions[i + 2] > 100) { + positions[i + 2] = -250 - Math.random() * 100 + positions[i + 5] = positions[i + 2] + 20 + Math.random() * 40 + + positions[i] = (Math.random() - 0.5) * 100 + positions[i + 1] = (Math.random() - 0.5) * 40 + positions[i + 3] = positions[i] + (Math.random() - 0.5) * 20 + positions[i + 4] = positions[i + 1] + (Math.random() - 0.5) * 8 + } + } + + speedLinesRef.current.geometry.attributes.position.needsUpdate = true + + // Rapid rotation for intense motion blur + speedLinesRef.current.rotation.x += 0.008 + speedLinesRef.current.rotation.y += 0.012 + speedLinesRef.current.rotation.z += 0.025 + } + + + + return ( + + ) +} \ No newline at end of file diff --git a/frontend/src/components/ConfigModal.tsx b/frontend/src/components/ConfigModal.tsx new file mode 100644 index 0000000..e7b1f9f --- /dev/null +++ b/frontend/src/components/ConfigModal.tsx @@ -0,0 +1,44 @@ +import React from 'react' + +type Props = { + isOpen: boolean + onClose: () => void + skipIntro: boolean + onSkipIntroChange: (skip: boolean) => void +} + +export const ConfigModal: React.FC = ({ isOpen, onClose, skipIntro, onSkipIntroChange }) => { + if (!isOpen) return null + + return ( +
+
e.stopPropagation()}> +
+

Configuration

+ +
+ +
+
+ +
+ Skip the loading screen animation on startup +
+
+
+ +
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/DialogueBox.tsx b/frontend/src/components/DialogueBox.tsx new file mode 100644 index 0000000..ad5975a --- /dev/null +++ b/frontend/src/components/DialogueBox.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +type Props = { + name?: string + text: string +} + +export const DialogueBox: React.FC = ({ name = 'You', text }) => { + return ( +
+
{name}
+
{text}
+
+ ) +} diff --git a/frontend/src/components/HeartLogo.tsx b/frontend/src/components/HeartLogo.tsx new file mode 100644 index 0000000..2d407f7 --- /dev/null +++ b/frontend/src/components/HeartLogo.tsx @@ -0,0 +1,270 @@ +import React, { useState, useEffect } from 'react' + +interface HeartLogoProps { + size?: 'small' | 'medium' | 'large' | 'header' | 'header-no-rays' + className?: string + onClick?: () => void +} + +export function HeartLogo({ size = 'small', className = '', onClick }: HeartLogoProps) { + const [animationOffset, setAnimationOffset] = useState(0) + const [isAnimating, setIsAnimating] = useState(false) + + // Click handler to toggle animation + const handleClick = () => { + setIsAnimating(!isAnimating) + if (onClick) { + onClick() + } + } + + // Animation for header beams + useEffect(() => { + if (size !== 'header-no-rays' || !isAnimating) return + + let animationId: number + let startTime = Date.now() + + const animate = () => { + const elapsed = Date.now() - startTime + const rotationSpeed = 0.02 // Slow rotation speed + const offset = (elapsed * rotationSpeed) % 360 + setAnimationOffset(offset) + animationId = requestAnimationFrame(animate) + } + + animationId = requestAnimationFrame(animate) + + return () => { + if (animationId) { + cancelAnimationFrame(animationId) + } + } + }, [size, isAnimating]) + if (size === 'header') { + // Header version with rays filling the entire header rectangle + return ( +
+
+
+
+ + {/* Header rays matching loading screen exactly */} + + {[...Array(16)].map((_, i) => { + const angle = (i * 22.5); + const rayWidth = 2; // Much thinner rays + + // Create triangular ray path - thin rays extending to header edges + const startAngle = angle - rayWidth; + const endAngle = angle + rayWidth; + + const innerRadius = 0; // Start from center (heart center) + const outerRadius = 200; // Extend far beyond to reach all header edges + + const x1 = Math.cos(startAngle * Math.PI / 180) * innerRadius; + const y1 = Math.sin(startAngle * Math.PI / 180) * innerRadius; + const x2 = Math.cos(endAngle * Math.PI / 180) * innerRadius; + const y2 = Math.sin(endAngle * Math.PI / 180) * innerRadius; + const x3 = Math.cos(startAngle * Math.PI / 180) * outerRadius; + const y3 = Math.sin(startAngle * Math.PI / 180) * outerRadius; + const x4 = Math.cos(endAngle * Math.PI / 180) * outerRadius; + const y4 = Math.sin(endAngle * Math.PI / 180) * outerRadius; + + return ( + + ); + })} + + +
+
+ + + +
+
+
+
+ ) + } + + if (size === 'header-no-rays') { + // Header version with small red beams - compact sunlight effect + return ( +
+
+
+ + {/* Red beams extending across entire header rectangle - optimized for wide screens */} + + {[...Array(32)].map((_, i) => { + const angle = (i * 11.25) + animationOffset; // 32 rays, 11.25 degrees apart, animated + const rayWidth = 2; // Much narrower rays to show background between them + + // Create triangular ray path + const startAngle = angle - rayWidth; + const endAngle = angle + rayWidth; + + const innerRadius = 0; // Start from heart center + + // Calculate intersection with rectangle bounds for wide header + const getIntersectionWithRect = (angleInDegrees: number) => { + const rad = angleInDegrees * Math.PI / 180; + const dx = Math.cos(rad); + const dy = Math.sin(rad); + + // Header rectangle bounds (wide format ~3:1 ratio) + const rectBoundsX = 200; // Full width + const rectBoundsY = 67; // Height to match header proportions + + // Calculate intersection with each edge + let t = Infinity; + + // Right edge (x = rectBoundsX) + if (dx > 0) { + t = Math.min(t, rectBoundsX / dx); + } + // Left edge (x = -rectBoundsX) + if (dx < 0) { + t = Math.min(t, -rectBoundsX / dx); + } + // Top edge (y = -rectBoundsY) + if (dy < 0) { + t = Math.min(t, -rectBoundsY / dy); + } + // Bottom edge (y = rectBoundsY) + if (dy > 0) { + t = Math.min(t, rectBoundsY / dy); + } + + return { x: dx * t, y: dy * t }; + }; + + const x1 = Math.cos(startAngle * Math.PI / 180) * innerRadius; + const y1 = Math.sin(startAngle * Math.PI / 180) * innerRadius; + const x2 = Math.cos(endAngle * Math.PI / 180) * innerRadius; + const y2 = Math.sin(endAngle * Math.PI / 180) * innerRadius; + + const edge1 = getIntersectionWithRect(startAngle); + const edge2 = getIntersectionWithRect(endAngle); + + return ( + + ); + })} + + +
+
+ + + +
+
+
+ ) + } + + // Original square version for other sizes + return ( +
+
+
+
+ + {/* Rising Sun flag style rays - triangular rays extending to rectangle edges */} + + {[...Array(16)].map((_, i) => { + const angle = (i * 22.5); + const rayWidth = 11.25; // Half the angle between rays for triangular shape + + // Create triangular ray path + const startAngle = angle - rayWidth; + const endAngle = angle + rayWidth; + + const innerRadius = 0; // Start from center (heart center) + + // Calculate intersection with rectangle bounds + // Rectangle aspect ratio 120:80 = 3:2, so use different bounds for x and y + const getIntersectionWithRect = (angleInDegrees: number) => { + const rad = angleInDegrees * Math.PI / 180; + const dx = Math.cos(rad); + const dy = Math.sin(rad); + + // Rectangle bounds matching the 120x80 aspect ratio (3:2) + const rectBoundsX = 100; // Full width + const rectBoundsY = 67; // 2/3 of width to maintain 3:2 ratio + + // Calculate intersection with each edge + let t = Infinity; + + // Right edge (x = rectBoundsX) + if (dx > 0) { + t = Math.min(t, rectBoundsX / dx); + } + // Left edge (x = -rectBoundsX) + if (dx < 0) { + t = Math.min(t, -rectBoundsX / dx); + } + // Top edge (y = -rectBoundsY) + if (dy < 0) { + t = Math.min(t, -rectBoundsY / dy); + } + // Bottom edge (y = rectBoundsY) + if (dy > 0) { + t = Math.min(t, rectBoundsY / dy); + } + + return { x: dx * t, y: dy * t }; + }; + + const x1 = Math.cos(startAngle * Math.PI / 180) * innerRadius; + const y1 = Math.sin(startAngle * Math.PI / 180) * innerRadius; + const x2 = Math.cos(endAngle * Math.PI / 180) * innerRadius; + const y2 = Math.sin(endAngle * Math.PI / 180) * innerRadius; + + const edge1 = getIntersectionWithRect(startAngle); + const edge2 = getIntersectionWithRect(endAngle); + + return ( + + ); + })} + + +
+
+ + + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/LandingPage.tsx b/frontend/src/components/LandingPage.tsx new file mode 100644 index 0000000..f5dc55c --- /dev/null +++ b/frontend/src/components/LandingPage.tsx @@ -0,0 +1,219 @@ +import React, { useState, useEffect } from 'react' +import { LoadingScreen } from './LoadingScreen' +import { HeartLogo } from './HeartLogo' +// Using direct PNG logo on landing header + +interface LandingPageProps { + onLogin: () => void +} + +export function LandingPage({ onLogin }: LandingPageProps) { + const [loading, setLoading] = useState(false) + const [showLanding, setShowLanding] = useState(false) + const [isStandby, setIsStandby] = useState(true) // false = LIVE, true = STDBY + const [asciiArt, setAsciiArt] = useState('') + const [isGlitching, setIsGlitching] = useState(false) + // Note: Removed VN preview and ASCII features to focus on an Art Deco/Nouveau landing. + + // Auto-fade in landing page content after component mounts + useEffect(() => { + const timer = setTimeout(() => { + setShowLanding(true) + }, 500) // Delay before fading in content + + return () => clearTimeout(timer) + }, []) + + // Removed VN dialogue preview / typing effect. + + // Load ASCII art for overlay in hero (right/bottom) + useEffect(() => { + fetch('/ascii/ascii1.txt') + .then((res) => res.text()) + .then((txt) => setAsciiArt(txt.replace(/\n+$/, ''))) + .catch(() => setAsciiArt('')) + }, []) + + // Occasionally trigger a brief glitch on the word "futurist" + useEffect(() => { + let armTimer: number | undefined + let activeTimer: number | undefined + let cancelled = false + + const arm = () => { + // Random delay between glitches: 0.8s–2s (slightly punchier) + const delay = 800 + Math.random() * 1200 + armTimer = window.setTimeout(() => { + setIsGlitching(true) + // Glitch duration: 150ms–350ms (snappier) + const dur = 150 + Math.random() * 200 + activeTimer = window.setTimeout(() => { + setIsGlitching(false) + if (!cancelled) arm() + }, dur) + }, delay) + } + + arm() + + return () => { + cancelled = true + if (armTimer) clearTimeout(armTimer) + if (activeTimer) clearTimeout(activeTimer) + } + }, []) + + + // Handle loading screen completion + const handleLoadingComplete = () => { + // Call the original onLogin immediately to transition to VN page + // Don't reset loading states as we're leaving the landing page + onLogin() + } + + // Handle login button click + const handleLogin = () => { + setLoading(true) + } + + // Removed ASCII art effects and rendering. + + return ( +
+ {loading && ( + + )} + + {/* Floating Header Bar - Hidden during loading */} + {!loading && ( +
+
+
+ Soryu { const img = (e.currentTarget as HTMLImageElement); img.onerror = null; img.src = '/logo/crane-logo.png'; }} + /> +
+
+ +
+ +
+
+ System: + setIsStandby(!isStandby)} + title="Click to toggle between LIVE and STANDBY" + > + + {isStandby ? 'STDBY' : 'LIVE'} + +
+
+ Version: + v1.0.0 +
+
+
+
+ )} + +
+ {/* Retro-futuristic page background */} + + {/* Taisho Magazine Cover Backdrop */} +
+ + + {/* Cover Content Grid */} +
+ {/* Vertical Masthead (magazine-style) */} +
+
+ そりゅう + SORYU +
+
かはいい Vol.01
+
+ + {/* Central Hero - full-frame retro-futuristic */} +
+
+
+ {/* Retro-futuristic racing hero content */} + +
+
+
+ + {/* CTA */} +
+ +
+ {/* Visitor Counter */} +
+ visit counter +
+
+
+
+
+ ) +} diff --git a/frontend/src/components/LoadingScreen.tsx b/frontend/src/components/LoadingScreen.tsx new file mode 100644 index 0000000..5f33d00 --- /dev/null +++ b/frontend/src/components/LoadingScreen.tsx @@ -0,0 +1,129 @@ +import React, { useEffect, useState, useRef } from 'react' + +type Props = { + onComplete: () => void +} + +export const LoadingScreen: React.FC = ({ onComplete }) => { + const [fadeOut, setFadeOut] = useState(false) + const [mounted, setMounted] = useState(true) + const [subtitle, setSubtitle] = useState('') + const [animatedText, setAnimatedText] = useState('') + const subtitleLoaded = useRef(false) + + useEffect(() => { + console.log('Loading screen mounted - starting timer...') + + // Set fixed subtitle and animate it + if (!subtitleLoaded.current) { + subtitleLoaded.current = true + + const fixedSubtitle = 'Whisper of the Heart' + setSubtitle(fixedSubtitle) + + // Animate text character by character + let currentIndex = 0 + const animateText = () => { + if (currentIndex <= fixedSubtitle.length) { + setAnimatedText(fixedSubtitle.slice(0, currentIndex)) + currentIndex++ + setTimeout(animateText, 50) // 50ms delay between characters + } + } + + // Start animation after heart appears (1000ms = 0.2s heart delay + 0.8s heart animation) + setTimeout(animateText, 1000) + } + + // Start fade after 4 seconds (longer to show ray spinning) + const fadeTimer = setTimeout(() => { + console.log('4 seconds passed - Starting fade out...') + setFadeOut(true) + }, 4000) + + // Complete after fade finishes (4s show + 1s fade = 5s total) + const completeTimer = setTimeout(() => { + console.log('5 seconds passed - Loading screen completing...') + setMounted(false) + onComplete() + }, 5000) + + return () => { + console.log('Cleaning up timers...') + clearTimeout(fadeTimer) + clearTimeout(completeTimer) + } + }, []) // Empty dependency array - run once on mount + + const handleClick = () => { + console.log('Loading screen clicked, completing early...') + setFadeOut(true) + setTimeout(() => { + setMounted(false) + onComplete() + }, 1000) + } + + console.log('LoadingScreen render - fadeOut:', fadeOut, 'mounted:', mounted) + + if (!mounted) return null + + return ( +
+
+
+
+ + {/* Rising Sun flag style rays - triangular rays extending to edges */} + + {[...Array(16)].map((_, i) => { + const angle = (i * 22.5); + const nextAngle = ((i + 1) * 22.5); + const rayWidth = 11.25; // Half the angle between rays for triangular shape + + // Create triangular ray path + const startAngle = angle - rayWidth; + const endAngle = angle + rayWidth; + + const innerRadius = 0; // Start from center (heart center) + const outerRadius = 70; // Extend to screen edge + + const x1 = Math.cos(startAngle * Math.PI / 180) * innerRadius; + const y1 = Math.sin(startAngle * Math.PI / 180) * innerRadius; + const x2 = Math.cos(endAngle * Math.PI / 180) * innerRadius; + const y2 = Math.sin(endAngle * Math.PI / 180) * innerRadius; + const x3 = Math.cos(startAngle * Math.PI / 180) * outerRadius; + const y3 = Math.sin(startAngle * Math.PI / 180) * outerRadius; + const x4 = Math.cos(endAngle * Math.PI / 180) * outerRadius; + const y4 = Math.sin(endAngle * Math.PI / 180) * outerRadius; + + return ( + + ); + })} + + +
+
+ + + +
+
soryu
+
{animatedText}
+
+ ... +
+
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/components/OrigamiDragonLogo.tsx b/frontend/src/components/OrigamiDragonLogo.tsx new file mode 100644 index 0000000..6a7a5fb --- /dev/null +++ b/frontend/src/components/OrigamiDragonLogo.tsx @@ -0,0 +1,180 @@ +import React from 'react' + +type Variant = 'mark' | 'ribbonS' | 'badge' | 'crane' | 'craneOutline' + +type Props = { + variant?: Variant + height?: number + color?: string + className?: string + title?: string + strokeWidth?: number +} + +/** + * Origami-style dragon logo for small header placement. + * - Uses currentColor by default so it adapts to surrounding text color. + * - Keep height around 20–24px in compact headers. + */ +export const OrigamiDragonLogo: React.FC = ({ + variant = 'crane', + height = 20, + color = 'currentColor', + className = '', + title = 'Soryu logo', + strokeWidth = 12, +}) => { + if (variant === 'craneOutline') { + // Outline rendition inspired by provided licensed crane icon + return ( + + {title} + + {/* Left tail → left wing base → body pivot */} + + {/* Left wing upstroke */} + + {/* Left wing inner crease to pivot */} + + {/* Body lower crease */} + + {/* Center mast (rear triangle) */} + + {/* Right wing outer edge */} + + {/* Right wing inner crease */} + + {/* Neck and head */} + + {/* Neck inner crease */} + + + + ) + } + if (variant === 'crane') { + // Origami crane silhouette with a dragon head + return ( + + {title} + {/* Left/up wing */} + + {/* Right/down wing */} + + {/* Body (diamond) */} + + {/* Tail */} + + {/* Neck */} + + {/* Dragon head (top plane) */} + + {/* Dragon jaw plane */} + + {/* Small horn */} + + + ) + } + if (variant === 'mark') { + // Angular origami dragon head/neck, faceted into simple planes + return ( + + {title} + {/* Upper head */} + + {/* Snout / jaw plane */} + + {/* Neck planes */} + + + {/* Small ear/horn suggestion */} + + + ) + } + + if (variant === 'ribbonS') { + // Folded ribbon forming an angular "S" silhouette + return ( + + {title} + {/* Top fold */} + + {/* Middle fold (turning back) */} + + {/* Lower fold */} + + + ) + } + + // badge + return ( + + {title} + {/* Hex container */} + + {/* Mini mark inside */} + + + + + + + ) +} + +export default OrigamiDragonLogo diff --git a/frontend/src/components/TopBar.tsx b/frontend/src/components/TopBar.tsx new file mode 100644 index 0000000..89d7206 --- /dev/null +++ b/frontend/src/components/TopBar.tsx @@ -0,0 +1,24 @@ +import React from 'react' + +type Props = { + title?: string + status?: string +} + +export const TopBar: React.FC = ({ title = 'PC-98 VISUAL NOVEL', status = 'IDLE' }) => { + return ( +
+
+ + FILE + SAVE + LOAD + CONFIG +
+
{title}
+
+ {status} +
+
+ ) +} diff --git a/frontend/src/components/VNApp.tsx b/frontend/src/components/VNApp.tsx new file mode 100644 index 0000000..0f73d2c --- /dev/null +++ b/frontend/src/components/VNApp.tsx @@ -0,0 +1,228 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react' +import { TopBar } from './TopBar' +import { VNViewport } from './VNViewport' +import { DialogueBox } from './DialogueBox' +import { ChoiceMenu } from './ChoiceMenu' +import { BottomBar } from './BottomBar' +import { LoadingScreen } from './LoadingScreen' +import { ConfigModal } from './ConfigModal' +import { VNWebSocket } from '../services/ws' +import { ChatMessage, Choice } from '../types' +// Using direct PNG logo in VN header + +const DEFAULT_CHOICES: Choice[] = [ + { id: 'greet', label: '"Hello?"' }, + { id: 'who', label: '"Who are you?"' }, + { id: 'silence', label: '(Stay silent)' }, +] + +export function VNApp() { + const [loading, setLoading] = useState(true) + const [loadingComplete, setLoadingComplete] = useState(false) + const [messages, setMessages] = useState([ + { id: 'm1', role: 'assistant', content: 'A warm CRT glow fills the room. A figure turns towards you...' }, + ]) + const [choices, setChoices] = useState(DEFAULT_CHOICES) + const [status, setStatus] = useState('OFFLINE') + const [name, setName] = useState('???') + const [bg, setBg] = useState('/__gaogao__56242cbde8f18ac64522e410bad04e68_waifu2x_art_noise2.png') + const [money, setMoney] = useState(15000) + const [currentTime, setCurrentTime] = useState(new Date()) + const [location, setLocation] = useState('Tokyo') + const [weather, setWeather] = useState('Sunny 22°C') + const [configModalOpen, setConfigModalOpen] = useState(false) + const [skipIntro, setSkipIntro] = useState(() => { + const saved = localStorage.getItem('skipIntro') + return saved === 'true' + }) + + const ws = useMemo(() => new VNWebSocket('ws://localhost:8080/ws'), []) + const lastId = useRef(2) + + // Check if intro should be skipped on initial load + useEffect(() => { + if (skipIntro) { + setLoading(false) + setLoadingComplete(true) + // Set skip intro to true after first time loading screen is shown + localStorage.setItem('skipIntro', 'true') + } + }, []) + + // Handle skip intro toggle + const handleSkipIntroChange = (skip: boolean) => { + setSkipIntro(skip) + localStorage.setItem('skipIntro', skip.toString()) + } + + // Handle loading screen completion + const handleLoadingComplete = () => { + setLoading(false) + setLoadingComplete(true) + // Set skip intro to true after first time loading screen is shown + if (!skipIntro) { + setSkipIntro(true) + localStorage.setItem('skipIntro', 'true') + } + } + + useEffect(() => { + ws.on('open', () => setStatus('ONLINE')) + ws.on('close', () => setStatus('OFFLINE')) + ws.on('message', (data) => { + // Expecting { type: 'assistant'|'choices'|'bg'|'name'|'system', ... } + if (data?.type === 'assistant') { + pushMessage('assistant', data.content || '') + } else if (data?.type === 'choices') { + setChoices((data.items || []).map((it: any, idx: number) => ({ id: it.id ?? String(idx), label: it.label ?? String(it) }))) + } else if (data?.type === 'bg') { + setBg(data.src || '/bg.jpg') + } else if (data?.type === 'name') { + setName(data.value || name) + } else if (data?.type === 'system') { + pushMessage('system', data.content || '') + } + }) + ws.connect() + return () => ws.close() + }, [ws]) + + // Update clock every second + useEffect(() => { + const timer = setInterval(() => { + setCurrentTime(new Date()) + }, 1000) + return () => clearInterval(timer) + }, []) + + function pushMessage(role: 'user' | 'assistant' | 'system', content: string) { + lastId.current += 1 + setMessages((prev) => [...prev, { id: 'm' + lastId.current, role, content }]) + } + + function handleChoice(id: string) { + const choice = choices.find(c => c.id === id) + const content = choice?.label || id + pushMessage('user', content) + ws.send({ type: 'user_choice', id, label: content }) + setChoices([]) + setTimeout(() => { + pushMessage('assistant', `The figure nods: "${content}"...`) + setChoices(DEFAULT_CHOICES) + setName('Guide') + }, 600) + } + + const lastAssistant = messages.slice().reverse().find(m => m.role !== 'user') + + return ( +
+ {!loadingComplete && ( + + )} +
+
+
+
+ Character A +
+
+
+ + + + +
+
+
+ +
+
+
+ Soryu { const img = (e.currentTarget as HTMLImageElement); img.onerror = null; img.src = '/logo/crane-logo.png'; }} + /> +
+
+ +
+
+ {location} - {weather} +
+
+
+ +
+ { + if (e.key === 'Enter') { + const target = e.target as HTMLInputElement; + if (target.value.trim()) { + pushMessage('user', target.value); + target.value = ''; + } + } + }} + /> +
+
+
{new Date().toLocaleDateString('ja-JP', { year: 'numeric', month: 'long', day: 'numeric' })} {new Date().toLocaleTimeString('ja-JP', { hour12: false })}
+
+
+ {currentTime.toLocaleDateString('ja-JP', { year: 'numeric', month: 'long', day: 'numeric' })} {currentTime.toLocaleTimeString('ja-JP', { hour12: false })} +
+
+ {money.toLocaleString()} +
+
+
+ + + + +
+
+ {}} + onAuto={() => {}} + onLog={() => {}} + location={location} + /> +
+ +
+
+ Character B +
+
+
+ {currentTime.toLocaleDateString('ja-JP', { year: 'numeric', month: 'long', day: 'numeric' })} +
+ {currentTime.toLocaleTimeString('ja-JP', { hour12: false })} +
+
+ {money.toLocaleString()} +
+
+
+
+
+ + setConfigModalOpen(false)} + skipIntro={skipIntro} + onSkipIntroChange={handleSkipIntroChange} + /> +
+ ) +} diff --git a/frontend/src/components/VNInterface.tsx b/frontend/src/components/VNInterface.tsx new file mode 100644 index 0000000..be71d27 --- /dev/null +++ b/frontend/src/components/VNInterface.tsx @@ -0,0 +1,208 @@ +import React, { useEffect } from 'react' +import { useStore } from '@nanostores/react' +import { + isStandbyStore, + currentTimeStore, + weatherStore, + showChoicesStore, + showSettingsModalStore, + isVisibleStore, + yenBalanceStore, + toggleStandby, + toggleShowChoices, + updateTime +} from '../stores' + +interface VNInterfaceProps { + onLogout: () => void +} + +export function VNInterface({ onLogout }: VNInterfaceProps) { + const isStandby = useStore(isStandbyStore) + const currentTime = useStore(currentTimeStore) + const weather = useStore(weatherStore) + const showChoices = useStore(showChoicesStore) + const showSettingsModal = useStore(showSettingsModalStore) + const isVisible = useStore(isVisibleStore) + const yenBalance = useStore(yenBalanceStore) + + // Fade in effect on mount + useEffect(() => { + const timer = setTimeout(() => { + isVisibleStore.set(true) + }, 100) + return () => clearTimeout(timer) + }, []) + + // Update clock every second (Japan Time) + useEffect(() => { + const timer = setInterval(() => { + const now = new Date() + // Convert to Japan Time (UTC+9) + const japanTime = new Date(now.getTime() + (now.getTimezoneOffset() * 60000) + (9 * 3600000)) + updateTime() + }, 1000) + return () => clearInterval(timer) + }, []) + + return ( +
+ {/* Background */} +
+ Background image +
+ + {/* Combined Info Panel (Top Right) */} +
+
+ {/* Weather Section */} +
+
🌤️
+
+
Tokyo
+
22°C Sunny
+
+
+ + {/* Time Section */} +
+
{currentTime.toLocaleDateString('ja-JP', { + year: 'numeric', + month: 'long', + day: 'numeric', + weekday: 'short' + })}
+
{currentTime.toLocaleTimeString('ja-JP', { + hour12: false, + hour: '2-digit', + minute: '2-digit' + })}
+
+ + {/* Status Section */} +
+
+ Balance: + ¥{yenBalance.toLocaleString()} +
+
+ System: + + + {isStandby ? 'STDBY' : 'LIVE'} + +
+
+
+
+ + {/* Main VN Viewport */} +
+
+
+
+ + {/* Dialogue Panel (Bottom) */} +
+
+
???
+
+ A warm CRT glow fills the room. A figure turns towards you... +
+
+
+ + {/* Input/Choice Panel (Bottom) */} +
+
+ {!showChoices ? ( + // Text Input Mode + { + if (e.key === 'Enter') { + const target = e.target as HTMLInputElement; + if (target.value.trim()) { + console.log('User input:', target.value); + target.value = ''; + } + } + }} + /> + ) : ( + // Choice Options Mode +
+ + + +
+ )} + + {/* Toggle Button */} + +
+
+ + {/* Floating Settings Button */} + + + {/* Settings Modal */} + {showSettingsModal && ( +
showSettingsModalStore.set(false)}> +
e.stopPropagation()}> +
+

Settings

+ +
+
+
+

Display Options

+
+ +
+
+
+

Audio

+
+ + +
+
+
+
+ + +
+
+
+ )} +
+ ) +} diff --git a/frontend/src/components/VNViewport.tsx b/frontend/src/components/VNViewport.tsx new file mode 100644 index 0000000..fe01264 --- /dev/null +++ b/frontend/src/components/VNViewport.tsx @@ -0,0 +1,22 @@ +import React from 'react' + +type Props = { + bgSrc?: string + children?: React.ReactNode +} + +export const VNViewport: React.FC = ({ bgSrc = '/bg.jpg', children }) => { + return ( +
+
+
+ {bgSrc && Background} +
+ {children} +
+