diff --git a/nest-front/.gitignore b/nest-front/.gitignore
new file mode 100644
index 0000000..d83ea62
--- /dev/null
+++ b/nest-front/.gitignore
@@ -0,0 +1,29 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# MISC
+.claude
+claude.md
+subject.md
diff --git a/nest-front/Dockerfile b/nest-front/Dockerfile
new file mode 100644
index 0000000..202c534
--- /dev/null
+++ b/nest-front/Dockerfile
@@ -0,0 +1,18 @@
+FROM node:22-alpine AS build
+
+WORKDIR /app
+
+COPY package.json package-lock.json ./
+RUN npm ci
+
+COPY . .
+RUN npm run build
+
+FROM nginx:alpine
+
+COPY --from=build /app/dist /usr/share/nginx/html
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+EXPOSE 5173
+
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/nest-front/README.md b/nest-front/README.md
new file mode 100644
index 0000000..d2e7761
--- /dev/null
+++ b/nest-front/README.md
@@ -0,0 +1,73 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
+
+```js
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+
+ // Remove tseslint.configs.recommended and replace with this
+ tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ tseslint.configs.stylisticTypeChecked,
+
+ // Other configs...
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
+
+You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ // Other configs...
+ // Enable lint rules for React
+ reactX.configs['recommended-typescript'],
+ // Enable lint rules for React DOM
+ reactDom.configs.recommended,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ // other options...
+ },
+ },
+])
+```
diff --git a/nest-front/docker-compose.yml b/nest-front/docker-compose.yml
new file mode 100644
index 0000000..0428972
--- /dev/null
+++ b/nest-front/docker-compose.yml
@@ -0,0 +1,6 @@
+services:
+ nest-front:
+ build: .
+ ports:
+ - "5173:5173"
+ restart: unless-stopped
diff --git a/nest-front/eslint.config.js b/nest-front/eslint.config.js
new file mode 100644
index 0000000..5e6b472
--- /dev/null
+++ b/nest-front/eslint.config.js
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/nest-front/index.html b/nest-front/index.html
new file mode 100644
index 0000000..5634312
--- /dev/null
+++ b/nest-front/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Headless Hazard | CrowMate Studio
+
+
+
+
+
+
diff --git a/nest-front/nginx.conf b/nest-front/nginx.conf
new file mode 100644
index 0000000..36fb97e
--- /dev/null
+++ b/nest-front/nginx.conf
@@ -0,0 +1,9 @@
+server {
+ listen 5173;
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
diff --git a/nest-front/package-lock.json b/nest-front/package-lock.json
new file mode 100644
index 0000000..c187a27
--- /dev/null
+++ b/nest-front/package-lock.json
@@ -0,0 +1,3817 @@
+{
+ "name": "client",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "client",
+ "version": "0.0.0",
+ "dependencies": {
+ "@tailwindcss/vite": "^4.1.18",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "react-router-dom": "^6.30.3",
+ "tailwindcss": "^4.1.18"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.48.0",
+ "vite": "^7.3.1"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "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.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@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.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@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,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "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,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "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.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@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==",
+ "license": "MIT",
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.23.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
+ "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
+ "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
+ "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-x64": "4.1.18",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.18",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
+ "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
+ "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
+ "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
+ "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
+ "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
+ "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
+ "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
+ "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
+ "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
+ "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.0",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
+ "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
+ "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz",
+ "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.18",
+ "@tailwindcss/oxide": "4.1.18",
+ "tailwindcss": "4.1.18"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "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,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.10.13",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
+ "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz",
+ "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/type-utils": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.56.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz",
+ "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz",
+ "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.56.0",
+ "@typescript-eslint/types": "^8.56.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz",
+ "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz",
+ "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz",
+ "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
+ "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz",
+ "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.56.0",
+ "@typescript-eslint/tsconfig-utils": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/visitor-keys": "8.56.0",
+ "debug": "^4.4.3",
+ "minimatch": "^9.0.5",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz",
+ "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.56.0",
+ "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz",
+ "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.56.0",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz",
+ "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
+ "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.19",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "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"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001770",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz",
+ "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==",
+ "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"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.286",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
+ "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.19.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
+ "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "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,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.2",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.26",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "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,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
+ "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
+ "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.3",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
+ "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.23.2",
+ "react-router": "6.30.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "license": "MIT",
+ "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.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
+ "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
+ "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz",
+ "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.56.0",
+ "@typescript-eslint/parser": "8.56.0",
+ "@typescript-eslint/typescript-estree": "8.56.0",
+ "@typescript-eslint/utils": "8.56.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "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"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ }
+ }
+}
diff --git a/nest-front/package.json b/nest-front/package.json
new file mode 100644
index 0000000..e6ac854
--- /dev/null
+++ b/nest-front/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "client",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@tailwindcss/vite": "^4.1.18",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "react-router-dom": "^6.30.3",
+ "tailwindcss": "^4.1.18"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.48.0",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/nest-front/public/manifest.json b/nest-front/public/manifest.json
new file mode 100644
index 0000000..b03f7ab
--- /dev/null
+++ b/nest-front/public/manifest.json
@@ -0,0 +1,21 @@
+{
+ "name": "Headless Hazard — CrowMate Studio",
+ "short_name": "Headless Hazard",
+ "description": "Community hub for Headless Hazard, a retro-futuristic puzzle game by CrowMate Studio.",
+ "start_url": "/",
+ "display": "standalone",
+ "background_color": "#ffffff",
+ "theme_color": "#ffffff",
+ "icons": [
+ {
+ "src": "/icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/nest-front/public/vite.svg b/nest-front/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/nest-front/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/nest-front/src/App.css b/nest-front/src/App.css
new file mode 100644
index 0000000..9aa63c7
--- /dev/null
+++ b/nest-front/src/App.css
@@ -0,0 +1 @@
+/* App-level overrides — global styles live in index.css */
diff --git a/nest-front/src/App.tsx b/nest-front/src/App.tsx
new file mode 100644
index 0000000..8af3f33
--- /dev/null
+++ b/nest-front/src/App.tsx
@@ -0,0 +1,54 @@
+import { lazy, Suspense } from 'react';
+import { Routes, Route } from 'react-router-dom';
+import { AuthProvider } from './contexts/AuthContext';
+import { ProtectedRoute } from './components/shared/ProtectedRoute';
+import { PublicLayout } from './components/layout/PublicLayout';
+import { PageLoader } from './components/shared/PageLoader';
+
+// ── Public Pages (lazy-loaded) ────────────────────────────────────────────────
+
+const HomePage = lazy(() => import('./pages/public/HomePage'));
+const StudioPage = lazy(() => import('./pages/public/StudioPage'));
+const EventsPage = lazy(() => import('./pages/public/EventsPage'));
+const ForumPage = lazy(() => import('./pages/public/ForumPage'));
+const ThreadPage = lazy(() => import('./pages/public/ThreadPage'));
+const BugReportPage = lazy(() => import('./pages/public/BugReportPage'));
+const BugDetailPage = lazy(() => import('./pages/public/BugDetailPage'));
+const AccountPage = lazy(() => import('./pages/public/AccountPage'));
+const LoginPage = lazy(() => import('./pages/public/LoginPage'));
+const RegisterPage = lazy(() => import('./pages/public/RegisterPage'));
+const NotFoundPage = lazy(() => import('./pages/public/NotFoundPage'));
+
+// ── App ────────────────────────────────────────────────────────────────────────
+
+export default function App() {
+ return (
+
+ }>
+
+ {/* Public Routes */}
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ }
+ />
+ } />
+ } />
+ } />
+
+
+
+
+ );
+}
diff --git a/nest-front/src/assets/react.svg b/nest-front/src/assets/react.svg
new file mode 100644
index 0000000..6c87de9
--- /dev/null
+++ b/nest-front/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/nest-front/src/components/layout/PublicLayout.tsx b/nest-front/src/components/layout/PublicLayout.tsx
new file mode 100644
index 0000000..d676591
--- /dev/null
+++ b/nest-front/src/components/layout/PublicLayout.tsx
@@ -0,0 +1,31 @@
+import { Outlet, useLocation } from 'react-router-dom';
+import { useEffect, useRef } from 'react';
+import { Navbar } from '../shared/Navbar';
+import { Footer } from '../shared/Footer';
+import { DevRoleSwitcher } from '../shared/DevRoleSwitcher';
+
+export function PublicLayout() {
+ const location = useLocation();
+ const mainRef = useRef(null);
+
+ // Scroll to top and add page-enter animation on route change
+ useEffect(() => {
+ window.scrollTo({ top: 0, behavior: 'instant' });
+ const el = mainRef.current;
+ if (!el) return;
+ el.classList.remove('page-enter');
+ void el.offsetWidth; // reflow to restart animation
+ el.classList.add('page-enter');
+ }, [location.pathname]);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/nest-front/src/components/shared/DevRoleSwitcher.tsx b/nest-front/src/components/shared/DevRoleSwitcher.tsx
new file mode 100644
index 0000000..a4627b8
--- /dev/null
+++ b/nest-front/src/components/shared/DevRoleSwitcher.tsx
@@ -0,0 +1,118 @@
+import { useAuth } from '../../contexts/AuthContext';
+import type { UserRole } from '../../types';
+
+/**
+ * Developer-only overlay to quickly switch user roles for testing.
+ * Only visible in development mode.
+ */
+export function DevRoleSwitcher() {
+ if (import.meta.env.PROD) return null;
+
+ return ;
+}
+
+function DevRoleSwitcherInner() {
+ const { user, isAuthenticated, devSetRole, login, logout } = useAuth();
+
+ const ROLES: UserRole[] = ['user', 'dev', 'com'];
+ const DEV_ACCOUNTS = [
+ { label: 'Dev/Admin (Kestrel)', email: 'kestrel@crowmate.dev' },
+ { label: 'Com Staff (Vesper)', email: 'vesper@crowmate.dev' },
+ { label: 'User (GlitchHunter)', email: 'glitch@mail.com' },
+ ];
+
+ return (
+
+
+ [DEV] Auth Switcher
+
+
+ {isAuthenticated ? (
+ <>
+
+ Logged as: {user?.username}
+
+
+ Role: {user?.role}
+
+
+
+ {ROLES.map((r) => (
+ devSetRole(r)}
+ style={{
+ background: user?.role === r ? 'var(--color-amber)' : 'transparent',
+ border: '1px solid var(--color-amber)',
+ color: user?.role === r ? '#000' : 'var(--color-amber)',
+ padding: '0.1rem 0.4rem',
+ cursor: 'pointer',
+ fontFamily: 'var(--font-mono)',
+ fontSize: '0.65rem',
+ }}
+ >
+ {r}
+
+ ))}
+
+
+
+ Logout
+
+ >
+ ) : (
+ <>
+
Quick login:
+ {DEV_ACCOUNTS.map(({ label, email }) => (
+
login(email, 'password')}
+ style={{
+ background: 'transparent',
+ border: '1px solid var(--color-border)',
+ color: 'var(--color-text-dim)',
+ padding: '0.2rem 0.4rem',
+ cursor: 'pointer',
+ fontFamily: 'var(--font-mono)',
+ fontSize: '0.63rem',
+ width: '100%',
+ marginBottom: '0.2rem',
+ textAlign: 'left',
+ }}
+ >
+ {label}
+
+ ))}
+ >
+ )}
+
+ );
+}
diff --git a/nest-front/src/components/shared/Footer.tsx b/nest-front/src/components/shared/Footer.tsx
new file mode 100644
index 0000000..124d0b2
--- /dev/null
+++ b/nest-front/src/components/shared/Footer.tsx
@@ -0,0 +1,103 @@
+import { Link } from 'react-router-dom';
+
+export function Footer() {
+ const year = new Date().getFullYear();
+
+ return (
+
+ );
+}
diff --git a/nest-front/src/components/shared/Navbar.tsx b/nest-front/src/components/shared/Navbar.tsx
new file mode 100644
index 0000000..4d418d5
--- /dev/null
+++ b/nest-front/src/components/shared/Navbar.tsx
@@ -0,0 +1,202 @@
+import { useState, useCallback } from 'react';
+import { Link, NavLink, useNavigate } from 'react-router-dom';
+import { useAuth } from '../../contexts/AuthContext';
+
+const NAV_LINKS = [
+ { to: '/', label: 'Home', end: true },
+ { to: '/studio', label: 'Studio', end: false },
+ { to: '/events', label: 'Events', end: false },
+ { to: '/forum', label: 'Forum', end: false },
+ { to: '/bugs', label: 'Bugs', end: false },
+];
+
+export function Navbar() {
+ const { user, isAuthenticated, logout } = useAuth();
+ const navigate = useNavigate();
+ const [menuOpen, setMenuOpen] = useState(false);
+
+ const handleLogout = useCallback(() => {
+ logout();
+ setMenuOpen(false);
+ navigate('/');
+ }, [logout, navigate]);
+
+ const closeMenu = useCallback(() => setMenuOpen(false), []);
+
+ const navLinkStyle = ({ isActive }: { isActive: boolean }): React.CSSProperties => ({
+ fontFamily: 'var(--font-mono)',
+ fontSize: '0.82rem',
+ textTransform: 'uppercase',
+ letterSpacing: '0.12em',
+ textDecoration: 'none',
+ color: isActive ? 'var(--color-yellow)' : 'var(--color-text-dim)',
+ borderBottom: isActive ? '2px solid var(--color-yellow)' : '2px solid transparent',
+ paddingBottom: '2px',
+ transition: 'color 0.1s, border-color 0.1s',
+ });
+
+ return (
+
+ );
+}
diff --git a/nest-front/src/components/shared/PageLoader.tsx b/nest-front/src/components/shared/PageLoader.tsx
new file mode 100644
index 0000000..b74ca67
--- /dev/null
+++ b/nest-front/src/components/shared/PageLoader.tsx
@@ -0,0 +1,25 @@
+export function PageLoader() {
+ return (
+
+
+
+ LOADING
+
+
+ CROWMATE STUDIO / HEADLESS HAZARD
+
+
+
+ );
+}
diff --git a/nest-front/src/components/shared/ProtectedRoute.tsx b/nest-front/src/components/shared/ProtectedRoute.tsx
new file mode 100644
index 0000000..917ac26
--- /dev/null
+++ b/nest-front/src/components/shared/ProtectedRoute.tsx
@@ -0,0 +1,29 @@
+import { Navigate, useLocation } from 'react-router-dom';
+import { useAuth } from '../../contexts/AuthContext';
+
+interface ProtectedRouteProps {
+ children: React.ReactNode;
+ /** If true, requires staff role (dev or com). */
+ staffOnly?: boolean;
+ /** Redirect destination when access is denied. Defaults to /login. */
+ redirectTo?: string;
+}
+
+export function ProtectedRoute({
+ children,
+ staffOnly = false,
+ redirectTo = '/login',
+}: ProtectedRouteProps) {
+ const { isAuthenticated, isStaff } = useAuth();
+ const location = useLocation();
+
+ if (!isAuthenticated) {
+ return ;
+ }
+
+ if (staffOnly && !isStaff) {
+ return ;
+ }
+
+ return <>{children}>;
+}
diff --git a/nest-front/src/contexts/AuthContext.tsx b/nest-front/src/contexts/AuthContext.tsx
new file mode 100644
index 0000000..d14d158
--- /dev/null
+++ b/nest-front/src/contexts/AuthContext.tsx
@@ -0,0 +1,156 @@
+import React, {
+ createContext,
+ useCallback,
+ useContext,
+ useMemo,
+ useState,
+} from 'react';
+import type { User, UserRole } from '../types';
+import { MOCK_USERS } from '../data/mockData';
+
+// ── Types ──────────────────────────────────────────────────────────────────────
+
+interface AuthContextValue {
+ user: User | null;
+ isAuthenticated: boolean;
+ isStaff: boolean;
+ isAdmin: boolean;
+ login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>;
+ register: (username: string, email: string, password: string) => Promise<{ success: boolean; error?: string }>;
+ logout: () => void;
+ updateUsername: (username: string) => void;
+ // Dev helper: quickly switch role for testing
+ devSetRole: (role: UserRole) => void;
+}
+
+// ── Context ────────────────────────────────────────────────────────────────────
+
+const AuthContext = createContext(null);
+
+// ── Provider ───────────────────────────────────────────────────────────────────
+
+const STORAGE_KEY = 'crowmate_auth_user';
+
+function loadUserFromStorage(): User | null {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (!raw) return null;
+ return JSON.parse(raw) as User;
+ } catch {
+ return null;
+ }
+}
+
+function saveUserToStorage(user: User | null): void {
+ if (user) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(user));
+ } else {
+ localStorage.removeItem(STORAGE_KEY);
+ }
+}
+
+export function AuthProvider({ children }: { children: React.ReactNode }) {
+ const [user, setUser] = useState(loadUserFromStorage);
+
+ const isAuthenticated = user !== null;
+ const isStaff = user?.role === 'dev' || user?.role === 'com';
+ const isAdmin = user?.isAdmin === true;
+
+ const login = useCallback(
+ async (email: string, _password: string): Promise<{ success: boolean; error?: string }> => {
+ // Simulate network delay
+ await new Promise((r) => setTimeout(r, 400));
+
+ const found = MOCK_USERS.find(
+ (u) => u.email.toLowerCase() === email.toLowerCase()
+ );
+
+ if (!found) {
+ return { success: false, error: 'No account found with that email address.' };
+ }
+ if (found.isBanned) {
+ return { success: false, error: 'This account has been suspended.' };
+ }
+
+ setUser(found);
+ saveUserToStorage(found);
+ return { success: true };
+ },
+ []
+ );
+
+ const register = useCallback(
+ async (username: string, email: string, _password: string): Promise<{ success: boolean; error?: string }> => {
+ await new Promise((r) => setTimeout(r, 500));
+
+ const emailTaken = MOCK_USERS.some(
+ (u) => u.email.toLowerCase() === email.toLowerCase()
+ );
+ if (emailTaken) {
+ return { success: false, error: 'An account with this email already exists.' };
+ }
+
+ const usernameTaken = MOCK_USERS.some(
+ (u) => u.username.toLowerCase() === username.toLowerCase()
+ );
+ if (usernameTaken) {
+ return { success: false, error: 'This username is already taken.' };
+ }
+
+ const newUser: User = {
+ id: `u${Date.now()}`,
+ username,
+ email,
+ role: 'user',
+ isAdmin: false,
+ isBanned: false,
+ createdAt: new Date().toISOString(),
+ };
+
+ setUser(newUser);
+ saveUserToStorage(newUser);
+ return { success: true };
+ },
+ []
+ );
+
+ const logout = useCallback(() => {
+ setUser(null);
+ saveUserToStorage(null);
+ }, []);
+
+ const updateUsername = useCallback((username: string) => {
+ setUser((prev) => {
+ if (!prev) return prev;
+ const updated = { ...prev, username };
+ saveUserToStorage(updated);
+ return updated;
+ });
+ }, []);
+
+ const devSetRole = useCallback((role: UserRole) => {
+ setUser((prev) => {
+ if (!prev) return prev;
+ const updated = { ...prev, role, isAdmin: role === 'dev' };
+ saveUserToStorage(updated);
+ return updated;
+ });
+ }, []);
+
+ const value = useMemo(
+ () => ({ user, isAuthenticated, isStaff, isAdmin, login, register, logout, updateUsername, devSetRole }),
+ [user, isAuthenticated, isStaff, isAdmin, login, register, logout, updateUsername, devSetRole]
+ );
+
+ return {children} ;
+}
+
+// ── Hook ───────────────────────────────────────────────────────────────────────
+
+export function useAuth(): AuthContextValue {
+ const ctx = useContext(AuthContext);
+ if (!ctx) {
+ throw new Error('useAuth must be used inside ');
+ }
+ return ctx;
+}
diff --git a/nest-front/src/data/mockData.ts b/nest-front/src/data/mockData.ts
new file mode 100644
index 0000000..792d22f
--- /dev/null
+++ b/nest-front/src/data/mockData.ts
@@ -0,0 +1,797 @@
+import type {
+ User,
+ ForumCategory,
+ ForumThread,
+ ForumReply,
+ BugReport,
+ BugComment,
+ BugReportNote,
+ StaffPost,
+ TeamMember,
+ EventPost,
+ Poll,
+} from '../types';
+
+// ── Mock Users ─────────────────────────────────────────────────────────────────
+
+export const MOCK_USERS: User[] = [
+ {
+ id: 'u1',
+ username: 'Kestrel',
+ email: 'kestrel@crowmate.dev',
+ role: 'dev',
+ isAdmin: true,
+ isBanned: false,
+ createdAt: '2023-09-01T08:00:00Z',
+ },
+ {
+ id: 'u2',
+ username: 'Vesper',
+ email: 'vesper@crowmate.dev',
+ role: 'com',
+ isAdmin: false,
+ isBanned: false,
+ createdAt: '2023-09-03T10:00:00Z',
+ },
+ {
+ id: 'u3',
+ username: 'GlitchHunter',
+ email: 'glitch@mail.com',
+ role: 'user',
+ isAdmin: false,
+ isBanned: false,
+ createdAt: '2024-01-15T14:22:00Z',
+ },
+ {
+ id: 'u4',
+ username: 'NullPointer',
+ email: 'null@mail.com',
+ role: 'user',
+ isAdmin: false,
+ isBanned: false,
+ createdAt: '2024-02-20T09:11:00Z',
+ },
+ {
+ id: 'u5',
+ username: 'XenoArch',
+ email: 'xeno@mail.com',
+ role: 'user',
+ isAdmin: false,
+ isBanned: false,
+ createdAt: '2024-03-05T16:44:00Z',
+ },
+ {
+ id: 'u6',
+ username: 'Phantom404',
+ email: 'phantom@mail.com',
+ role: 'user',
+ isAdmin: false,
+ isBanned: true,
+ createdAt: '2024-04-12T12:00:00Z',
+ },
+ {
+ id: 'u7',
+ username: 'NeonCrawler',
+ email: 'neon@mail.com',
+ role: 'user',
+ isAdmin: false,
+ isBanned: false,
+ createdAt: '2024-05-01T08:30:00Z',
+ },
+ {
+ id: 'u8',
+ username: 'ByteWitch',
+ email: 'byte@mail.com',
+ role: 'dev',
+ isAdmin: false,
+ isBanned: false,
+ createdAt: '2023-09-10T11:00:00Z',
+ },
+];
+
+// Quick-access login credentials for demo
+// Any email + password combo works. Role determined by MOCK_USERS list.
+export const DEMO_CREDENTIALS: Record = {
+ admin: 'admin@crowmate.dev', // kestrel — dev + admin
+ staff: 'vesper@crowmate.dev', // vesper — com
+ user: 'glitch@mail.com', // glitchhunter — user
+};
+
+// ── Mock Forum Categories ──────────────────────────────────────────────────────
+
+export const MOCK_CATEGORIES: ForumCategory[] = [
+ {
+ id: 'cat1',
+ name: 'General Discussion',
+ description: 'Everything and anything about Headless Hazard.',
+ icon: '///',
+ threadCount: 42,
+ lastActivity: '2026-02-17T18:30:00Z',
+ },
+ {
+ id: 'cat2',
+ name: 'Game Suggestions',
+ description: 'Share your ideas to improve the game.',
+ icon: '[!]',
+ threadCount: 27,
+ lastActivity: '2026-02-16T09:15:00Z',
+ },
+ {
+ id: 'cat3',
+ name: 'Multiplayer',
+ description: 'Find teammates, share strategies, report cheaters.',
+ icon: '>>',
+ threadCount: 19,
+ lastActivity: '2026-02-18T07:00:00Z',
+ },
+ {
+ id: 'cat4',
+ name: 'Lore & Theories',
+ description: 'Dig into the deep lore of the corporate complex.',
+ icon: '[?]',
+ threadCount: 33,
+ lastActivity: '2026-02-15T21:00:00Z',
+ },
+ {
+ id: 'cat5',
+ name: 'Off Topic',
+ description: 'Chat about anything not related to the game.',
+ icon: '~',
+ threadCount: 14,
+ lastActivity: '2026-02-14T13:45:00Z',
+ },
+ {
+ id: 'cat6',
+ name: 'Technical Support',
+ description: 'Having trouble running the game? Ask here.',
+ icon: '[X]',
+ threadCount: 8,
+ lastActivity: '2026-02-17T10:20:00Z',
+ },
+];
+
+// ── Mock Forum Threads ─────────────────────────────────────────────────────────
+
+export const MOCK_THREADS: ForumThread[] = [
+ {
+ id: 'th1',
+ title: 'Official Welcome Thread — Read Before Posting!',
+ authorId: 'u2',
+ authorName: 'Vesper',
+ categoryId: 'cat1',
+ categoryName: 'General Discussion',
+ content: `Welcome to the official Headless Hazard community forum. Before you post, please read our community guidelines.\n\n1. Be respectful\n2. No spoilers without tags\n3. Use the bug report page for bugs, not the forum\n\nHappy gaming — and watch out for rogue security protocols.`,
+ isPinned: true,
+ isLocked: false,
+ replyCount: 12,
+ createdAt: '2025-11-01T10:00:00Z',
+ updatedAt: '2026-01-10T08:00:00Z',
+ lastReplyAuthor: 'NeonCrawler',
+ },
+ {
+ id: 'th2',
+ title: 'The head physics feel clunky — how do you control it?',
+ authorId: 'u3',
+ authorName: 'GlitchHunter',
+ categoryId: 'cat1',
+ categoryName: 'General Discussion',
+ content: `I've been playing for 3 hours and I still can't wrap my head (lol) around the head movement controls. The inertia system is wild. Anyone have tips?\n\nI keep slamming the head into walls when I try to look around corners.`,
+ isPinned: false,
+ isLocked: false,
+ replyCount: 8,
+ createdAt: '2026-01-20T15:30:00Z',
+ updatedAt: '2026-01-22T11:00:00Z',
+ lastReplyAuthor: 'XenoArch',
+ },
+ {
+ id: 'th3',
+ title: 'SUGGESTION: Let us name the girl!',
+ authorId: 'u4',
+ authorName: 'NullPointer',
+ categoryId: 'cat2',
+ categoryName: 'Game Suggestions',
+ content: `She's referred to as "the girl" throughout the whole game. I think we should be able to name her. It would add so much to the emotional connection.\n\nSome candidates I thought of: Zara, Pip, Elodie, Mira...`,
+ isPinned: false,
+ isLocked: false,
+ replyCount: 31,
+ createdAt: '2026-01-25T09:00:00Z',
+ updatedAt: '2026-02-10T17:30:00Z',
+ lastReplyAuthor: 'ByteWitch',
+ },
+ {
+ id: 'th4',
+ title: 'Looking for co-op partner — Floor 3 boss is brutal',
+ authorId: 'u5',
+ authorName: 'XenoArch',
+ categoryId: 'cat3',
+ categoryName: 'Multiplayer',
+ content: `Floor 3 boss: The Sentinel Prime. It tracks the body AND the head simultaneously. Solo is nearly impossible on hard mode.\n\nAnyone want to team up? I'm online weekday evenings UTC+1.`,
+ isPinned: false,
+ isLocked: false,
+ replyCount: 5,
+ createdAt: '2026-02-01T20:00:00Z',
+ updatedAt: '2026-02-03T09:15:00Z',
+ lastReplyAuthor: 'NeonCrawler',
+ },
+ {
+ id: 'th5',
+ title: 'THEORY: The girl is the daughter of the [BLEEP] CEO',
+ authorId: 'u7',
+ authorName: 'NeonCrawler',
+ categoryId: 'cat4',
+ categoryName: 'Lore & Theories',
+ content: `Hear me out. There's a family portrait in the executive suite on floor 5. The girl in the painting has the same red hair as our protagonist. AND the security clearance codes found in the vault match a name that's always been redacted...\n\nI think her father IS the corporation. This changes everything about why the protocols went haywire.`,
+ isPinned: false,
+ isLocked: false,
+ replyCount: 44,
+ createdAt: '2026-02-05T11:00:00Z',
+ updatedAt: '2026-02-17T22:10:00Z',
+ lastReplyAuthor: 'GlitchHunter',
+ },
+ {
+ id: 'th6',
+ title: 'Game crashes on startup — Windows 11 RTX 4080',
+ authorId: 'u3',
+ authorName: 'GlitchHunter',
+ categoryId: 'cat6',
+ categoryName: 'Technical Support',
+ content: `Getting a DirectX 12 error on startup. Already tried reinstalling, verifying files, and updating drivers.\n\nError: DXGI_ERROR_DEVICE_REMOVED\nOS: Windows 11 22H2\nGPU: RTX 4080`,
+ isPinned: false,
+ isLocked: false,
+ replyCount: 3,
+ createdAt: '2026-02-10T14:20:00Z',
+ updatedAt: '2026-02-11T10:00:00Z',
+ lastReplyAuthor: 'Vesper',
+ },
+ {
+ id: 'th7',
+ title: 'The VHS aesthetic is a MASTERPIECE — appreciation post',
+ authorId: 'u4',
+ authorName: 'NullPointer',
+ categoryId: 'cat1',
+ categoryName: 'General Discussion',
+ content: `Just want to say: whoever did the visual design deserves an award. The scan lines, the color grading, the way the CRT flickers when the head rolls across a monitor screen — *chef's kiss*.\n\nThis is what indie games are about.`,
+ isPinned: false,
+ isLocked: false,
+ replyCount: 17,
+ createdAt: '2026-02-12T08:45:00Z',
+ updatedAt: '2026-02-16T19:30:00Z',
+ lastReplyAuthor: 'Kestrel',
+ },
+ {
+ id: 'th8',
+ title: 'Speedrun strats — sub 45min run possible?',
+ authorId: 'u5',
+ authorName: 'XenoArch',
+ categoryId: 'cat3',
+ categoryName: 'Multiplayer',
+ content: `Current WR is 48:32 by user HexBlade (not on this forum). I think sub-45 is possible with the elevator skip on floor 2 and the head-throw glitch to open the airlock.\n\nLet's document all known skips here.`,
+ isPinned: false,
+ isLocked: false,
+ replyCount: 9,
+ createdAt: '2026-02-14T17:00:00Z',
+ updatedAt: '2026-02-18T06:00:00Z',
+ lastReplyAuthor: 'NullPointer',
+ },
+];
+
+// ── Mock Replies ───────────────────────────────────────────────────────────────
+
+export const MOCK_REPLIES: ForumReply[] = [
+ // Thread 1 replies
+ {
+ id: 'r1',
+ content: 'Thanks for the welcome! Excited to dive into this game. The concept sounds wild.',
+ authorId: 'u3',
+ authorName: 'GlitchHunter',
+ threadId: 'th1',
+ createdAt: '2025-11-02T09:00:00Z',
+ },
+ {
+ id: 'r2',
+ content: 'Read the guidelines. Solid rules. Looking forward to theorizing about the lore!',
+ authorId: 'u7',
+ authorName: 'NeonCrawler',
+ threadId: 'th1',
+ createdAt: '2025-11-05T14:00:00Z',
+ },
+ // Thread 2 replies
+ {
+ id: 'r3',
+ content: 'The trick is to use short bursts. Don\'t hold the direction key — tap it. The head has massive momentum.',
+ authorId: 'u5',
+ authorName: 'XenoArch',
+ threadId: 'th2',
+ createdAt: '2026-01-20T16:00:00Z',
+ },
+ {
+ id: 'r4',
+ content: 'Also check your sensitivity settings. Mine was at 100% by default which is insane. I dropped it to 35%.',
+ authorId: 'u4',
+ authorName: 'NullPointer',
+ threadId: 'th2',
+ createdAt: '2026-01-21T09:30:00Z',
+ },
+ // Thread 3 replies
+ {
+ id: 'r5',
+ content: 'I\'ve been calling her Mira since day one. Feels right.',
+ authorId: 'u7',
+ authorName: 'NeonCrawler',
+ threadId: 'th3',
+ createdAt: '2026-01-25T10:00:00Z',
+ },
+ {
+ id: 'r6',
+ content: 'Hard disagree — the ambiguity is part of the point. She\'s every lost child. Naming her loses that.',
+ authorId: 'u8',
+ authorName: 'ByteWitch',
+ threadId: 'th3',
+ createdAt: '2026-01-25T11:30:00Z',
+ },
+ {
+ id: 'r7',
+ content: 'Voting for Pip. Short, punchy, and weirdly adorable for someone causing this much chaos.',
+ authorId: 'u5',
+ authorName: 'XenoArch',
+ threadId: 'th3',
+ createdAt: '2026-01-26T08:15:00Z',
+ },
+ // Thread 5 replies
+ {
+ id: 'r8',
+ content: 'I noticed that too! The hair is definitely the same shade. And there\'s a memo on floor 3 signed with a heavily redacted name with only the initials visible...',
+ authorId: 'u3',
+ authorName: 'GlitchHunter',
+ threadId: 'th5',
+ createdAt: '2026-02-05T12:00:00Z',
+ },
+ {
+ id: 'r9',
+ content: 'The developers confirmed on stream that the girl\'s backstory will be explored in a DLC. This theory might be onto something.',
+ authorId: 'u8',
+ authorName: 'ByteWitch',
+ threadId: 'th5',
+ createdAt: '2026-02-06T15:00:00Z',
+ },
+ // Thread 6 replies
+ {
+ id: 'r10',
+ content: 'We\'re aware of this crash on certain Nvidia configurations. A patch is being prepared. ETA: next week. Sorry for the inconvenience.',
+ authorId: 'u2',
+ authorName: 'Vesper',
+ threadId: 'th6',
+ createdAt: '2026-02-11T10:00:00Z',
+ },
+ // Thread 7 replies
+ {
+ id: 'r11',
+ content: 'Agreed 100%. The moment I saw the first CRT flicker I knew this was something special.',
+ authorId: 'u3',
+ authorName: 'GlitchHunter',
+ threadId: 'th7',
+ createdAt: '2026-02-12T10:00:00Z',
+ },
+ {
+ id: 'r12',
+ content: 'Thanks for the kind words! The visual team worked incredibly hard on those effects. More surprises coming in future updates.',
+ authorId: 'u1',
+ authorName: 'Kestrel',
+ threadId: 'th7',
+ createdAt: '2026-02-13T09:00:00Z',
+ },
+];
+
+// ── Mock Bug Reports ───────────────────────────────────────────────────────────
+
+export const MOCK_BUG_NOTES: BugReportNote[] = [
+ {
+ id: 'n1',
+ bugReportId: 'bug1',
+ authorId: 'u1',
+ authorName: 'Kestrel',
+ content: 'Reproduced internally. Relates to the head collision hitbox on slopes. Assigning to ByteWitch for physics review.',
+ createdAt: '2026-01-16T09:00:00Z',
+ },
+ {
+ id: 'n2',
+ bugReportId: 'bug3',
+ authorId: 'u8',
+ authorName: 'ByteWitch',
+ content: 'This is the DirectX 12 feature level issue. Nvidia driver 566.x introduced a regression. Workaround: force DX11 via launch options.',
+ createdAt: '2026-02-11T11:00:00Z',
+ },
+];
+
+export const MOCK_BUGS: BugReport[] = [
+ {
+ id: 'bug1',
+ uniqueCode: 'HH-0001',
+ title: 'Head clips through floor geometry on ramp sections',
+ description:
+ 'When rolling the head down the maintenance ramp on floor 2, the head occasionally clips through the floor and falls into the void. This causes a soft reset but loses all checkpoint progress.',
+ stepsToReproduce:
+ '1. Reach Floor 2, section C\n2. Roll head down the maintenance ramp at full speed\n3. Head clips through at approximately 80% of the way down\n4. Game enters a permanent loading state',
+ severity: 'high',
+ gameVersion: '0.9.3-alpha',
+ status: 'in_progress',
+ submittedById: 'u3',
+ submittedByName: 'GlitchHunter',
+ assignedToId: 'u8',
+ assignedToName: 'ByteWitch',
+ createdAt: '2026-01-15T18:00:00Z',
+ updatedAt: '2026-01-16T09:00:00Z',
+ notes: [MOCK_BUG_NOTES[0]],
+ meTooBugs: ['u4', 'u5', 'u7'],
+ },
+ {
+ id: 'bug2',
+ uniqueCode: 'HH-0002',
+ title: 'Audio desync in co-op mode after host migration',
+ description:
+ 'When the host player disconnects and host migration occurs, all audio becomes desynced. Sound effects play 2-3 seconds after the triggering event.',
+ stepsToReproduce:
+ '1. Start a co-op session with 3+ players\n2. Have the host disconnect mid-game\n3. Continue playing after host migration completes\n4. Observe audio desync within 30 seconds',
+ severity: 'medium',
+ gameVersion: '0.9.3-alpha',
+ status: 'open',
+ submittedById: 'u5',
+ submittedByName: 'XenoArch',
+ createdAt: '2026-01-28T14:00:00Z',
+ updatedAt: '2026-01-28T14:00:00Z',
+ notes: [],
+ meTooBugs: ['u3'],
+ },
+ {
+ id: 'bug3',
+ uniqueCode: 'HH-0003',
+ title: 'Game crashes on startup — DirectX 12 error',
+ description:
+ 'Game fails to launch with DXGI_ERROR_DEVICE_REMOVED on RTX 4080 with specific Nvidia driver versions (566.x series).',
+ stepsToReproduce:
+ '1. Install Nvidia driver 566.03 or higher\n2. Attempt to launch Headless Hazard\n3. Game shows splash screen then crashes\n4. Windows Event Viewer shows DXGI_ERROR_DEVICE_REMOVED',
+ severity: 'critical',
+ gameVersion: '0.9.3-alpha',
+ status: 'in_progress',
+ submittedById: 'u3',
+ submittedByName: 'GlitchHunter',
+ assignedToId: 'u1',
+ assignedToName: 'Kestrel',
+ createdAt: '2026-02-10T14:20:00Z',
+ updatedAt: '2026-02-11T11:00:00Z',
+ notes: [MOCK_BUG_NOTES[1]],
+ meTooBugs: ['u4', 'u5', 'u7', 'u2'],
+ },
+ {
+ id: 'bug4',
+ uniqueCode: 'HH-0004',
+ title: 'Girl NPC gets stuck in T-pose near elevator door',
+ description:
+ 'The girl character enters a T-pose animation state when the elevator door closes while she is within 1 meter of the door. She remains stuck until the player rolls the head near her.',
+ stepsToReproduce:
+ '1. Floor 1, elevator B\n2. Position the girl next to the closed elevator door\n3. Call the elevator\n4. As doors close, T-pose triggers',
+ severity: 'low',
+ gameVersion: '0.9.2-alpha',
+ status: 'resolved',
+ submittedById: 'u4',
+ submittedByName: 'NullPointer',
+ assignedToId: 'u8',
+ assignedToName: 'ByteWitch',
+ createdAt: '2025-12-20T10:00:00Z',
+ updatedAt: '2026-01-05T16:00:00Z',
+ notes: [],
+ meTooBugs: [],
+ },
+ {
+ id: 'bug5',
+ uniqueCode: 'HH-0005',
+ title: 'Save corruption when quitting during cutscene',
+ description:
+ 'Alt+F4 or force-quitting during any cutscene corrupts the save file. The save file becomes unreadable and the game starts fresh on next launch.',
+ stepsToReproduce:
+ '1. Trigger any in-game cutscene\n2. While the cutscene is playing, alt+F4 the game\n3. Relaunch the game\n4. Game shows "Save file corrupted" and resets progress',
+ severity: 'critical',
+ gameVersion: '0.9.3-alpha',
+ status: 'open',
+ submittedById: 'u7',
+ submittedByName: 'NeonCrawler',
+ createdAt: '2026-02-14T19:30:00Z',
+ updatedAt: '2026-02-14T19:30:00Z',
+ notes: [],
+ meTooBugs: ['u3', 'u4', 'u8'],
+ },
+ {
+ id: 'bug6',
+ uniqueCode: 'HH-0006',
+ title: 'Head-camera view flickers when standing near florescent lights',
+ description:
+ 'Minor visual bug: when viewing through the head camera near certain florescent light fixtures, the camera feed flickers at approximately 60hz in an uncomfortable strobing pattern.',
+ stepsToReproduce:
+ '1. Floor 0 server room\n2. Position head under the overhead fluorescent tubes\n3. Open the head camera view\n4. Observe strobing flicker',
+ severity: 'low',
+ gameVersion: '0.9.3-alpha',
+ status: 'closed',
+ submittedById: 'u5',
+ submittedByName: 'XenoArch',
+ createdAt: '2026-01-10T11:00:00Z',
+ updatedAt: '2026-01-12T14:00:00Z',
+ notes: [],
+ meTooBugs: ['u4'],
+ },
+];
+
+// ── Mock Bug Comments ──────────────────────────────────────────────────────────
+
+export const MOCK_BUG_COMMENTS: BugComment[] = [
+ {
+ id: 'bc1',
+ bugReportId: 'bug1',
+ authorId: 'u5',
+ authorName: 'XenoArch',
+ content: 'Can confirm this happens to me too. Specifically when the head reaches full speed before the curve at the bottom.',
+ createdAt: '2026-01-16T10:00:00Z',
+ },
+ {
+ id: 'bc2',
+ bugReportId: 'bug1',
+ authorId: 'u4',
+ authorName: 'NullPointer',
+ content: 'Workaround: slow down before the bend. Tap the brake key twice. Annoying but it prevents the clip.',
+ createdAt: '2026-01-17T08:30:00Z',
+ },
+ {
+ id: 'bc3',
+ bugReportId: 'bug3',
+ authorId: 'u4',
+ authorName: 'NullPointer',
+ content: 'Downgrading to driver 565.90 fixed it for me. Not ideal but at least I can play.',
+ createdAt: '2026-02-11T14:00:00Z',
+ },
+ {
+ id: 'bc4',
+ bugReportId: 'bug3',
+ authorId: 'u7',
+ authorName: 'NeonCrawler',
+ content: 'The DX11 workaround mentioned in the internal note also works. Add -dx11 to launch options in Steam.',
+ createdAt: '2026-02-12T09:00:00Z',
+ },
+ {
+ id: 'bc5',
+ bugReportId: 'bug5',
+ authorId: 'u3',
+ authorName: 'GlitchHunter',
+ content: 'This wiped my 6-hour playthrough. Please prioritize this fix.',
+ createdAt: '2026-02-14T20:00:00Z',
+ },
+ {
+ id: 'bc6',
+ bugReportId: 'bug5',
+ authorId: 'u4',
+ authorName: 'NullPointer',
+ content: 'Same here. Lost floor 4 and 5 progress. The auto-save system really needs to not write mid-cutscene.',
+ createdAt: '2026-02-15T11:00:00Z',
+ },
+];
+
+// ── Mock Staff Feed ────────────────────────────────────────────────────────────
+
+export const MOCK_STAFF_POSTS: StaffPost[] = [
+ {
+ id: 'sp1',
+ authorId: 'u1',
+ authorName: 'Kestrel',
+ authorRole: 'dev',
+ content: 'Physics patch is ready for internal testing. ByteWitch, can you check the head-ramp collision fix before EOD?',
+ createdAt: '2026-02-18T08:30:00Z',
+ },
+ {
+ id: 'sp2',
+ authorId: 'u2',
+ authorName: 'Vesper',
+ authorRole: 'com',
+ content: 'Social media post about the co-op update went live. Already 400 likes in 2 hours. The community is really excited.',
+ createdAt: '2026-02-18T09:15:00Z',
+ },
+ {
+ id: 'sp3',
+ authorId: 'u8',
+ authorName: 'ByteWitch',
+ authorRole: 'dev',
+ content: 'Checked the ramp fix — collision normals are now correct. Still seeing minor jitter at the bottom but nothing game-breaking. Marking as good to merge.',
+ createdAt: '2026-02-18T11:00:00Z',
+ },
+ {
+ id: 'sp4',
+ authorId: 'u1',
+ authorName: 'Kestrel',
+ authorRole: 'dev',
+ content: 'Merging physics fix. Will bundle with the DirectX hotfix into patch 0.9.4. Target: Monday release.',
+ createdAt: '2026-02-18T11:45:00Z',
+ },
+ {
+ id: 'sp5',
+ authorId: 'u2',
+ authorName: 'Vesper',
+ authorRole: 'com',
+ content: 'Reminder: community AMA is scheduled for Wednesday 7pm UTC. I\'ll be handling questions, feel free to DM me answers for anything technical.',
+ createdAt: '2026-02-17T16:00:00Z',
+ },
+ {
+ id: 'sp6',
+ authorId: 'u8',
+ authorName: 'ByteWitch',
+ authorRole: 'dev',
+ content: 'Save corruption bug (HH-0005) root cause identified: file handle wasn\'t being closed before force-quit. Simple fix, will be in next patch.',
+ createdAt: '2026-02-15T14:30:00Z',
+ },
+];
+
+// ── Mock Events & Polls ────────────────────────────────────────────────────────
+
+export const MOCK_POLLS: Poll[] = [
+ {
+ id: 'poll1',
+ eventId: 'evt2',
+ question: 'Which feature should we prioritize for the next major update?',
+ options: [
+ { id: 'opt1', text: 'New multiplayer maps', votes: 42, votedUserIds: ['u3', 'u4', 'u5', 'u7'] },
+ { id: 'opt2', text: 'Co-op campaign mode', votes: 78, votedUserIds: ['u1', 'u2', 'u8'] },
+ { id: 'opt3', text: 'Advanced physics system', votes: 35, votedUserIds: [] },
+ { id: 'opt4', text: 'Character customization', votes: 51, votedUserIds: [] },
+ ],
+ isActive: true,
+ endsAt: '2026-02-28T23:59:59Z',
+ allowMultipleVotes: false,
+ createdAt: '2026-02-16T10:00:00Z',
+ },
+ {
+ id: 'poll2',
+ eventId: 'evt5',
+ question: 'What type of content would you like to see more of in our devlogs?',
+ options: [
+ { id: 'opt5', text: 'Behind-the-scenes coding', votes: 23, votedUserIds: [] },
+ { id: 'opt6', text: 'Art process & concept art', votes: 45, votedUserIds: [] },
+ { id: 'opt7', text: 'Level design breakdown', votes: 18, votedUserIds: [] },
+ { id: 'opt8', text: 'Bug fix explanations', votes: 12, votedUserIds: [] },
+ ],
+ isActive: false,
+ endsAt: '2026-02-10T23:59:59Z',
+ allowMultipleVotes: true,
+ createdAt: '2026-02-01T09:00:00Z',
+ },
+];
+
+export const MOCK_EVENTS: EventPost[] = [
+ {
+ id: 'evt1',
+ type: 'milestone',
+ title: 'Version 0.9.5 Released!',
+ content: 'We are excited to announce version 0.9.5 is now live! This update includes major performance improvements, the new "Factory District" map, and over 30 bug fixes. Check the changelog for full details. Thank you to everyone who participated in testing!',
+ authorId: 'u1',
+ authorName: 'Kestrel',
+ authorRole: 'dev',
+ createdAt: '2026-02-17T14:00:00Z',
+ isPublic: true,
+ },
+ {
+ id: 'evt2',
+ type: 'poll',
+ title: 'Community Poll: Next Feature Priority',
+ content: 'Help us decide what to work on next! We want to hear from you about which feature would enhance your experience the most. Vote below and feel free to discuss in the forum.',
+ authorId: 'u2',
+ authorName: 'Vesper',
+ authorRole: 'com',
+ createdAt: '2026-02-16T10:00:00Z',
+ isPublic: true,
+ pollId: 'poll1',
+ },
+ {
+ id: 'evt3',
+ type: 'announcement',
+ title: 'Server Maintenance Scheduled',
+ content: 'We will be performing server maintenance on February 20th from 2:00 AM to 6:00 AM UTC. Multiplayer services will be unavailable during this time. Single-player mode will remain accessible. We apologize for any inconvenience!',
+ authorId: 'u8',
+ authorName: 'ByteWitch',
+ authorRole: 'dev',
+ createdAt: '2026-02-15T16:30:00Z',
+ isPublic: true,
+ },
+ {
+ id: 'evt4',
+ type: 'update',
+ title: 'Co-op Mode Development Progress',
+ content: 'Quick update on co-op mode development: networking code is 80% complete, and we\'ve successfully tested 4-player sessions internally. Still working on some sync issues with physics objects, but overall progress is excellent. Aiming for beta testing in March!',
+ authorId: 'u1',
+ authorName: 'Kestrel',
+ authorRole: 'dev',
+ createdAt: '2026-02-14T11:20:00Z',
+ isPublic: true,
+ },
+ {
+ id: 'evt5',
+ type: 'poll',
+ title: 'Devlog Content Poll',
+ content: 'We want to make our devlogs more interesting for you! Let us know what kind of behind-the-scenes content you\'d like to see more of. You can vote for multiple options.',
+ authorId: 'u2',
+ authorName: 'Vesper',
+ authorRole: 'com',
+ createdAt: '2026-02-01T09:00:00Z',
+ isPublic: true,
+ pollId: 'poll2',
+ },
+ {
+ id: 'evt6',
+ type: 'announcement',
+ title: 'Community AMA This Wednesday',
+ content: 'Join us for a live AMA (Ask Me Anything) session this Wednesday at 7:00 PM UTC! The dev team will be answering questions about the game, upcoming features, and the development process. Post your questions in the forum thread beforehand or ask live during the session.',
+ authorId: 'u2',
+ authorName: 'Vesper',
+ authorRole: 'com',
+ createdAt: '2026-02-12T14:00:00Z',
+ isPublic: true,
+ },
+ {
+ id: 'evt7',
+ type: 'update',
+ title: 'New Character Model Work in Progress',
+ content: 'Our art team has been working on updated character models with more detailed textures while maintaining the retro aesthetic. Early tests look fantastic! We\'ll share some screenshots next week. This won\'t affect performance - we\'re being very careful about optimization.',
+ authorId: 'u1',
+ authorName: 'Kestrel',
+ authorRole: 'dev',
+ createdAt: '2026-02-08T10:15:00Z',
+ isPublic: true,
+ },
+];
+
+// ── Team Members ───────────────────────────────────────────────────────────────
+
+export const TEAM_MEMBERS: TeamMember[] = [
+ {
+ id: 'tm1',
+ name: 'Alexei Voronov',
+ role: 'Studio Director & Lead Developer',
+ bio: 'Former AAA engine programmer turned indie. Obsessed with physics simulations and 80s science fiction. The brains behind the detached head mechanic.',
+ avatarInitials: 'AV',
+ social: { twitter: '@alexei_dev', github: 'alexei-v' },
+ },
+ {
+ id: 'tm2',
+ name: 'Sadie Mercier',
+ role: 'Lead Artist & Art Director',
+ bio: 'Pixel art veteran and VHS enthusiast. Responsible for the retro-futuristic visual identity of Headless Hazard. Also makes incredible cheese.',
+ avatarInitials: 'SM',
+ social: { twitter: '@sadie_pixels' },
+ },
+ {
+ id: 'tm3',
+ name: 'Rio Tanaka',
+ role: 'Game Designer & Narrative Lead',
+ bio: 'Wrote the full lore bible for the Headless Hazard universe, including 300 pages that will never see daylight. Loves bureaucratic dystopias.',
+ avatarInitials: 'RT',
+ social: { twitter: '@rio_writes', github: 'rio-tanaka' },
+ },
+ {
+ id: 'tm4',
+ name: 'Misha Devereux',
+ role: 'Sound Designer & Composer',
+ bio: 'Creates audio using a mix of synthesizers, field recordings in abandoned factories, and heavily processed VHS tapes. The soundscape of HH is entirely his.',
+ avatarInitials: 'MD',
+ social: { twitter: '@misha_sounds' },
+ },
+ {
+ id: 'tm5',
+ name: 'Priya Anand',
+ role: 'Backend & Infrastructure Engineer',
+ bio: 'Keeps the multiplayer servers alive and the databases sane. Dark mode absolutist. Currently obsessed with Rust.',
+ avatarInitials: 'PA',
+ social: { github: 'priya-anand-dev' },
+ },
+ {
+ id: 'tm6',
+ name: 'Camille Dupont',
+ role: 'Community Manager & QA Lead',
+ bio: 'The bridge between the studio and the players. Has played through the full game 47 times for QA purposes and still finds it fun somehow.',
+ avatarInitials: 'CD',
+ social: { twitter: '@camille_crow' },
+ },
+];
diff --git a/nest-front/src/index.css b/nest-front/src/index.css
new file mode 100644
index 0000000..d343e8f
--- /dev/null
+++ b/nest-front/src/index.css
@@ -0,0 +1,269 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
+@import "tailwindcss";
+
+/* ── Design Tokens — White Theme ─────────────────── */
+:root {
+ --color-bg: #ffffff;
+ --color-bg-alt: #f8f9fa;
+ --color-surface: #ffffff;
+ --color-surface-alt: #f1f3f5;
+ --color-border: #dee2e6;
+ --color-border-dim: #e9ecef;
+
+ /* Primary accent: blue */
+ --color-yellow: #2563eb;
+ --color-yellow-dim: #1d4ed8;
+
+ /* Secondary accent: dark gray */
+ --color-cyan: #374151;
+ --color-cyan-dim: #4b5563;
+
+ /* Tertiary: purple */
+ --color-magenta: #7c3aed;
+ --color-magenta-dim: #6d28d9;
+
+ /* Red for errors/danger */
+ --color-red: #dc2626;
+
+ /* Text */
+ --color-text: #1f2937;
+ --color-text-dim: #4b5563;
+ --color-text-muted: #9ca3af;
+
+ /* Legacy aliases kept so existing pages compile without changes */
+ --color-green: #059669;
+ --color-green-dim: #047857;
+ --color-amber: #d97706;
+ --color-amber-dim: #b45309;
+
+ --font-mono: 'JetBrains Mono', 'Courier New', monospace;
+ --font-heading: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+}
+
+/* ── Base ──────────────────────────────────────────────── */
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ margin: 0;
+ background-color: var(--color-bg);
+ color: var(--color-text);
+ font-family: var(--font-mono);
+ font-size: 17px;
+ min-height: 100vh;
+ overflow-x: hidden;
+ line-height: 1.7;
+ display: block;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+#root {
+ min-height: 100vh;
+}
+
+/* ── Scrollbar ─────────────────────────────────────────── */
+::-webkit-scrollbar { width: 8px; }
+::-webkit-scrollbar-track { background: var(--color-bg-alt); }
+::-webkit-scrollbar-thumb { background: var(--color-border); border-radius: 4px; }
+::-webkit-scrollbar-thumb:hover { background: var(--color-cyan); }
+
+/* ── Typography ────────────────────────────────────────── */
+h1, h2, h3, h4, h5 {
+ font-family: var(--font-heading);
+ font-weight: 600;
+ letter-spacing: -0.02em;
+ margin: 0;
+}
+
+a {
+ color: var(--color-yellow);
+ text-decoration: underline;
+ transition: color 0.1s;
+}
+a:hover { color: var(--color-yellow-dim); }
+
+/* ── Content box ─────────────────────────────────────── */
+.crt-box {
+ border: 1px solid var(--color-border);
+ background: var(--color-surface);
+ position: relative;
+ overflow: hidden;
+ border-radius: 8px;
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
+}
+
+/* ── Scanlines — kept as no-op class (Minitel: no effect) */
+.scanlines { position: relative; }
+.scanlines::after { display: none; }
+
+/* ── VHS grain — removed (no-op) ──────────────────────── */
+.vhs-grain { position: relative; }
+.vhs-grain::before { display: none; }
+
+/* ── Glitch text — replaced by simple color shift ──────── */
+.glitch-text { position: relative; }
+.glitch-text::before,
+.glitch-text::after { display: none; }
+
+/* ── Redacted — blocked text ─── */
+.redacted {
+ display: inline-block;
+ color: #6b7280;
+ background: #6b7280;
+ font-family: var(--font-mono);
+ padding: 0 2px;
+ user-select: none;
+ cursor: default;
+ letter-spacing: 0;
+ border: 1px solid #6b7280;
+ transition: background 0.1s, color 0.1s;
+}
+.redacted:hover {
+ background: var(--color-red);
+ border-color: var(--color-red);
+ color: var(--color-red);
+}
+
+/* ── Color classes ── */
+.glow-green { color: var(--color-green); }
+.glow-amber { color: var(--color-amber); }
+.flicker {}
+
+/* ── Buttons — clean white theme style ───────────────── */
+.btn-terminal {
+ font-family: var(--font-mono);
+ background: var(--color-bg);
+ border: 2px solid var(--color-yellow);
+ color: var(--color-yellow);
+ padding: 0.6rem 1.3rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ cursor: pointer;
+ transition: background 0.15s, color 0.15s;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 1rem;
+ border-radius: 6px;
+ text-decoration: none;
+ font-weight: 500;
+}
+.btn-terminal::before { display: none; }
+.btn-terminal:hover {
+ background: var(--color-yellow);
+ color: #ffffff;
+}
+
+.btn-amber {
+ border-color: var(--color-amber);
+ color: var(--color-amber);
+}
+.btn-amber:hover {
+ background: var(--color-amber);
+ color: #ffffff;
+}
+
+.btn-danger {
+ border-color: var(--color-red);
+ color: var(--color-red);
+}
+.btn-danger:hover {
+ background: var(--color-red);
+ color: #fff;
+}
+
+/* ── Form inputs — clean style ──────────────────── */
+.input-terminal {
+ background: var(--color-bg);
+ border: 2px solid var(--color-border);
+ border-radius: 6px;
+ color: var(--color-text);
+ font-family: var(--font-mono);
+ font-size: 1.05rem;
+ padding: 0.6rem 0.9rem;
+ width: 100%;
+ transition: border-color 0.15s, box-shadow 0.15s;
+ outline: none;
+ -webkit-appearance: none;
+ appearance: none;
+}
+.input-terminal::placeholder {
+ color: var(--color-text-muted);
+}
+.input-terminal:focus {
+ border-color: var(--color-yellow);
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+}
+.input-terminal.error {
+ border-color: var(--color-red);
+}
+
+/* ── Section label ─────────────────────────────────────── */
+.section-label {
+ font-family: var(--font-mono);
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: var(--color-text-dim);
+ text-transform: uppercase;
+ letter-spacing: 0.15em;
+ margin-bottom: 0.5rem;
+}
+
+/* ── Page transition ───────────────────────────────────── */
+@keyframes page-enter {
+ from { opacity: 0; transform: translateY(6px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+.page-enter {
+ animation: page-enter 0.2s ease forwards;
+}
+
+/* ── Status badges — clean style ──────────────────────── */
+.badge {
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ padding: 0.2rem 0.5rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ border-radius: 4px;
+ display: inline-block;
+ white-space: nowrap;
+ border: 1px solid;
+}
+.badge-open { background: #d1fae5; color: #065f46; border-color: #059669; }
+.badge-progress { background: #fef3c7; color: #92400e; border-color: #d97706; }
+.badge-resolved { background: #dbeafe; color: #1e40af; border-color: #2563eb; }
+.badge-closed { background: #f3f4f6; color: #6b7280; border-color: #9ca3af; }
+.badge-critical { background: #fee2e2; color: #991b1b; border-color: #dc2626; }
+.badge-high { background: #fed7aa; color: #9a3412; border-color: #ea580c; }
+.badge-medium { background: #fef3c7; color: #92400e; border-color: #d97706; }
+.badge-low { background: #e0e7ff; color: #3730a3; border-color: #6366f1; }
+
+/* ── Blink cursor ──────────────────────────────────────── */
+@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
+.cursor-blink::after {
+ content: '_';
+ animation: blink 1s step-end infinite;
+ color: var(--color-text-dim);
+}
+
+/* ── Horizontal rule ───────────────────────────── */
+.minitel-rule {
+ border: none;
+ border-top: 1px solid var(--color-border);
+ margin: 2rem 0;
+}
+
+/* ── Block decoration ──────────────────────────── */
+.block-deco {
+ color: var(--color-border);
+ font-family: var(--font-mono);
+ letter-spacing: -2px;
+ user-select: none;
+}
diff --git a/nest-front/src/main.tsx b/nest-front/src/main.tsx
new file mode 100644
index 0000000..7b1e504
--- /dev/null
+++ b/nest-front/src/main.tsx
@@ -0,0 +1,16 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+import './index.css';
+import App from './App.tsx';
+
+const rootElement = document.getElementById('root');
+if (!rootElement) throw new Error('Root element not found');
+
+createRoot(rootElement).render(
+
+
+
+
+
+);
diff --git a/nest-front/src/pages/public/AccountPage.tsx b/nest-front/src/pages/public/AccountPage.tsx
new file mode 100644
index 0000000..6f89be0
--- /dev/null
+++ b/nest-front/src/pages/public/AccountPage.tsx
@@ -0,0 +1,248 @@
+import { useState, useCallback } from 'react';
+import { useAuth } from '../../contexts/AuthContext';
+import { MOCK_THREADS, MOCK_BUGS } from '../../data/mockData';
+import { formatDate } from '../../utils/format';
+import { Link } from 'react-router-dom';
+
+type Tab = 'profile' | 'threads' | 'bugs' | 'password';
+
+export default function AccountPage() {
+ const { user, updateUsername } = useAuth();
+ const [activeTab, setActiveTab] = useState('profile');
+
+ const userThreads = MOCK_THREADS.filter((t) => t.authorId === user?.id);
+ const userBugs = MOCK_BUGS.filter((b) => b.submittedById === user?.id);
+
+ const tabs: { id: Tab; label: string }[] = [
+ { id: 'profile', label: 'Profile' },
+ { id: 'threads', label: `Threads (${userThreads.length})` },
+ { id: 'bugs', label: `Bug Reports (${userBugs.length})` },
+ { id: 'password', label: 'Change Password' },
+ ];
+
+ if (!user) return null;
+
+ return (
+
+
My Account
+
+ {user.username}
+
+
+ {/* Tabs */}
+
+ {tabs.map(({ id, label }) => (
+ setActiveTab(id)}
+ style={{
+ background: 'transparent',
+ border: 'none',
+ borderBottom: activeTab === id ? '2px solid var(--color-green)' : '2px solid transparent',
+ color: activeTab === id ? 'var(--color-green)' : 'var(--color-text-muted)',
+ fontFamily: 'var(--font-mono)',
+ fontSize: '0.78rem',
+ padding: '0.6rem 1rem',
+ cursor: 'pointer',
+ letterSpacing: '0.05em',
+ textTransform: 'uppercase',
+ transition: 'all 0.2s',
+ }}
+ >
+ {label}
+
+ ))}
+
+
+ {/* Profile Tab */}
+ {activeTab === 'profile' && (
+
+ )}
+
+ {/* Threads Tab */}
+ {activeTab === 'threads' && (
+
+ {userThreads.length === 0 ? (
+
+ You haven't posted any threads yet.{' '}
+ Go to Forum
+
+ ) : (
+ userThreads.map((t) => (
+
+
+
+ {t.title}
+
+
+ {formatDate(t.createdAt)}
+
+
+
+ {t.categoryName} — {t.replyCount} replies
+
+
+ ))
+ )}
+
+ )}
+
+ {/* Bug Reports Tab */}
+ {activeTab === 'bugs' && (
+
+ {userBugs.length === 0 ? (
+
+ You haven't submitted any bug reports.{' '}
+ Report a Bug
+
+ ) : (
+ userBugs.map((b) => (
+
+
+ {b.title}
+ {formatDate(b.createdAt)}
+
+
+ {b.uniqueCode}
+ {b.status}
+ {b.severity}
+
+
+ ))
+ )}
+
+ )}
+
+ {/* Password Tab */}
+ {activeTab === 'password' &&
}
+
+ );
+}
+
+// ── Profile Tab ────────────────────────────────────────────────────────────────
+
+function ProfileTab({ user, updateUsername }: { user: NonNullable['user']>; updateUsername: (u: string) => void }) {
+ const [editing, setEditing] = useState(false);
+ const [username, setUsername] = useState(user.username);
+ const [error, setError] = useState('');
+ const [saved, setSaved] = useState(false);
+
+ const handleSave = useCallback(() => {
+ if (!username.trim()) { setError('Username cannot be empty.'); return; }
+ if (username.length < 3) { setError('Must be at least 3 characters.'); return; }
+ updateUsername(username.trim());
+ setEditing(false);
+ setSaved(true);
+ setTimeout(() => setSaved(false), 3000);
+ }, [username, updateUsername]);
+
+ return (
+
+ {saved && (
+
+ [OK] Username updated successfully.
+
+ )}
+
+
+ {/* Username */}
+
+
USERNAME
+ {editing ? (
+
+ { setUsername(e.target.value); setError(''); }}
+ style={{ flex: 1 }}
+ autoFocus
+ />
+ Save
+ { setEditing(false); setUsername(user.username); setError(''); }} style={{ padding: '0.35rem 0.75rem', fontSize: '0.75rem' }}>Cancel
+
+ ) : (
+
+ {user.username}
+ setEditing(true)}
+ style={{ padding: '0.2rem 0.6rem', fontSize: '0.65rem' }}
+ >
+ Edit
+
+
+ )}
+
+ {error &&
{error}
}
+
+ {/* Static fields */}
+ {[
+ { label: 'EMAIL', value: user.email },
+ { label: 'ROLE', value: user.role.toUpperCase() },
+ { label: 'MEMBER SINCE', value: formatDate(user.createdAt) },
+ { label: 'ADMIN', value: user.isAdmin ? 'Yes' : 'No' },
+ ].map(({ label, value }) => (
+
+ {label}
+ {value}
+
+ ))}
+
+
+ );
+}
+
+// ── Change Password Form ───────────────────────────────────────────────────────
+
+function ChangePasswordForm() {
+ const [form, setForm] = useState({ current: '', next: '', confirm: '' });
+ const [errors, setErrors] = useState>({});
+ const [loading, setLoading] = useState(false);
+
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
+ e.preventDefault();
+ const next: typeof errors = {};
+ if (!form.current) next.current = 'Current password required.';
+ if (!form.next) next.next = 'New password required.';
+ else if (form.next.length < 8) next.next = 'Must be at least 8 characters.';
+ if (form.next !== form.confirm) next.confirm = 'Passwords do not match.';
+ setErrors(next);
+ if (Object.keys(next).length > 0) return;
+
+ setLoading(true);
+ await new Promise((r) => setTimeout(r, 400));
+ setLoading(false);
+ setForm({ current: '', next: '', confirm: '' });
+ setErrors({ success: 'Password changed successfully.' });
+ }, [form]);
+
+ return (
+
+ );
+}
diff --git a/nest-front/src/pages/public/BugDetailPage.tsx b/nest-front/src/pages/public/BugDetailPage.tsx
new file mode 100644
index 0000000..96417dc
--- /dev/null
+++ b/nest-front/src/pages/public/BugDetailPage.tsx
@@ -0,0 +1,347 @@
+import { useState, useCallback, useMemo } from 'react';
+import { Link, Navigate, useParams } from 'react-router-dom';
+import { MOCK_BUGS, MOCK_BUG_COMMENTS } from '../../data/mockData';
+import { useAuth } from '../../contexts/AuthContext';
+import { formatDate, formatDateTime } from '../../utils/format';
+import type { BugComment, BugReport, BugSeverity, BugStatus } from '../../types';
+
+// ── Helpers ────────────────────────────────────────────────────────────────────
+
+function StatusBadge({ status }: { status: BugStatus }) {
+ const map: Record = {
+ open: 'badge-open', in_progress: 'badge-progress', resolved: 'badge-resolved', closed: 'badge-closed',
+ };
+ const labels: Record = {
+ open: 'Open', in_progress: 'In Progress', resolved: 'Resolved', closed: 'Closed',
+ };
+ return {labels[status]} ;
+}
+
+function SeverityBadge({ severity }: { severity: BugSeverity }) {
+ const map: Record = {
+ low: 'badge-low', medium: 'badge-medium', high: 'badge-high', critical: 'badge-critical',
+ };
+ return {severity} ;
+}
+
+// ── Comment component ──────────────────────────────────────────────────────────
+
+function CommentItem({ comment }: { comment: BugComment }) {
+ return (
+
+
+
+ {comment.authorName}
+
+
+ {formatDateTime(comment.createdAt)}
+
+
+
+ {comment.content}
+
+
+ );
+}
+
+// ── Bug Detail Page ────────────────────────────────────────────────────────────
+
+export default function BugDetailPage() {
+ const { id } = useParams<{ id: string }>();
+ const { user, isAuthenticated } = useAuth();
+
+ // Local state — mirrors the global bug list in memory
+ const [bugs, setBugs] = useState(MOCK_BUGS);
+ const [comments, setComments] = useState(MOCK_BUG_COMMENTS);
+ const [newComment, setNewComment] = useState('');
+ const [commentError, setCommentError] = useState('');
+ const [submitting, setSubmitting] = useState(false);
+
+ const bug = useMemo(() => bugs.find((b) => b.id === id), [bugs, id]);
+
+ const bugComments = useMemo(
+ () => comments.filter((c) => c.bugReportId === id).sort((a, b) => a.createdAt.localeCompare(b.createdAt)),
+ [comments, id]
+ );
+
+ // "I have this too" logic
+ const alreadyVoted = useMemo(
+ () => !!user && !!bug && bug.meTooBugs.includes(user.id),
+ [user, bug]
+ );
+ const isOwnReport = useMemo(
+ () => !!user && !!bug && bug.submittedById === user.id,
+ [user, bug]
+ );
+
+ const handleMeToo = useCallback(() => {
+ if (!user || !bug || alreadyVoted || isOwnReport) return;
+ setBugs((prev) =>
+ prev.map((b) =>
+ b.id === bug.id ? { ...b, meTooBugs: [...b.meTooBugs, user.id] } : b
+ )
+ );
+ }, [user, bug, alreadyVoted, isOwnReport]);
+
+ const handleComment = useCallback(async () => {
+ if (!user) return;
+ if (!newComment.trim()) { setCommentError('Comment cannot be empty.'); return; }
+ if (newComment.trim().length < 5) { setCommentError('Comment must be at least 5 characters.'); return; }
+
+ setCommentError('');
+ setSubmitting(true);
+ await new Promise((r) => setTimeout(r, 250));
+
+ const comment: BugComment = {
+ id: `bc${Date.now()}`,
+ bugReportId: id!,
+ authorId: user.id,
+ authorName: user.username,
+ content: newComment.trim(),
+ createdAt: new Date().toISOString(),
+ };
+ setComments((prev) => [...prev, comment]);
+ setNewComment('');
+ setSubmitting(false);
+ }, [user, newComment, id]);
+
+ if (!bug) {
+ return ;
+ }
+
+ const metooCount = bug.meTooBugs.length;
+
+ return (
+
+ {/* Breadcrumb */}
+
+ Bug Reports
+ {' '}>{' '}
+ {bug.uniqueCode}
+
+
+ {/* Header */}
+
+ {/* Badges */}
+
+
+ {bug.uniqueCode}
+
+
+
+
+
+ {/* Title */}
+
+ {bug.title}
+
+
+ {/* Meta grid */}
+
+ {[
+ { label: 'Submitted by', value: bug.submittedByName },
+ { label: 'Date', value: formatDate(bug.createdAt) },
+ { label: 'Game Version', value: `v${bug.gameVersion}` },
+ { label: 'Assigned to', value: bug.assignedToName ?? 'Unassigned' },
+ ].map(({ label, value }) => (
+
+
+ {label}
+
+
{value}
+
+ ))}
+
+
+ {/* Description */}
+
+
Description
+
+ {bug.description}
+
+
+
+ {/* Steps to reproduce */}
+
+
Steps to Reproduce
+
+ {bug.stepsToReproduce}
+
+
+
+ {/* "I have this too" section */}
+
+ {/* Count */}
+
+ {metooCount} {' '}
+ {metooCount === 1 ? 'user has' : 'users have'} this issue
+
+
+ {/* Button logic */}
+ {!isAuthenticated ? (
+
+ Login to confirm you have this issue
+
+ ) : isOwnReport ? (
+
+ (this is your report)
+
+ ) : alreadyVoted ? (
+
+ ✓ You reported this too
+
+ ) : (
+
+ ▶ I have this too
+
+ )}
+
+
+
+ {/* Comments section */}
+
+
+
+ Discussion
+
+
+ {bugComments.length}
+
+
+
+ {/* Comment list */}
+ {bugComments.length === 0 ? (
+
+ No comments yet. Be the first to comment.
+
+ ) : (
+ bugComments.map((comment) => (
+
+ ))
+ )}
+
+ {/* Add comment */}
+
+ {isAuthenticated ? (
+
+ ) : (
+
+
+ You must be logged in to comment.
+
+
+ Login
+ Register
+
+
+ )}
+
+
+
+ );
+}
diff --git a/nest-front/src/pages/public/BugReportPage.tsx b/nest-front/src/pages/public/BugReportPage.tsx
new file mode 100644
index 0000000..79caba8
--- /dev/null
+++ b/nest-front/src/pages/public/BugReportPage.tsx
@@ -0,0 +1,440 @@
+import { useState, useMemo, useCallback } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { MOCK_BUGS } from '../../data/mockData';
+import { useAuth } from '../../contexts/AuthContext';
+import { timeAgo } from '../../utils/format';
+import type { BugReport, BugSeverity, BugStatus, BugReportFormData } from '../../types';
+
+// ── Helpers ────────────────────────────────────────────────────────────────────
+
+function StatusBadge({ status }: { status: BugStatus }) {
+ const map: Record = {
+ open: 'badge-open',
+ in_progress: 'badge-progress',
+ resolved: 'badge-resolved',
+ closed: 'badge-closed',
+ };
+ const labels: Record = {
+ open: 'Open', in_progress: 'In Progress', resolved: 'Resolved', closed: 'Closed',
+ };
+ return {labels[status]} ;
+}
+
+function SeverityBadge({ severity }: { severity: BugSeverity }) {
+ const map: Record = {
+ low: 'badge-low', medium: 'badge-medium', high: 'badge-high', critical: 'badge-critical',
+ };
+ return {severity} ;
+}
+
+// ── Bug List Card ──────────────────────────────────────────────────────────────
+
+interface BugCardProps {
+ bug: BugReport;
+ highlight?: boolean;
+}
+
+function BugCard({ bug, highlight }: BugCardProps) {
+ const navigate = useNavigate();
+
+ const handleClick = useCallback(() => {
+ navigate(`/bugs/${bug.id}`);
+ }, [bug.id, navigate]);
+
+ return (
+ e.key === 'Enter' && handleClick()}
+ role="button"
+ tabIndex={0}
+ aria-label={`View bug report ${bug.uniqueCode}: ${bug.title}`}
+ style={{
+ background: highlight ? 'rgba(255,255,0,0.04)' : 'var(--color-surface)',
+ border: `2px solid ${highlight ? 'var(--color-yellow)' : 'var(--color-border)'}`,
+ padding: '0.9rem 1.1rem',
+ cursor: 'pointer',
+ marginBottom: '0.5rem',
+ transition: 'border-color 0.1s, background 0.1s',
+ }}
+ >
+
+
+ {/* Badges row */}
+
+
+ {bug.uniqueCode}
+
+
+
+ {/* MeToo count */}
+
+ ▶ {bug.meTooBugs.length} {bug.meTooBugs.length === 1 ? 'user' : 'users'} have this
+
+
+
+ {/* Title */}
+
+ {bug.title}
+
+
+ {/* Meta */}
+
+ by {bug.submittedByName} — {timeAgo(bug.createdAt)} — v{bug.gameVersion}
+
+
+
+ {/* Arrow */}
+
+ VIEW >
+
+
+
+ );
+}
+
+// ── Submit Form ────────────────────────────────────────────────────────────────
+
+const GAME_VERSIONS = ['0.9.3-alpha', '0.9.2-alpha', '0.9.1-alpha'];
+const SEVERITIES: BugSeverity[] = ['low', 'medium', 'high', 'critical'];
+
+function SubmitBugForm({ onSubmit }: { onSubmit: (data: BugReportFormData) => void }) {
+ const [form, setForm] = useState({
+ title: '',
+ description: '',
+ stepsToReproduce: '',
+ severity: 'medium',
+ gameVersion: '0.9.3-alpha',
+ });
+ const [errors, setErrors] = useState>>({});
+ const [submitted, setSubmitted] = useState(false);
+
+ const set = useCallback((key: K, value: BugReportFormData[K]) => {
+ setForm((prev) => ({ ...prev, [key]: value }));
+ setErrors((prev) => ({ ...prev, [key]: undefined }));
+ }, []);
+
+ const validate = (): boolean => {
+ const next: typeof errors = {};
+ if (!form.title.trim()) next.title = 'Title is required.';
+ else if (form.title.length < 10) next.title = 'Title must be at least 10 characters.';
+ if (!form.description.trim()) next.description = 'Description is required.';
+ else if (form.description.length < 20) next.description = 'Description must be at least 20 characters.';
+ if (!form.stepsToReproduce.trim()) next.stepsToReproduce = 'Steps to reproduce are required.';
+ setErrors(next);
+ return Object.keys(next).length === 0;
+ };
+
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!validate()) return;
+ await new Promise((r) => setTimeout(r, 400));
+ onSubmit(form);
+ setSubmitted(true);
+ }, [form, onSubmit]);
+
+ const labelStyle: React.CSSProperties = {
+ display: 'block',
+ fontFamily: 'var(--font-mono)',
+ color: 'var(--color-text-muted)',
+ fontSize: '0.72rem',
+ letterSpacing: '0.1em',
+ marginBottom: '0.35rem',
+ textTransform: 'uppercase',
+ };
+
+ if (submitted) {
+ return (
+
+
+ [OK] Bug report submitted.
+
+
+ A unique code has been assigned. The team will review it shortly.
+
+
setSubmitted(false)}>
+ Submit Another
+
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+// ── Bug Report Page ────────────────────────────────────────────────────────────
+
+export default function BugReportPage() {
+ const { user, isAuthenticated } = useAuth();
+ const [bugs, setBugs] = useState(MOCK_BUGS);
+ const [statusFilter, setStatusFilter] = useState('all');
+ const [severityFilter, setSeverityFilter] = useState('all');
+ const [showForm, setShowForm] = useState(false);
+
+ // Separate: user's own bugs and all others, both filtered
+ const { myBugs, otherBugs } = useMemo(() => {
+ const passes = (b: BugReport) => {
+ if (statusFilter !== 'all' && b.status !== statusFilter) return false;
+ if (severityFilter !== 'all' && b.severity !== severityFilter) return false;
+ return true;
+ };
+ const my: BugReport[] = [];
+ const other: BugReport[] = [];
+ bugs.forEach((b) => {
+ if (!passes(b)) return;
+ if (user && b.submittedById === user.id) my.push(b);
+ else other.push(b);
+ });
+ return { myBugs: my, otherBugs: other };
+ }, [bugs, statusFilter, severityFilter, user]);
+
+ const handleNewReport = useCallback((data: BugReportFormData) => {
+ const newBug: BugReport = {
+ id: `bug${Date.now()}`,
+ uniqueCode: `HH-${String(bugs.length + 1).padStart(4, '0')}`,
+ ...data,
+ status: 'open',
+ submittedById: user?.id ?? 'unknown',
+ submittedByName: user?.username ?? 'You',
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ notes: [],
+ meTooBugs: [],
+ };
+ setBugs((prev) => [newBug, ...prev]);
+ setShowForm(false);
+ }, [bugs.length, user]);
+
+ const openCount = bugs.filter((b) => b.status === 'open').length;
+ const inProgressCount = bugs.filter((b) => b.status === 'in_progress').length;
+ const resolvedCount = bugs.filter((b) => b.status === 'resolved').length;
+
+ return (
+
+ {/* Header */}
+
+
+
Issue Tracker
+
+ BUG REPORTS
+
+
+ {openCount} open —
+ {inProgressCount} in progress —
+ {resolvedCount} resolved
+
+
+
+ {isAuthenticated ? (
+
setShowForm((v) => !v)}
+ >
+ {showForm ? 'Cancel' : '▶ Submit Bug'}
+
+ ) : (
+
+ Login to submit a report
+
+ )}
+
+
+ {/* Submit form */}
+ {showForm && isAuthenticated && (
+
+
+
+ )}
+
+ {/* Filters */}
+
+ setStatusFilter(e.target.value as BugStatus | 'all')}
+ aria-label="Filter by status"
+ >
+ All Statuses
+ Open
+ In Progress
+ Resolved
+ Closed
+
+
+ setSeverityFilter(e.target.value as BugSeverity | 'all')}
+ aria-label="Filter by severity"
+ >
+ All Severities
+ Critical
+ High
+ Medium
+ Low
+
+
+
+ {/* "Your Reports" section — only for logged-in users with their own bugs */}
+ {isAuthenticated && myBugs.length > 0 && (
+
+
+
+ ▶ Your Reports
+
+
+ {myBugs.length}
+
+
+ {myBugs.map((bug) => (
+
+ ))}
+
+ )}
+
+ {/* All other reports */}
+
+ {isAuthenticated && myBugs.length > 0 && (
+
+
+ All Reports
+
+
+ {otherBugs.length}
+
+
+ )}
+
+ {otherBugs.length === 0 && myBugs.length === 0 ? (
+
+ No bug reports match the selected filters.
+
+ ) : otherBugs.length === 0 && isAuthenticated ? (
+
+ No other reports match the selected filters.
+
+ ) : (
+ otherBugs.map((bug) => (
+
+ ))
+ )}
+
+
+ );
+}
diff --git a/nest-front/src/pages/public/EventsPage.tsx b/nest-front/src/pages/public/EventsPage.tsx
new file mode 100644
index 0000000..cd2ae91
--- /dev/null
+++ b/nest-front/src/pages/public/EventsPage.tsx
@@ -0,0 +1,366 @@
+import { useState, useCallback } from 'react';
+import { MOCK_EVENTS, MOCK_POLLS } from '../../data/mockData';
+import { useAuth } from '../../contexts/AuthContext';
+import { formatDateTime } from '../../utils/format';
+import type { EventPost, EventType, Poll, UserRole } from '../../types';
+
+const EVENT_TYPE_COLORS: Record = {
+ announcement: 'var(--color-yellow)',
+ update: 'var(--color-blue)',
+ milestone: 'var(--color-green)',
+ poll: 'var(--color-amber)',
+};
+
+const EVENT_TYPE_LABELS: Record = {
+ announcement: 'ANNOUNCEMENT',
+ update: 'DEV UPDATE',
+ milestone: 'MILESTONE',
+ poll: 'COMMUNITY POLL',
+};
+
+const ROLE_COLORS: Record = {
+ dev: 'var(--color-green)',
+ com: 'var(--color-amber)',
+ user: 'var(--color-text-muted)',
+};
+
+// ── Poll Component ─────────────────────────────────────────────────────────────
+
+function PollCard({ poll, onVote }: { poll: Poll; onVote: (pollId: string, optionId: string) => void }) {
+ const { user } = useAuth();
+ const totalVotes = poll.options.reduce((sum, opt) => sum + opt.votes, 0);
+ const isEnded = poll.endsAt ? new Date(poll.endsAt) < new Date() : false;
+
+ return (
+
+
+ {poll.question}
+
+
+ {poll.options.map((option) => {
+ const percentage = totalVotes > 0 ? Math.round((option.votes / totalVotes) * 100) : 0;
+ const userVoted = option.votedUserIds.includes(user?.id || '');
+
+ return (
+
{
+ if (!isEnded && poll.isActive && user) {
+ onVote(poll.id, option.id);
+ }
+ }}
+ >
+ {/* Progress bar */}
+
+
+
+ {userVoted && '✓ '}
+ {option.text}
+
+
+ {option.votes} ({percentage}%)
+
+
+
+ );
+ })}
+
+
+
+ {totalVotes} total vote{totalVotes !== 1 ? 's' : ''}
+ {poll.allowMultipleVotes && ' • Multiple votes allowed'}
+
+ {poll.endsAt && (
+
+ {isEnded ? 'Poll Ended' : `Ends ${formatDateTime(poll.endsAt)}`}
+
+ )}
+
+ {!user && !isEnded && poll.isActive && (
+
+ )}
+
+ );
+}
+
+// ── Event Card Component ───────────────────────────────────────────────────────
+
+function EventCard({
+ event,
+ poll,
+ onVote,
+}: {
+ event: EventPost;
+ poll?: Poll;
+ onVote: (pollId: string, optionId: string) => void;
+}) {
+ return (
+
+ {/* Header */}
+
+
+
+ {EVENT_TYPE_LABELS[event.type]}
+
+
+
+ {event.title}
+
+
+
+ {event.authorName}
+
+ •
+ {formatDateTime(event.createdAt)}
+
+
+
+ {/* Content */}
+
+ {event.content}
+
+
+ {/* Poll if exists */}
+ {poll &&
}
+
+ );
+}
+
+// ── Main Component ─────────────────────────────────────────────────────────────
+
+export default function EventsPage() {
+ const { user } = useAuth();
+ // Filter to show only public events
+ const publicEvents = MOCK_EVENTS.filter((event) => event.isPublic);
+ const [events] = useState(publicEvents);
+ const [polls, setPolls] = useState(MOCK_POLLS);
+
+ const handleVote = useCallback(
+ (pollId: string, optionId: string) => {
+ if (!user) return;
+
+ setPolls((prevPolls) =>
+ prevPolls.map((poll) => {
+ if (poll.id !== pollId) return poll;
+
+ const hasVotedForOption = poll.options.some((opt) =>
+ opt.votedUserIds.includes(user.id)
+ );
+
+ return {
+ ...poll,
+ options: poll.options.map((opt) => {
+ if (opt.id === optionId) {
+ // Add vote to this option
+ return {
+ ...opt,
+ votes: opt.votedUserIds.includes(user.id) ? opt.votes : opt.votes + 1,
+ votedUserIds: opt.votedUserIds.includes(user.id)
+ ? opt.votedUserIds
+ : [...opt.votedUserIds, user.id],
+ };
+ } else if (!poll.allowMultipleVotes && hasVotedForOption) {
+ // Remove vote from other options if single vote
+ return {
+ ...opt,
+ votes: opt.votedUserIds.includes(user.id) ? opt.votes - 1 : opt.votes,
+ votedUserIds: opt.votedUserIds.filter((id) => id !== user.id),
+ };
+ }
+ return opt;
+ }),
+ };
+ })
+ );
+ },
+ [user]
+ );
+
+ return (
+
+
+ {/* Header */}
+
+
+ DEVELOPMENT UPDATES
+
+
+ COMMUNITY EVENTS
+
+
+ Stay up to date with the latest game development news, announcements, and participate
+ in community polls to help shape the future of Headless Hazard.
+
+
+
+ {/* Events Grid */}
+
+ {events.length === 0 ? (
+
+
+ No events available at the moment. Check back soon!
+
+
+ ) : (
+ events.map((event) => {
+ const poll = event.pollId ? polls.find((p) => p.id === event.pollId) : undefined;
+ return
;
+ })
+ )}
+
+
+
+ );
+}
diff --git a/nest-front/src/pages/public/ForumPage.tsx b/nest-front/src/pages/public/ForumPage.tsx
new file mode 100644
index 0000000..9229038
--- /dev/null
+++ b/nest-front/src/pages/public/ForumPage.tsx
@@ -0,0 +1,188 @@
+import { useMemo, useState } from 'react';
+import { Link } from 'react-router-dom';
+import { MOCK_CATEGORIES, MOCK_THREADS } from '../../data/mockData';
+import { timeAgo } from '../../utils/format';
+import type { ForumCategory, ForumThread } from '../../types';
+
+// ── Sub-components ─────────────────────────────────────────────────────────────
+
+function CategoryCard({ category, threads }: { category: ForumCategory; threads: ForumThread[] }) {
+ const pinned = threads.filter((t) => t.isPinned && t.categoryId === category.id);
+ const regular = threads.filter((t) => !t.isPinned && t.categoryId === category.id);
+ const categoryThreads = [...pinned, ...regular];
+
+ return (
+
+ {/* Category header */}
+
+
+
+ {category.icon}
+
+
+
+ {category.name}
+
+
+ {category.description}
+
+
+
+
+ {category.threadCount} threads
+
+
+
+ {/* Threads */}
+
+ {categoryThreads.length === 0 ? (
+
+ No threads yet. Be the first to post.
+
+ ) : (
+ categoryThreads.map((thread, idx) => (
+
+
+
+ {thread.isPinned && (
+ Pinned
+ )}
+ {thread.isLocked && (
+ Locked
+ )}
+
+ {thread.title}
+
+
+
+ by {thread.authorName}
+ {' '}— {timeAgo(thread.createdAt)}
+
+
+
+
{thread.replyCount}
+
replies
+
+
+ ))
+ )}
+
+
+ );
+}
+
+// ── Forum Page ─────────────────────────────────────────────────────────────────
+
+export default function ForumPage() {
+ const [search, setSearch] = useState('');
+
+ const filteredCategories = useMemo(() => {
+ if (!search.trim()) return MOCK_CATEGORIES;
+ const q = search.toLowerCase();
+ return MOCK_CATEGORIES.filter((cat) =>
+ cat.name.toLowerCase().includes(q) ||
+ MOCK_THREADS.some((t) => t.categoryId === cat.id && t.title.toLowerCase().includes(q))
+ );
+ }, [search]);
+
+ return (
+
+ {/* Header */}
+
+
+
Community
+
+ FORUM
+
+
+ Read freely. Login to post.
+
+
+
+ {/* Search */}
+
+ setSearch(e.target.value)}
+ style={{ width: '220px' }}
+ aria-label="Search forum threads"
+ />
+
+
+
+ {/* Categories */}
+ {filteredCategories.length === 0 ? (
+
+ No results found for "{search}"
+
+ ) : (
+ filteredCategories.map((cat) => (
+
+ ))
+ )}
+
+ );
+}
diff --git a/nest-front/src/pages/public/HomePage.tsx b/nest-front/src/pages/public/HomePage.tsx
new file mode 100644
index 0000000..5fc93d4
--- /dev/null
+++ b/nest-front/src/pages/public/HomePage.tsx
@@ -0,0 +1,460 @@
+import { Link } from 'react-router-dom';
+
+// ── Sub-components ─────────────────────────────────────────────────────────────
+
+function Redacted({ children }: { children: string }) {
+ return (
+
+ {children}
+
+ );
+}
+
+function SectionDivider() {
+ return (
+
+ );
+}
+
+// ── Hero Section ───────────────────────────────────────────────────────────────
+
+function HeroSection() {
+ return (
+
+ {/* Subtle gradient background */}
+
+
+ {/* Grid lines — subtle pattern */}
+
+
+
+ {/* Pre-title */}
+
+ CrowMate Studio presents
+
+
+ {/* Game Title */}
+
+ HEADLESS HAZARD
+
+
+ {/* Subtitle */}
+
+ >> LOSE YOUR HEAD. KEEP YOUR BODY.
+
+
+ {/* Tagline */}
+
+ Navigate a sprawling underground complex. Control a detached robotic head.
+ Survive bureaucratic hell. Save the girl.
+
+ (or don't — the corporation doesn't care either way)
+
+
+
+ {/* CTA buttons */}
+
+
+ {/* Version tag */}
+
+ ALPHA v0.9.3 — EARLY ACCESS COMING SOON
+
+
+
+ );
+}
+
+// ── Lore Section ───────────────────────────────────────────────────────────────
+
+function LoreSection() {
+ return (
+
+
+
The World
+
+ SOMEWHERE UNDERGROUND
+
+
+
+
+ {/* Classification header */}
+
+ ■■■ CLASSIFIED — LEVEL 9 CLEARANCE REQUIRED ■■■
+
+
+
+
+ Deep beneath the surface, an underground megacomplex spans 47 floors of corridors, server rooms,
+ cafeterias, and security checkpoints — all operated by{' '}
+ AMALGAM INDUSTRIES CORP
+ {', '}a corporation so powerful it has legally removed its own name from public record.
+
+
+
+ UNIT-7 is a security enforcement robot. Standard model. Bipedal, armored,
+ designed to neutralize threats with efficiency and no questions asked.
+ There is one small problem: its head is no longer attached to its body.
+ This is, officially, a{' '}
+ NON-CRITICAL OPERATIONAL DEVIATION .
+ The head still works. The body still works. They just work... separately.
+
+
+
+ Then there is the girl. Eight years old. Lost. She wandered into the complex
+ through an unsecured maintenance hatch — and when she found the central computer,
+ she did what any eight-year-old would do:{' '}
+ she started pressing buttons .
+ All of them. At once. The terminal, she explained later, looked exactly like
+ an arcade cabinet. This triggered{' '}
+ PROTOCOL OMEGA — activating
+ every automated defense system, locking every door, and sealing every exit
+ in the facility.
+
+
+
+ The girl is currently located in Sector 12-C. She is eating from the emergency ration
+ storage and appears to be having the time of her life. UNIT-7 has been tasked with
+ extraction. Corporate does not know she exists. This is everyone's problem now.
+
+
+
+
+ );
+}
+
+// ── Gameplay Section ───────────────────────────────────────────────────────────
+
+function GameplaySection() {
+ const mechanics = [
+ {
+ icon: '[CAM]',
+ title: 'Head as Camera',
+ desc: 'Roll, bounce, and launch your detached head through vents and around corners. The head sees everything your body cannot.',
+ },
+ {
+ icon: '[BOT]',
+ title: 'Body Controls',
+ desc: 'Direct your headless body remotely. Lift, punch, carry, operate terminals — but it\'s blind. The head is its only eyes.',
+ },
+ {
+ icon: '[CO-OP]',
+ title: 'Multiplayer Chaos',
+ desc: 'One player controls the head, another the body. Communication is everything. Miscommunication is hilarious.',
+ },
+ {
+ icon: '[SLO]',
+ title: 'Solo Campaign',
+ desc: 'Switch between head and body control at will. 12 floors of escalating complexity, optional challenge rooms, and a full narrative.',
+ },
+ ];
+
+ return (
+
+
+
+
How It Works
+
+ GAMEPLAY MECHANICS
+
+
+ Second-person perspective puzzle-platformer with asymmetric co-op support.
+ Control the detached head as a camera drone. Direct the headless body remotely.
+
+
+
+
+ {mechanics.map(({ icon, title, desc }) => (
+
+
+ {icon}
+
+
+ {title}
+
+
+ {desc}
+
+
+ ))}
+
+
+
+ );
+}
+
+// ── Visual Style Section ───────────────────────────────────────────────────────
+
+function VisualStyleSection() {
+ const attributes = [
+ { label: 'Aesthetic', value: 'Retro-Futuristic / 1980s Megacorp' },
+ { label: 'Visual Effect', value: 'VHS Tape Artifacts + CRT Scanlines' },
+ { label: 'Color Palette', value: 'Terminal Green, Amber Warning, Void Black' },
+ { label: 'Typography', value: 'Monospace + Condensed Industrial' },
+ { label: 'Architecture', value: 'Brutalist Bunker + Corporate Bureaucracy' },
+ { label: 'Tone', value: 'Dark Comedy / Kafkaesque Horror' },
+ ];
+
+ return (
+
+
+
Aesthetic
+
+ THE VISUAL IDENTITY
+
+
+
+
+
+ {attributes.map(({ label, value }, i) => (
+
+
+ {label}
+
+
+ {value}
+
+
+ ))}
+
+
+
+ >
+ The world of Headless Hazard is a love letter to the aesthetic of late-80s science fiction.
+ Think corporate cafeterias lit by flickering fluorescent tubes. Think instruction manuals
+ written in Comic Sans translated from Japanese. Think a DANGER warning label on a door
+ that has been there so long nobody remembers what the danger was.
+
+
+
+ );
+}
+
+// ── Home Page ──────────────────────────────────────────────────────────────────
+
+export default function HomePage() {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/nest-front/src/pages/public/LoginPage.tsx b/nest-front/src/pages/public/LoginPage.tsx
new file mode 100644
index 0000000..9ca200b
--- /dev/null
+++ b/nest-front/src/pages/public/LoginPage.tsx
@@ -0,0 +1,151 @@
+import { useState, useCallback, useEffect } from 'react';
+import { Link, useNavigate, useLocation } from 'react-router-dom';
+import { useAuth } from '../../contexts/AuthContext';
+
+export default function LoginPage() {
+ const { login, isAuthenticated } = useAuth();
+ const navigate = useNavigate();
+ const location = useLocation();
+ const from = (location.state as { from?: Location })?.from?.pathname ?? '/';
+
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [errors, setErrors] = useState<{ email?: string; password?: string; form?: string }>({});
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (isAuthenticated) navigate(from, { replace: true });
+ }, [isAuthenticated, from, navigate]);
+
+ const validate = (): boolean => {
+ const next: typeof errors = {};
+ if (!email.trim()) next.email = 'Email is required.';
+ else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) next.email = 'Enter a valid email address.';
+ if (!password) next.password = 'Password is required.';
+ setErrors(next);
+ return Object.keys(next).length === 0;
+ };
+
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!validate()) return;
+ setLoading(true);
+ const result = await login(email, password);
+ setLoading(false);
+ if (!result.success) {
+ setErrors({ form: result.error });
+ }
+ }, [email, password, login]);
+
+ return (
+
+
+
+
Authentication
+
+ LOGIN
+
+
+ CROWMATE STUDIO / HEADLESS HAZARD COMMUNITY
+
+
+
+
+ {/* Demo hint */}
+
+
+ [DEMO] Quick login emails:
+
+ {[
+ { label: 'Dev/Admin', email: 'kestrel@crowmate.dev' },
+ { label: 'Com Staff', email: 'vesper@crowmate.dev' },
+ { label: 'User', email: 'glitch@mail.com' },
+ ].map(({ label, email: e }) => (
+
{ setEmail(e); setPassword('password'); }}
+ style={{
+ background: 'transparent',
+ border: 'none',
+ color: 'var(--color-text-muted)',
+ cursor: 'pointer',
+ fontFamily: 'var(--font-mono)',
+ fontSize: '0.65rem',
+ display: 'block',
+ textAlign: 'left',
+ padding: '0.1rem 0',
+ width: '100%',
+ }}
+ >
+ > {label}: {e}
+
+ ))}
+
+
+ {errors.form && (
+
+ [ERROR] {errors.form}
+
+ )}
+
+ {/* Email */}
+
+
+ Email Address
+
+
{ setEmail(e.target.value); setErrors((p) => ({ ...p, email: undefined })); }}
+ aria-describedby={errors.email ? 'email-error' : undefined}
+ />
+ {errors.email &&
{errors.email}
}
+
+
+ {/* Password */}
+
+
+ Password
+
+
{ setPassword(e.target.value); setErrors((p) => ({ ...p, password: undefined })); }}
+ aria-describedby={errors.password ? 'pass-error' : undefined}
+ />
+ {errors.password &&
{errors.password}
}
+
+
+
+ {loading ? 'Authenticating...' : '> Login'}
+
+
+
+ No account?{' '}
+ Register here
+
+
+
+
+ );
+}
diff --git a/nest-front/src/pages/public/NotFoundPage.tsx b/nest-front/src/pages/public/NotFoundPage.tsx
new file mode 100644
index 0000000..40b10a2
--- /dev/null
+++ b/nest-front/src/pages/public/NotFoundPage.tsx
@@ -0,0 +1,41 @@
+import { Link } from 'react-router-dom';
+
+export default function NotFoundPage() {
+ return (
+
+
+
+ 404
+
+
+ SECTOR NOT FOUND
+
+
+ The page you're looking for doesn't exist, has been moved, or was redacted by{' '}
+
+ AMALGAM CORP
+ .
+
+
> Return to Base
+
+
+ );
+}
diff --git a/nest-front/src/pages/public/RegisterPage.tsx b/nest-front/src/pages/public/RegisterPage.tsx
new file mode 100644
index 0000000..b206293
--- /dev/null
+++ b/nest-front/src/pages/public/RegisterPage.tsx
@@ -0,0 +1,153 @@
+import { useState, useCallback, useEffect } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { useAuth } from '../../contexts/AuthContext';
+
+export default function RegisterPage() {
+ const { register, isAuthenticated } = useAuth();
+ const navigate = useNavigate();
+
+ const [form, setForm] = useState({ username: '', email: '', password: '', confirmPassword: '' });
+ const [errors, setErrors] = useState>({});
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (isAuthenticated) navigate('/', { replace: true });
+ }, [isAuthenticated, navigate]);
+
+ const set = (key: keyof typeof form, value: string) => {
+ setForm((prev) => ({ ...prev, [key]: value }));
+ setErrors((prev) => ({ ...prev, [key]: undefined }));
+ };
+
+ const validate = (): boolean => {
+ const next: typeof errors = {};
+ if (!form.username.trim()) next.username = 'Username is required.';
+ else if (form.username.length < 3) next.username = 'Username must be at least 3 characters.';
+ else if (!/^[a-zA-Z0-9_-]+$/.test(form.username)) next.username = 'Only letters, numbers, _ and - allowed.';
+ if (!form.email.trim()) next.email = 'Email is required.';
+ else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) next.email = 'Enter a valid email.';
+ if (!form.password) next.password = 'Password is required.';
+ else if (form.password.length < 8) next.password = 'Password must be at least 8 characters.';
+ if (form.password !== form.confirmPassword) next.confirmPassword = 'Passwords do not match.';
+ setErrors(next);
+ return Object.keys(next).length === 0;
+ };
+
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!validate()) return;
+ setLoading(true);
+ const result = await register(form.username, form.email, form.password);
+ setLoading(false);
+ if (!result.success) {
+ setErrors({ form: result.error });
+ }
+ }, [form, register]);
+
+ const inputStyle = (_field?: keyof typeof form) => ({
+ display: 'block' as const,
+ fontFamily: 'var(--font-mono)',
+ color: 'var(--color-text-muted)',
+ fontSize: '0.75rem',
+ marginBottom: '0.4rem',
+ });
+
+ return (
+
+
+
+
Create Account
+
+ REGISTER
+
+
+
+
+ {errors.form && (
+
+ [ERROR] {errors.form}
+
+ )}
+
+ {/* Username */}
+
+
Username
+
set('username', e.target.value)}
+ />
+ {errors.username &&
{errors.username}
}
+
+
+ {/* Email */}
+
+
Email Address
+
set('email', e.target.value)}
+ />
+ {errors.email &&
{errors.email}
}
+
+
+ {/* Password */}
+
+
Password
+
set('password', e.target.value)}
+ />
+ {errors.password &&
{errors.password}
}
+
+
+ {/* Confirm Password */}
+
+
Confirm Password
+
set('confirmPassword', e.target.value)}
+ />
+ {errors.confirmPassword &&
{errors.confirmPassword}
}
+
+
+
+ {loading ? 'Creating account...' : '> Create Account'}
+
+
+
+ Already registered?{' '}
+ Login here
+
+
+
+
+ );
+}
diff --git a/nest-front/src/pages/public/StudioPage.tsx b/nest-front/src/pages/public/StudioPage.tsx
new file mode 100644
index 0000000..b398088
--- /dev/null
+++ b/nest-front/src/pages/public/StudioPage.tsx
@@ -0,0 +1,206 @@
+import { TEAM_MEMBERS } from '../../data/mockData';
+
+export default function StudioPage() {
+ return (
+
+ {/* Header */}
+
+
About
+
+ CROWMATE STUDIO
+
+
+
+ CrowMate Studio is an independent game studio founded in 2023 by a team of six developers
+ united by a shared obsession: games that are strange, atmospheric, and actually interesting.
+ We are headquartered somewhere in Europe and operate fully remote.
+
+
+ >> {' '}
+ Our debut title, Headless Hazard ,
+ is currently in development. We believe that constraints breed creativity — and that
+ you don't need a $200 million budget to make something that sticks.
+
+
+
+
+ {/* History & Vision */}
+
+
Our Vision
+
+ WHY WE BUILD
+
+
+
+ {[
+ {
+ title: 'Strange Mechanics',
+ content: 'We look for the game ideas that make people say "wait, how does that even work?" — then we find out.',
+ },
+ {
+ title: 'Atmospheric Worlds',
+ content: 'Every pixel, every sound, every line of UI text should reinforce the world. Atmosphere is not decoration, it is the game.',
+ },
+ {
+ title: 'Community First',
+ content: 'We build in public. We listen to our players. Bug reports are not annoyances — they are conversations.',
+ },
+ ].map(({ title, content }) => (
+
+
+ {title}
+
+
+ {content}
+
+
+ ))}
+
+
+
+ {/* Team */}
+
+
The Team
+
+ MEET THE CREW
+
+
+
+ {TEAM_MEMBERS.map((member) => (
+
+
+ {/* Avatar */}
+
+ {member.avatarInitials}
+
+
+
+ {member.name}
+
+
+ {member.role}
+
+
+
+
+ {member.bio && (
+
+ {member.bio}
+
+ )}
+
+ {member.social && (
+
+ )}
+
+ ))}
+
+
+
+ );
+}
diff --git a/nest-front/src/pages/public/ThreadPage.tsx b/nest-front/src/pages/public/ThreadPage.tsx
new file mode 100644
index 0000000..1c521ca
--- /dev/null
+++ b/nest-front/src/pages/public/ThreadPage.tsx
@@ -0,0 +1,225 @@
+import { useState, useCallback } from 'react';
+import { Link, useParams, Navigate } from 'react-router-dom';
+import { MOCK_THREADS, MOCK_REPLIES } from '../../data/mockData';
+import { useAuth } from '../../contexts/AuthContext';
+import { formatDateTime, timeAgo } from '../../utils/format';
+
+export default function ThreadPage() {
+ const { id } = useParams<{ id: string }>();
+ const { user, isAuthenticated } = useAuth();
+
+ const thread = MOCK_THREADS.find((t) => t.id === id);
+
+ // Local state for new reply (stored in memory, not persisted)
+ const [replies, setReplies] = useState(
+ MOCK_REPLIES.filter((r) => r.threadId === id)
+ );
+ const [newReply, setNewReply] = useState('');
+ const [replyError, setReplyError] = useState('');
+ const [submitting, setSubmitting] = useState(false);
+
+ const handleReply = useCallback(async () => {
+ if (!newReply.trim()) {
+ setReplyError('Reply cannot be empty.');
+ return;
+ }
+ if (newReply.trim().length < 10) {
+ setReplyError('Reply must be at least 10 characters.');
+ return;
+ }
+ setReplyError('');
+ setSubmitting(true);
+
+ await new Promise((r) => setTimeout(r, 300));
+
+ const reply = {
+ id: `r${Date.now()}`,
+ content: newReply.trim(),
+ authorId: user!.id,
+ authorName: user!.username,
+ threadId: id!,
+ createdAt: new Date().toISOString(),
+ };
+
+ setReplies((prev) => [...prev, reply]);
+ setNewReply('');
+ setSubmitting(false);
+ }, [newReply, user, id]);
+
+ if (!thread) {
+ return ;
+ }
+
+ const category = thread.categoryName;
+
+ return (
+
+ {/* Breadcrumb */}
+
+ Forum
+ {' '}>{' '}
+ {category}
+
+
+ {/* Thread Header */}
+
+
+ {thread.isPinned && Pinned }
+ {thread.isLocked && Locked }
+ {category}
+
+
+
+ {thread.title}
+
+
+
+
+ {thread.authorName[0].toUpperCase()}
+
+
+ {thread.authorName}
+ —
+ {formatDateTime(thread.createdAt)}
+
+
+
+
+ {thread.content}
+
+
+
+ {/* Replies */}
+
+
+ {replies.length} {replies.length === 1 ? 'Reply' : 'Replies'}
+
+
+ {replies.length === 0 ? (
+
+ No replies yet. Be the first to respond.
+
+ ) : (
+
+ {replies.map((reply) => (
+
+
+
+ {reply.authorName[0].toUpperCase()}
+
+
+ {reply.authorName}
+ — {timeAgo(reply.createdAt)}
+
+
+
+ {reply.content}
+
+
+ ))}
+
+ )}
+
+
+ {/* Reply form */}
+ {thread.isLocked ? (
+
+
+ This thread is locked. No new replies can be posted.
+
+
+ ) : isAuthenticated ? (
+
+
Post a Reply
+
{
+ setNewReply(e.target.value);
+ if (replyError) setReplyError('');
+ }}
+ style={{ resize: 'vertical', marginBottom: '0.75rem' }}
+ aria-label="Reply content"
+ disabled={submitting}
+ />
+ {replyError && (
+
+ [ERROR] {replyError}
+
+ )}
+
+ {submitting ? 'Posting...' : '> Post Reply'}
+
+
+ ) : (
+
+
+ You must be logged in to reply.
+
+
+ Login
+ Register
+
+
+ )}
+
+ );
+}
diff --git a/nest-front/src/types/index.ts b/nest-front/src/types/index.ts
new file mode 100644
index 0000000..db9ca04
--- /dev/null
+++ b/nest-front/src/types/index.ts
@@ -0,0 +1,197 @@
+// ── User & Auth ────────────────────────────────────────────────────────────────
+
+export type UserRole = 'user' | 'dev' | 'com';
+
+export interface User {
+ id: string;
+ username: string;
+ email: string;
+ role: UserRole;
+ isAdmin: boolean;
+ isBanned: boolean;
+ createdAt: string; // ISO 8601
+ avatarUrl?: string;
+}
+
+export interface AuthState {
+ user: User | null;
+ isAuthenticated: boolean;
+}
+
+// ── Forum ──────────────────────────────────────────────────────────────────────
+
+export interface ForumCategory {
+ id: string;
+ name: string;
+ description: string;
+ icon: string;
+ threadCount: number;
+ lastActivity?: string;
+}
+
+export interface ForumThread {
+ id: string;
+ title: string;
+ authorId: string;
+ authorName: string;
+ categoryId: string;
+ categoryName: string;
+ content: string;
+ isPinned: boolean;
+ isLocked: boolean;
+ replyCount: number;
+ createdAt: string;
+ updatedAt: string;
+ lastReplyAuthor?: string;
+}
+
+export interface ForumReply {
+ id: string;
+ content: string;
+ authorId: string;
+ authorName: string;
+ threadId: string;
+ createdAt: string;
+}
+
+// ── Bug Reports ────────────────────────────────────────────────────────────────
+
+export type BugSeverity = 'low' | 'medium' | 'high' | 'critical';
+export type BugStatus = 'open' | 'in_progress' | 'resolved' | 'closed';
+
+export interface BugReport {
+ id: string;
+ uniqueCode: string;
+ title: string;
+ description: string;
+ stepsToReproduce: string;
+ severity: BugSeverity;
+ gameVersion: string;
+ screenshotUrl?: string;
+ status: BugStatus;
+ submittedById: string;
+ submittedByName: string;
+ assignedToId?: string;
+ assignedToName?: string;
+ createdAt: string;
+ updatedAt: string;
+ notes?: BugReportNote[];
+ /** IDs of users who clicked "I have this too" */
+ meTooBugs: string[];
+}
+
+export interface BugComment {
+ id: string;
+ bugReportId: string;
+ authorId: string;
+ authorName: string;
+ content: string;
+ createdAt: string;
+}
+
+export interface BugReportNote {
+ id: string;
+ bugReportId: string;
+ authorId: string;
+ authorName: string;
+ content: string;
+ createdAt: string;
+}
+
+export interface BugReportFormData {
+ title: string;
+ description: string;
+ stepsToReproduce: string;
+ severity: BugSeverity;
+ gameVersion: string;
+ screenshotUrl?: string;
+}
+
+// ── Staff Feed ─────────────────────────────────────────────────────────────────
+
+export interface StaffPost {
+ id: string;
+ authorId: string;
+ authorName: string;
+ authorRole: UserRole;
+ content: string;
+ createdAt: string;
+}
+
+// ── Events & Polls ─────────────────────────────────────────────────────────────
+
+export type EventType = 'announcement' | 'update' | 'milestone' | 'poll';
+
+export interface EventPost {
+ id: string;
+ type: EventType;
+ title: string;
+ content: string;
+ authorId: string;
+ authorName: string;
+ authorRole: UserRole;
+ createdAt: string;
+ updatedAt?: string;
+ isPublic: boolean; // whether visible to community
+ pollId?: string; // reference to poll if type is 'poll'
+}
+
+export interface PollOption {
+ id: string;
+ text: string;
+ votes: number;
+ votedUserIds: string[]; // track who voted for this option
+}
+
+export interface Poll {
+ id: string;
+ eventId: string;
+ question: string;
+ options: PollOption[];
+ isActive: boolean;
+ endsAt?: string; // ISO 8601
+ allowMultipleVotes: boolean;
+ createdAt: string;
+}
+
+// ── Team / Studio ──────────────────────────────────────────────────────────────
+
+export interface TeamMember {
+ id: string;
+ name: string;
+ role: string;
+ bio?: string;
+ avatarInitials: string;
+ social?: {
+ twitter?: string;
+ github?: string;
+ };
+}
+
+// ── Forms ──────────────────────────────────────────────────────────────────────
+
+export interface LoginFormData {
+ email: string;
+ password: string;
+}
+
+export interface RegisterFormData {
+ username: string;
+ email: string;
+ password: string;
+ confirmPassword: string;
+}
+
+export interface ChangePasswordFormData {
+ currentPassword: string;
+ newPassword: string;
+ confirmPassword: string;
+}
+
+// ── Filters ────────────────────────────────────────────────────────────────────
+
+export interface BugFilters {
+ status: BugStatus | 'all';
+ severity: BugSeverity | 'all';
+ assignedTo: string | 'all';
+}
diff --git a/nest-front/src/utils/format.ts b/nest-front/src/utils/format.ts
new file mode 100644
index 0000000..4b21bce
--- /dev/null
+++ b/nest-front/src/utils/format.ts
@@ -0,0 +1,47 @@
+/**
+ * Format an ISO 8601 date string to a human-readable date.
+ */
+export function formatDate(iso: string): string {
+ return new Date(iso).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ });
+}
+
+/**
+ * Format an ISO 8601 date string to a human-readable datetime.
+ */
+export function formatDateTime(iso: string): string {
+ return new Date(iso).toLocaleString('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+}
+
+/**
+ * Return a relative time string (e.g. "3 days ago").
+ */
+export function timeAgo(iso: string): string {
+ const diff = Date.now() - new Date(iso).getTime();
+ const seconds = Math.floor(diff / 1000);
+ if (seconds < 60) return 'just now';
+ const minutes = Math.floor(seconds / 60);
+ if (minutes < 60) return `${minutes}m ago`;
+ const hours = Math.floor(minutes / 60);
+ if (hours < 24) return `${hours}h ago`;
+ const days = Math.floor(hours / 24);
+ if (days < 30) return `${days}d ago`;
+ return formatDate(iso);
+}
+
+/**
+ * Truncate a string to maxLength and append ellipsis.
+ */
+export function truncate(str: string, maxLength: number): string {
+ if (str.length <= maxLength) return str;
+ return str.slice(0, maxLength).trimEnd() + '...';
+}
diff --git a/nest-front/tsconfig.app.json b/nest-front/tsconfig.app.json
new file mode 100644
index 0000000..a9b5a59
--- /dev/null
+++ b/nest-front/tsconfig.app.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/nest-front/tsconfig.json b/nest-front/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/nest-front/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/nest-front/tsconfig.node.json b/nest-front/tsconfig.node.json
new file mode 100644
index 0000000..8a67f62
--- /dev/null
+++ b/nest-front/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/nest-front/vite.config.ts b/nest-front/vite.config.ts
new file mode 100644
index 0000000..c676acd
--- /dev/null
+++ b/nest-front/vite.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
+
+export default defineConfig({
+ plugins: [
+ react(),
+ tailwindcss(),
+ ],
+})