diff --git a/docs/technical/architecture.md b/docs/technical/architecture.md index b10ff54..61ec7b2 100644 --- a/docs/technical/architecture.md +++ b/docs/technical/architecture.md @@ -44,12 +44,12 @@ This document describes the code that exists today in the repository. ## Editor System - `src/pages/editor/EditorPage.tsx` is the route-level editor page for `/editor`. -- `src/features/editor/components/EditorControls.tsx` renders the HTML editor control panel. -- `src/features/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, shortcuts, and map rendering. -- `src/features/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. -- `src/features/editor/controls/FlyController.tsx` provides player-style editor navigation. -- `src/features/editor/hooks/useEditorSceneData.ts` loads scene data and handles folder upload fallback. -- `src/features/editor/hooks/useEditorHistory.ts` owns editor undo and redo state. +- `src/components/editor/EditorControls.tsx` renders the HTML editor control panel. +- `src/components/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, shortcuts, and map rendering. +- `src/components/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. +- `src/controls/editor/FlyController.tsx` provides player-style editor navigation. +- `src/hooks/editor/useEditorSceneData.ts` loads scene data and handles folder upload fallback. +- `src/hooks/editor/useEditorHistory.ts` owns editor undo and redo state. - `src/utils/editor/loadEditorScene.ts` handles editor-only folder upload parsing. - `src/utils/loadMapSceneData.ts` is shared by the game scene and editor to load `public/map.json` and resolve model URLs. - `src/types/editor.ts` contains the shared `MapNode`, `SceneData`, and `TransformMode` types. @@ -63,9 +63,9 @@ This document describes the code that exists today in the repository. ## Current Limitations -- The repository is still a prototype, not the full intended game runtime. -- `src/world/debug/TestScene.tsx` is still part of the active scene composition. -- There is no central gameplay orchestrator such as `GameManager` yet. +- The repository is a prototype, not the full intended game runtime. +- `src/world/debug/TestScene.tsx` is part of the active scene composition. +- There is no central gameplay orchestrator such as `GameManager`. - Missions, zones, cinematics, and dialogue systems are not implemented. - The player uses octree collision and simple movement rules, not a complete gameplay physics stack. - Editor save-to-server is implemented as a Vite dev-server plugin, not a production backend API. diff --git a/docs/technical/editor.md b/docs/technical/editor.md index 35e9761..fd0e8cb 100644 --- a/docs/technical/editor.md +++ b/docs/technical/editor.md @@ -10,8 +10,8 @@ The editor is a React route used to inspect and adjust the `public/map.json` sce - `/` renders the playable La-Fabrik scene. - `/editor` renders the map editor. -- `src/main.tsx` wraps the app with `BrowserRouter`. -- `src/App.tsx` defines the route and imports `EditorPage` from `src/pages/editor/EditorPage.tsx`. +- `src/App.tsx` mounts TanStack Router through `RouterProvider`. +- `src/router.tsx` defines the `/editor` route and imports `EditorPage` from `src/pages/editor/page.tsx`. ## File Structure @@ -19,19 +19,20 @@ The editor is a React route used to inspect and adjust the `public/map.json` sce src/ ├── pages/ │ └── editor/ -│ └── EditorPage.tsx -├── features/ +│ └── page.tsx +├── components/ │ └── editor/ -│ ├── components/ -│ │ └── EditorControls.tsx -│ ├── controls/ -│ │ └── FlyController.tsx -│ ├── hooks/ -│ │ ├── useEditorHistory.ts -│ │ └── useEditorSceneData.ts -│ ├── scene/ -│ │ ├── EditorMap.tsx -│ │ └── EditorScene.tsx +│ ├── EditorControls.tsx +│ └── scene/ +│ ├── EditorMap.tsx +│ └── EditorScene.tsx +├── controls/ +│ └── editor/ +│ └── FlyController.tsx +├── hooks/ +│ └── editor/ +│ ├── useEditorHistory.ts +│ └── useEditorSceneData.ts ├── types/ │ └── editor.ts └── utils/ @@ -42,19 +43,19 @@ src/ ## Responsibilities -`src/pages/editor/EditorPage.tsx` is the route-level composition component. It owns route-specific state such as selected object, hovered object, transform mode, and player-mode toggle. +`src/pages/editor/page.tsx` is the route-level composition component. It owns route-specific state such as selected object, hovered object, transform mode, and player-mode toggle. -`src/features/editor/hooks/useEditorSceneData.ts` loads the default map data and handles folder uploads. +`src/hooks/editor/useEditorSceneData.ts` loads the default map data and handles folder uploads. -`src/features/editor/hooks/useEditorHistory.ts` owns editor undo and redo history. +`src/hooks/editor/useEditorHistory.ts` owns editor undo and redo history. -`src/features/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, keyboard shortcuts, and `EditorMap`. +`src/components/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, keyboard shortcuts, and `EditorMap`. -`src/features/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. +`src/components/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. -`src/features/editor/components/EditorControls.tsx` renders the HTML control panel outside the canvas. +`src/components/editor/EditorControls.tsx` renders the HTML control panel outside the canvas. -`src/features/editor/controls/FlyController.tsx` provides editor movement controls for player-style navigation. +`src/controls/editor/FlyController.tsx` provides editor movement controls for player-style navigation. `src/utils/loadMapSceneData.ts` is shared by the game map and editor. It loads `/map.json` and resolves available `public/models/{name}/model.gltf` files. @@ -138,6 +139,6 @@ Editor styles are in `src/index.css` under the `/* Editor page */` section. Clas ## Known Limitations - Uploaded model object URLs are not currently revoked after replacement or unmount. -- Large `map.json` files may need virtualization, culling, or LOD support later. -- There is no snap-to-grid, duplication, material editing, or object creation workflow yet. +- Large `map.json` files are not virtualized, culled, or LOD-managed. +- There is no snap-to-grid, duplication, material editing, or object creation workflow. - Save to Server is a Vite dev-server helper, not a production backend API. diff --git a/docs/technical/target-architecture.md b/docs/technical/target-architecture.md index 9ad9c0e..80efa9f 100644 --- a/docs/technical/target-architecture.md +++ b/docs/technical/target-architecture.md @@ -5,7 +5,7 @@ This document describes the intended medium-term architecture for the project. ## Relationship To The Current Code - `docs/technical/architecture.md` is the source of truth for what exists now. -- This document is intentionally aspirational. +- This document describes intended direction, not implemented behavior. - If this document conflicts with the current implementation, the current implementation wins. ## Goals @@ -40,12 +40,12 @@ This document describes the intended medium-term architecture for the project. - performance overlay - scene helpers - free camera and calibration controls - - temporary test scenes used during development + - debug test scenes used during development ### UI Layer - `src/components/ui/` should contain player-facing HTML overlays. -- Expected future examples: +- Candidate examples: - crosshair - loading flow - mission HUD @@ -54,7 +54,7 @@ This document describes the intended medium-term architecture for the project. ### Gameplay Layer - As the project grows, gameplay state can move toward a clearer orchestration layer. -- Likely future concerns: +- Likely concerns: - missions - zones - cinematics @@ -67,4 +67,4 @@ This document describes the intended medium-term architecture for the project. - Prefer direct, working code over speculative scaffolding. - Shared types should stay close to their domain until they have multiple real consumers. - Avoid creating new managers or service layers without an active runtime need. -- Debug-only runtime paths should be clearly marked and easy to remove later. +- Debug-only runtime paths should be clearly marked and easy to remove when obsolete. diff --git a/docs/user/editor.md b/docs/user/editor.md new file mode 100644 index 0000000..1c92b74 --- /dev/null +++ b/docs/user/editor.md @@ -0,0 +1,83 @@ +# Editor User Guide + +The map editor is available at `/editor`. It is a browser-based tool for inspecting and adjusting the objects listed in `public/map.json`. + +## Purpose + +Use the editor when you need to move, rotate, or scale existing map objects without editing JSON by hand. + +The editor reads the same map data as the runtime scene: + +- `public/map.json` contains the object list. +- `public/models/{name}/model.gltf` contains the matching 3D model for each object name. +- Missing models are displayed as gray fallback cubes, so incomplete maps remain editable. + +## Map Node Format + +Each entry in `public/map.json` represents one object: + +| Field | Description | +| ---------- | ------------------------------------------------- | +| `name` | Model folder name in `public/models/{name}` | +| `type` | Object category | +| `position` | Object position as `[x, y, z]` | +| `rotation` | Object rotation as `[x, y, z]`, expressed radians | +| `scale` | Object scale as `[x, y, z]` | + +## Editing Workflow + +1. Open `/editor` in the local app. +2. Click an object in the scene to select it. +3. Choose a transform mode: translate, rotate, or scale. +4. Drag the transform gizmo in the 3D view. +5. Check the JSON inspector if you need exact values. +6. Use undo or redo if the transform is not correct. +7. Export the JSON or save it to the dev server. + +## Controls + +| Action | Input | +| -------------------- | -------------------------- | +| Select object | Click object | +| Deselect | `Esc` or click empty space | +| Translate mode | `T` | +| Rotate mode | `R` | +| Scale mode | `S` | +| Undo | `Ctrl+Z` | +| Redo | `Ctrl+Y` | +| Locked view movement | `WASD`, `ZQSD`, arrows | +| Move up | `Space` | +| Move down | `Shift` | + +## View Mode + +The `Lock view` action switches the editor into a movement mode closer to the runtime player camera. Use it to navigate larger scenes while keeping the transform tools available. + +## JSON Inspector + +The side panel includes a raw JSON inspector: + +- When no object is selected, it shows the full map node list. +- When an object is selected, it highlights the JSON lines for that object. + +This is useful for checking numeric transform values before saving or exporting. + +## Saving Changes + +### Export JSON + +`Export JSON` downloads the current map node list as `map.json`. Use this when you want to manually replace `public/map.json`. + +### Save To Server + +`Save to server` is available only during local development. It writes the edited map back to `public/map.json` through the Vite dev-server endpoint. + +The button is hidden in production builds because production persistence is not implemented. + +## Current Limitations + +- The editor only modifies existing nodes. +- It does not create or delete objects. +- It does not edit model files or textures. +- It does not provide production persistence. +- Fallback cubes indicate missing models; they are editor placeholders, not exported assets. diff --git a/package-lock.json b/package-lock.json index 1481516..baae035 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,13 +12,15 @@ "@react-three/fiber": "^9.6.0", "@react-three/postprocessing": "^3.0.4", "@react-three/rapier": "^2.2.0", + "@tanstack/react-router": "^1.168.25", "gsap": "^3.15.0", "lil-gui": "^0.21.0", "lucide-react": "^1.11.0", "r3f-perf": "^7.2.3", "react": "^19.2.4", "react-dom": "^19.2.4", - "react-router-dom": "^7.5.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "three": "^0.183.2" }, "devDependencies": { @@ -673,18 +675,6 @@ "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } }, - "node_modules/@react-spring/rafz": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", - "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==", - "license": "MIT" - }, - "node_modules/@react-spring/types": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", - "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==", - "license": "MIT" - }, "node_modules/@react-three/drei": { "version": "10.7.7", "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz", @@ -907,6 +897,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -924,6 +917,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -941,6 +937,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -958,6 +957,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -975,6 +977,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -992,6 +997,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1087,6 +1095,92 @@ "react": ">= 16.3.0" } }, + "node_modules/@tanstack/history": { + "version": "1.161.6", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.161.6.tgz", + "integrity": "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==", + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-router": { + "version": "1.168.25", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.168.25.tgz", + "integrity": "sha512-4U/E76dc+fYuLixjV1RLNfqrkQoexSL8MqGNpIHOodtvY3fMPGaALrvDVtBDQYBEU4z5r5fHaV6+kclWAVFP9A==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.161.6", + "@tanstack/react-store": "^0.9.3", + "@tanstack/router-core": "1.168.17", + "isbot": "^5.1.22" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.3.tgz", + "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.9.3", + "use-sync-external-store": "^1.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/router-core": { + "version": "1.168.17", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.168.17.tgz", + "integrity": "sha512-VDq7HCqRK3sdpxoETwYoTXTaYi+OVQC197g1fdzaiZBUmhntfjn+PQc15OzTqNNhf8Menk6r6ftmuphybMKdig==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.161.6", + "cookie-es": "^3.0.0", + "seroval": "^1.5.0", + "seroval-plugins": "^1.5.0" + }, + "bin": { + "intent": "bin/intent.js" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/store": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz", + "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tweenjs/tween.js": { "version": "23.1.3", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", @@ -1104,6 +1198,15 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/draco3d": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", @@ -1114,9 +1217,26 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1124,6 +1244,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", @@ -1194,6 +1329,12 @@ "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", "license": "Apache-2.0" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@types/webxr": { "version": "0.5.24", "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", @@ -1201,17 +1342,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.0.tgz", - "integrity": "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.0", - "@typescript-eslint/type-utils": "8.59.0", - "@typescript-eslint/utils": "8.59.0", - "@typescript-eslint/visitor-keys": "8.59.0", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1224,7 +1365,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.0", + "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -1240,16 +1381,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.0.tgz", - "integrity": "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.0", - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/typescript-estree": "8.59.0", - "@typescript-eslint/visitor-keys": "8.59.0", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "engines": { @@ -1265,14 +1406,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.0.tgz", - "integrity": "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.0", - "@typescript-eslint/types": "^8.59.0", + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "engines": { @@ -1287,14 +1428,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.0.tgz", - "integrity": "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/visitor-keys": "8.59.0" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1305,9 +1446,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.0.tgz", - "integrity": "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, "license": "MIT", "engines": { @@ -1322,15 +1463,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.0.tgz", - "integrity": "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/typescript-estree": "8.59.0", - "@typescript-eslint/utils": "8.59.0", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1347,9 +1488,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.0.tgz", - "integrity": "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true, "license": "MIT", "engines": { @@ -1361,16 +1502,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.0.tgz", - "integrity": "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.0", - "@typescript-eslint/tsconfig-utils": "8.59.0", - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/visitor-keys": "8.59.0", + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1441,16 +1582,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.0.tgz", - "integrity": "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.0", - "@typescript-eslint/types": "8.59.0", - "@typescript-eslint/typescript-estree": "8.59.0" + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1465,13 +1606,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.0.tgz", - "integrity": "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.0", + "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1495,6 +1636,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@use-gesture/core": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", @@ -1619,6 +1766,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1781,6 +1938,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1798,6 +1965,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1818,6 +2025,16 @@ "dev": true, "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1832,18 +2049,11 @@ "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } + "node_modules/cookie-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", + "license": "MIT" }, "node_modules/cross-env": { "version": "7.0.3", @@ -1887,7 +2097,6 @@ "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" @@ -1901,6 +2110,19 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1908,6 +2130,15 @@ "dev": true, "license": "MIT" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-gpu": { "version": "5.0.70", "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", @@ -1927,6 +2158,19 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/draco3d": { "version": "1.5.7", "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", @@ -2184,6 +2428,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2200,6 +2454,12 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2376,6 +2636,46 @@ "node": ">=8" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -2399,6 +2699,16 @@ "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==", "license": "Apache-2.0" }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -2462,6 +2772,46 @@ "node": ">=0.8.19" } }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2485,12 +2835,43 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "license": "MIT" }, + "node_modules/isbot": { + "version": "5.1.39", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.39.tgz", + "integrity": "sha512-obH0yYahGXdzNxo+djmHhBYThUKDkz565cxkIlt2L9hXfv1NlaLKoDBHo6KxXsYrIXx2RK3x5vY36CfZcobxEw==", + "license": "Unlicense", + "engines": { + "node": ">=18" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2513,6 +2894,7 @@ "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": { @@ -2751,6 +3133,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2772,6 +3157,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2793,6 +3181,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2814,6 +3205,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2898,16 +3292,14 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/lru-cache": { @@ -2939,6 +3331,298 @@ "three": ">=0.134.0" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/meshline": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz", @@ -2954,6 +3638,569 @@ "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", "license": "MIT" }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -2971,7 +4218,6 @@ "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/n8ao": { @@ -3017,15 +4263,6 @@ "dev": true, "license": "MIT" }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3089,6 +4326,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3221,15 +4483,14 @@ "lie": "^3.0.2" } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/punycode": { @@ -3272,168 +4533,6 @@ } } }, - "node_modules/r3f-perf/node_modules/@react-three/drei": { - "version": "9.122.0", - "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.122.0.tgz", - "integrity": "sha512-SEO/F/rBCTjlLez7WAlpys+iGe9hty4rNgjZvgkQeXFSiwqD4Hbk/wNHMAbdd8vprO2Aj81mihv4dF5bC7D0CA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mediapipe/tasks-vision": "0.10.17", - "@monogrid/gainmap-js": "^3.0.6", - "@react-spring/three": "~9.7.5", - "@use-gesture/react": "^10.3.1", - "camera-controls": "^2.9.0", - "cross-env": "^7.0.3", - "detect-gpu": "^5.0.56", - "glsl-noise": "^0.0.0", - "hls.js": "^1.5.17", - "maath": "^0.10.8", - "meshline": "^3.3.1", - "react-composer": "^5.0.3", - "stats-gl": "^2.2.8", - "stats.js": "^0.17.0", - "suspend-react": "^0.1.3", - "three-mesh-bvh": "^0.7.8", - "three-stdlib": "^2.35.6", - "troika-three-text": "^0.52.0", - "tunnel-rat": "^0.1.2", - "utility-types": "^3.11.0", - "zustand": "^5.0.1" - }, - "peerDependencies": { - "@react-three/fiber": "^8", - "react": "^18", - "react-dom": "^18", - "three": ">=0.137" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/r3f-perf/node_modules/@react-three/drei/node_modules/@react-spring/three": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.7.5.tgz", - "integrity": "sha512-RxIsCoQfUqOS3POmhVHa1wdWS0wyHAUway73uRLp3GAL5U2iYVNdnzQsep6M2NZ994BlW8TcKuMtQHUqOsy6WA==", - "license": "MIT", - "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/core": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "@react-three/fiber": ">=6.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "three": ">=0.126" - } - }, - "node_modules/r3f-perf/node_modules/@react-three/drei/node_modules/@react-spring/three/node_modules/@react-spring/animated": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz", - "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", - "license": "MIT", - "dependencies": { - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/r3f-perf/node_modules/@react-three/drei/node_modules/@react-spring/three/node_modules/@react-spring/core": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", - "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", - "license": "MIT", - "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/react-spring/donate" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/r3f-perf/node_modules/@react-three/drei/node_modules/@react-spring/three/node_modules/@react-spring/shared": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", - "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", - "license": "MIT", - "dependencies": { - "@react-spring/rafz": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/r3f-perf/node_modules/@react-three/drei/node_modules/react-composer": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz", - "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==", - "license": "MIT", - "dependencies": { - "prop-types": "^15.6.0" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/r3f-perf/node_modules/@react-three/drei/node_modules/zustand": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", - "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - }, - "node_modules/r3f-perf/node_modules/camera-controls": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.10.1.tgz", - "integrity": "sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w==", - "license": "MIT", - "peerDependencies": { - "three": ">=0.126.1" - } - }, - "node_modules/r3f-perf/node_modules/three-mesh-bvh": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.8.tgz", - "integrity": "sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==", - "deprecated": "Deprecated due to three.js version incompatibility. Please use v0.8.0, instead.", - "license": "MIT", - "peerDependencies": { - "three": ">= 0.151.0" - } - }, "node_modules/r3f-perf/node_modules/zustand": { "version": "4.5.7", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", @@ -3483,48 +4582,31 @@ "react": "^19.2.5" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/react-router": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz", - "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==", + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "license": "MIT", "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, - "engines": { - "node": ">=20.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" }, "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.2.tgz", - "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==", - "license": "MIT", - "dependencies": { - "react-router": "7.14.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" + "@types/react": ">=18", + "react": ">=18" } }, "node_modules/react-use-measure": { @@ -3542,6 +4624,72 @@ } } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3618,11 +4766,26 @@ "semver": "bin/semver.js" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" + "node_modules/seroval": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.2.tgz", + "integrity": "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.2.tgz", + "integrity": "sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } }, "node_modules/shebang-command": { "version": "2.0.0", @@ -3655,6 +4818,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stats-gl": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz", @@ -3681,6 +4854,20 @@ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==", "license": "MIT" }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3694,6 +4881,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3787,6 +4992,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/troika-three-text": { "version": "0.52.4", "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz", @@ -3817,6 +5032,16 @@ "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", "license": "MIT" }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -3903,16 +5128,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.0.tgz", - "integrity": "sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.0", - "@typescript-eslint/parser": "8.59.0", - "@typescript-eslint/typescript-estree": "8.59.0", - "@typescript-eslint/utils": "8.59.0" + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3933,6 +5158,93 @@ "dev": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -3992,6 +5304,34 @@ "node": ">= 4" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "8.0.10", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", @@ -4177,6 +5517,16 @@ "optional": true } } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 9e1cef4..e9c0ac5 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "private": true, "version": "0.0.1", "type": "module", + "engines": { + "node": ">=20.19.0 || >=22.12.0" + }, "scripts": { "dev": "vite", "build": "tsc -b && vite build", @@ -18,13 +21,15 @@ "@react-three/fiber": "^9.6.0", "@react-three/postprocessing": "^3.0.4", "@react-three/rapier": "^2.2.0", + "@tanstack/react-router": "^1.168.25", "gsap": "^3.15.0", "lil-gui": "^0.21.0", "lucide-react": "^1.11.0", "r3f-perf": "^7.2.3", "react": "^19.2.4", "react-dom": "^19.2.4", - "react-router-dom": "^7.5.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "three": "^0.183.2" }, "devDependencies": { @@ -44,7 +49,9 @@ "typescript-eslint": "^8.58.0", "vite": "^8.0.4" }, - "engines": { - "node": ">=20.19.0 || >=22.12.0" + "overrides": { + "r3f-perf": { + "@react-three/drei": "$@react-three/drei" + } } } diff --git a/src/App.tsx b/src/App.tsx index 9cc5acc..c6ccffa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,33 +1,8 @@ -import { Routes, Route } from "react-router-dom"; -import { Suspense } from "react"; -import { Canvas } from "@react-three/fiber"; -import { Crosshair } from "@/components/ui/Crosshair"; -import { InteractPrompt } from "@/components/ui/InteractPrompt"; -import { DebugPerf } from "@/utils/debug/DebugPerf"; -import { World } from "@/world/World"; -import { EditorPage } from "@/pages/editor/EditorPage"; +import { RouterProvider } from "@tanstack/react-router"; +import { router } from "@/router"; function App(): React.JSX.Element { - return ( - - - - - - - - - - - - } - /> - } /> - - ); + return ; } export default App; diff --git a/src/utils/debug/DebugPerf.tsx b/src/components/debug/DebugPerf.tsx similarity index 100% rename from src/utils/debug/DebugPerf.tsx rename to src/components/debug/DebugPerf.tsx diff --git a/src/utils/debug/scene/DebugCameraControls.tsx b/src/components/debug/scene/DebugCameraControls.tsx similarity index 77% rename from src/utils/debug/scene/DebugCameraControls.tsx rename to src/components/debug/scene/DebugCameraControls.tsx index 9fc5f99..11acdb0 100644 --- a/src/utils/debug/scene/DebugCameraControls.tsx +++ b/src/components/debug/scene/DebugCameraControls.tsx @@ -3,17 +3,18 @@ import { DEBUG_CAMERA_DAMPING_FACTOR, DEBUG_CAMERA_MAX_DISTANCE, DEBUG_CAMERA_MIN_DISTANCE, -} from "@/data/debugConfig"; +} from "@/data/debug/debugConfig"; import { PLAYER_EYE_HEIGHT, PLAYER_SPAWN_POSITION_GAME, -} from "@/data/playerConfig"; +} from "@/data/player/playerConfig"; +import type { Vector3Tuple } from "@/types/three"; -const DEBUG_CAMERA_TARGET = [ +const DEBUG_CAMERA_TARGET: Vector3Tuple = [ PLAYER_SPAWN_POSITION_GAME[0], PLAYER_EYE_HEIGHT, PLAYER_SPAWN_POSITION_GAME[2], -] as const; +]; export function DebugCameraControls(): React.JSX.Element { return ( diff --git a/src/utils/debug/scene/DebugHelpers.tsx b/src/components/debug/scene/DebugHelpers.tsx similarity index 94% rename from src/utils/debug/scene/DebugHelpers.tsx rename to src/components/debug/scene/DebugHelpers.tsx index 738c46e..17b50ac 100644 --- a/src/utils/debug/scene/DebugHelpers.tsx +++ b/src/components/debug/scene/DebugHelpers.tsx @@ -5,7 +5,7 @@ import { DEBUG_GRID_SECONDARY_COLOR, DEBUG_GRID_SIZE, DEBUG_GRID_Y, -} from "@/data/debugConfig"; +} from "@/data/debug/debugConfig"; import { Debug } from "@/utils/debug/Debug"; export function DebugHelpers(): React.JSX.Element | null { diff --git a/src/components/docs/DocsDocument.tsx b/src/components/docs/DocsDocument.tsx new file mode 100644 index 0000000..379dbb9 --- /dev/null +++ b/src/components/docs/DocsDocument.tsx @@ -0,0 +1,51 @@ +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { useDocsLanguage } from "@/hooks/docs/useDocsLanguage"; + +interface DocsDocumentProps { + title: string; + meta: string; + content: string; + frContent: string; +} + +export function DocsDocument({ + title, + meta, + content, + frContent, +}: DocsDocumentProps): React.JSX.Element { + const { language, toggleLanguage } = useDocsLanguage(); + const translatedContent = language === "fr" ? frContent : content; + + return ( +
+
+ {title} + +
+ +
+
+ {title} + {meta} +
+ + {translatedContent} + +
+
+ ); +} diff --git a/src/components/docs/DocsLayout.tsx b/src/components/docs/DocsLayout.tsx new file mode 100644 index 0000000..65fc3a5 --- /dev/null +++ b/src/components/docs/DocsLayout.tsx @@ -0,0 +1,53 @@ +import { Link, Outlet } from "@tanstack/react-router"; +import { Home } from "lucide-react"; +import { docGroups } from "@/data/docs/docsSections"; +import { DocsLanguageProvider } from "@/providers/docs/DocsLanguageProvider"; + +export function DocsLayout(): React.JSX.Element { + return ( + +
+ + + +
+
+ ); +} diff --git a/src/features/editor/components/EditorControls.tsx b/src/components/editor/EditorControls.tsx similarity index 81% rename from src/features/editor/components/EditorControls.tsx rename to src/components/editor/EditorControls.tsx index f3db353..e68cd70 100644 --- a/src/features/editor/components/EditorControls.tsx +++ b/src/components/editor/EditorControls.tsx @@ -31,6 +31,20 @@ interface EditorControlsProps { isPlayerMode?: boolean; } +const TRANSFORM_OPTIONS = [ + { mode: "translate", label: "Translate", shortcut: "T", Icon: Move3D }, + { mode: "rotate", label: "Rotate", shortcut: "R", Icon: RotateCw }, + { mode: "scale", label: "Scale", shortcut: "S", Icon: Expand }, +] as const; + +const EDITOR_SHORTCUTS = [ + ["Click", "Select object"], + ["T / R / S", "Transform mode"], + ["Ctrl Z / Y", "Undo / redo"], + ["Esc", "Deselect"], + ["WASD", "Move when locked"], +] as const; + export function EditorControls({ transformMode, onTransformModeChange, @@ -69,33 +83,18 @@ export function EditorControls({
- - - + {TRANSFORM_OPTIONS.map(({ mode, label, shortcut, Icon }) => ( + + ))}
@@ -203,26 +202,12 @@ export function EditorControls({
-
-
Click
-
Select object
-
-
-
T / R / S
-
Transform mode
-
-
-
Ctrl Z / Y
-
Undo / redo
-
-
-
Esc
-
Deselect
-
-
-
WASD
-
Move when locked
-
+ {EDITOR_SHORTCUTS.map(([keys, description]) => ( +
+
{keys}
+
{description}
+
+ ))}
diff --git a/src/features/editor/scene/EditorMap.tsx b/src/components/editor/scene/EditorMap.tsx similarity index 86% rename from src/features/editor/scene/EditorMap.tsx rename to src/components/editor/scene/EditorMap.tsx index 53cb125..8b94718 100644 --- a/src/features/editor/scene/EditorMap.tsx +++ b/src/components/editor/scene/EditorMap.tsx @@ -29,6 +29,12 @@ interface EditorNodeCommonProps { onHoverNode: (index: number | null) => void; } +interface EditorNodePointerHandlers { + onClick: (event: ThreeEvent) => void; + onPointerEnter: (event: ThreeEvent) => void; + onPointerLeave: (event: ThreeEvent) => void; +} + function applyNodeTransform(object: THREE.Object3D, node: MapNode): void { object.position.set(...node.position); object.rotation.set(...node.rotation); @@ -88,6 +94,36 @@ function cloneHighlightedMaterial( return clone; } +function getNodeHighlightColor( + isSelected: boolean, + isHovered: boolean, +): string | null { + if (isSelected) return "#ffffff"; + if (isHovered) return "#b8b8b8"; + return null; +} + +function createEditorNodePointerHandlers( + index: number, + onSelectNode: (index: number | null) => void, + onHoverNode: (index: number | null) => void, +): EditorNodePointerHandlers { + return { + onClick: (event) => { + event.stopPropagation(); + onSelectNode(index); + }, + onPointerEnter: (event) => { + event.stopPropagation(); + onHoverNode(index); + }, + onPointerLeave: (event) => { + event.stopPropagation(); + onHoverNode(null); + }, + }; +} + export function EditorMap({ sceneData, selectedNodeIndex, @@ -224,15 +260,16 @@ function EditorModelNode({ const { scene } = useGLTF(modelUrl); const sceneInstance = useMemo(() => scene.clone(true), [scene]); + const pointerHandlers = createEditorNodePointerHandlers( + index, + onSelectNode, + onHoverNode, + ); useRegisteredEditorNode(groupRef, index, node, objectsMapRef); useEffect(() => { if (!groupRef.current) return; - const highlightColor = isSelected - ? "#ffffff" - : isHovered - ? "#b8b8b8" - : null; + const highlightColor = getNodeHighlightColor(isSelected, isHovered); groupRef.current.traverse((child) => { if (!(child instanceof THREE.Mesh)) { @@ -288,18 +325,7 @@ function EditorModelNode({ position={node.position} rotation={node.rotation} scale={node.scale} - onClick={(e: ThreeEvent) => { - e.stopPropagation(); - onSelectNode(index); - }} - onPointerEnter={(e: ThreeEvent) => { - e.stopPropagation(); - onHoverNode(index); - }} - onPointerLeave={(e: ThreeEvent) => { - e.stopPropagation(); - onHoverNode(null); - }} + {...pointerHandlers} /> ); } @@ -314,9 +340,14 @@ function EditorFallbackNode({ onHoverNode, }: EditorNodeCommonProps) { const meshRef = useRef(null); + const pointerHandlers = createEditorNodePointerHandlers( + index, + onSelectNode, + onHoverNode, + ); useRegisteredEditorNode(meshRef, index, node, objectsMapRef); - const color = isSelected ? "#ffffff" : isHovered ? "#b8b8b8" : "#6f6f6f"; + const color = getNodeHighlightColor(isSelected, isHovered) ?? "#6f6f6f"; return ( ) => { - e.stopPropagation(); - onSelectNode(index); - }} - onPointerEnter={(e: ThreeEvent) => { - e.stopPropagation(); - onHoverNode(index); - }} - onPointerLeave={(e: ThreeEvent) => { - e.stopPropagation(); - onHoverNode(null); - }} + {...pointerHandlers} > diff --git a/src/features/editor/scene/EditorScene.tsx b/src/components/editor/scene/EditorScene.tsx similarity index 95% rename from src/features/editor/scene/EditorScene.tsx rename to src/components/editor/scene/EditorScene.tsx index 7497b5e..000b681 100644 --- a/src/features/editor/scene/EditorScene.tsx +++ b/src/components/editor/scene/EditorScene.tsx @@ -1,7 +1,7 @@ import { useEffect } from "react"; import { OrbitControls } from "@react-three/drei"; -import { FlyController } from "@/features/editor/controls/FlyController"; -import { EditorMap } from "@/features/editor/scene/EditorMap"; +import { EditorMap } from "@/components/editor/scene/EditorMap"; +import { FlyController } from "@/controls/editor/FlyController"; import type { MapNode, TransformMode, SceneData } from "@/types/editor"; interface EditorSceneProps { diff --git a/src/components/3d/GrabbableObject.tsx b/src/components/three/GrabbableObject.tsx similarity index 95% rename from src/components/3d/GrabbableObject.tsx rename to src/components/three/GrabbableObject.tsx index 0499484..abd580e 100644 --- a/src/components/3d/GrabbableObject.tsx +++ b/src/components/three/GrabbableObject.tsx @@ -3,7 +3,7 @@ import { useFrame, useThree } from "@react-three/fiber"; import { RigidBody } from "@react-three/rapier"; import type { RapierRigidBody } from "@react-three/rapier"; import * as THREE from "three"; -import { InteractableObject } from "@/components/3d/InteractableObject"; +import { InteractableObject } from "@/components/three/InteractableObject"; import { GRAB_DEFAULT_COLLIDERS, GRAB_DEFAULT_LABEL, @@ -19,9 +19,9 @@ import { GRAB_THROW_BOOST_MAX, GRAB_THROW_BOOST_MIN, GRAB_THROW_BOOST_STEP, -} from "@/data/grabConfig"; +} from "@/data/interaction/grabConfig"; import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; -import type { ColliderShape, Vector3Tuple } from "@/types/3d"; +import type { ColliderShape, Vector3Tuple } from "@/types/three"; interface GrabbableObjectProps { position: Vector3Tuple; diff --git a/src/components/3d/InteractableObject.tsx b/src/components/three/InteractableObject.tsx similarity index 71% rename from src/components/3d/InteractableObject.tsx rename to src/components/three/InteractableObject.tsx index 4403579..bcca255 100644 --- a/src/components/3d/InteractableObject.tsx +++ b/src/components/three/InteractableObject.tsx @@ -8,13 +8,13 @@ import { INTERACTION_DEBUG_SPHERE_COLOR, INTERACTION_DEBUG_SPHERE_OPACITY, INTERACTION_DEBUG_SPHERE_SEGMENTS, -} from "@/data/debugConfig"; +} from "@/data/debug/debugConfig"; import { Debug } from "@/utils/debug/Debug"; import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; -import { InteractionManager } from "@/stateManager/InteractionManager"; -import { INTERACTION_RADIUS } from "@/data/interactionConfig"; -import type { Vector3Tuple } from "@/types/3d"; -import type { InteractableHandle, InteractableKind } from "@/types/interaction"; +import { InteractionManager } from "@/managers/InteractionManager"; +import { INTERACTION_RADIUS } from "@/data/interaction/interactionConfig"; +import type { Vector3Tuple } from "@/types/three"; +import type { InteractableHandle } from "@/types/interaction"; interface InteractableObjectBaseProps { label: string; @@ -37,46 +37,67 @@ type InteractableObjectProps = | TriggerInteractableObjectProps | GrabInteractableObjectProps; -type MutableInteractableHandle = { - kind: InteractableKind; - label: string; - onPress: () => void; - onRelease?: () => void; -}; - const _cameraPos = new THREE.Vector3(); const _cameraDir = new THREE.Vector3(); const _objectPos = new THREE.Vector3(); const _raycaster = new THREE.Raycaster(); +function createInteractableHandle( + props: InteractableObjectProps, +): InteractableHandle { + if (props.kind === "grab") { + return { + kind: props.kind, + label: props.label, + onPress: props.onPress, + onRelease: props.onRelease, + }; + } + + return { + kind: props.kind, + label: props.label, + onPress: props.onPress, + }; +} + export function InteractableObject( props: InteractableObjectProps, ): React.JSX.Element { const { kind, label, position, bodyRef, onPress, children } = props; - const onRelease = props.kind === "grab" ? props.onRelease : undefined; + const onRelease = props.kind === "grab" ? props.onRelease : null; const camera = useThree((state) => state.camera); const groupRef = useRef(null); const debugSphereRef = useRef(null); - const handle = useRef( - props.kind === "grab" - ? { kind: props.kind, label, onPress, onRelease: props.onRelease } - : { kind: props.kind, label, onPress }, - ); + const handle = useRef(createInteractableHandle(props)); useEffect(() => { - const current = handle.current as MutableInteractableHandle; - current.kind = kind; - current.label = label; - current.onPress = onPress; + const currentHandle = handle.current; + + if (currentHandle.kind === kind) { + currentHandle.label = label; + currentHandle.onPress = onPress; + + if (currentHandle.kind === "grab") { + if (!onRelease) return; + currentHandle.onRelease = onRelease; + } - if (kind === "grab" && onRelease) { - current.onRelease = onRelease; return; } - delete current.onRelease; - return undefined; + if (kind === "grab") { + if (!onRelease) return; + handle.current = { kind, label, onPress, onRelease }; + } else { + handle.current = { kind, label, onPress }; + } + + const manager = InteractionManager.getInstance(); + if (manager.getState().focused === currentHandle) { + manager.setFocused(handle.current); + } }, [kind, label, onPress, onRelease]); const setupInteractionDebugFolder = useCallback((folder: GUI) => { diff --git a/src/components/3d/TriggerObject.tsx b/src/components/three/TriggerObject.tsx similarity index 90% rename from src/components/3d/TriggerObject.tsx rename to src/components/three/TriggerObject.tsx index 937dfae..4e11c96 100644 --- a/src/components/3d/TriggerObject.tsx +++ b/src/components/three/TriggerObject.tsx @@ -1,15 +1,15 @@ import { useState } from "react"; import { useGLTF } from "@react-three/drei"; import { RigidBody } from "@react-three/rapier"; -import { InteractableObject } from "@/components/3d/InteractableObject"; +import { InteractableObject } from "@/components/three/InteractableObject"; import { TRIGGER_DEFAULT_COLLIDERS, TRIGGER_DEFAULT_LABEL, TRIGGER_DEFAULT_SOUND_VOLUME, TRIGGER_DEFAULT_SPAWN_OFFSET, -} from "@/data/triggerConfig"; -import { AudioManager } from "@/stateManager/AudioManager"; -import type { ColliderShape, Vector3Tuple } from "@/types/3d"; +} from "@/data/interaction/triggerConfig"; +import { AudioManager } from "@/managers/AudioManager"; +import type { ColliderShape, Vector3Tuple } from "@/types/three"; interface SpawnedModel { id: number; diff --git a/src/components/ui/InteractPrompt.tsx b/src/components/ui/InteractPrompt.tsx index 4c86b21..70b09db 100644 --- a/src/components/ui/InteractPrompt.tsx +++ b/src/components/ui/InteractPrompt.tsx @@ -1,4 +1,4 @@ -import { INTERACT_KEY } from "@/data/keybindings"; +import { INTERACT_KEY } from "@/data/input/keybindings"; import { useCameraMode } from "@/hooks/debug/useCameraMode"; import { useInteraction } from "@/hooks/useInteraction"; diff --git a/src/contexts/docs/DocsLanguageContext.ts b/src/contexts/docs/DocsLanguageContext.ts new file mode 100644 index 0000000..60e6865 --- /dev/null +++ b/src/contexts/docs/DocsLanguageContext.ts @@ -0,0 +1,11 @@ +import { createContext } from "react"; + +export type DocsLanguage = "en" | "fr"; + +interface DocsLanguageContextValue { + language: DocsLanguage; + toggleLanguage: () => void; +} + +export const DocsLanguageContext = + createContext(null); diff --git a/src/features/editor/controls/FlyController.tsx b/src/controls/editor/FlyController.tsx similarity index 100% rename from src/features/editor/controls/FlyController.tsx rename to src/controls/editor/FlyController.tsx diff --git a/src/data/debugConfig.ts b/src/data/debug/debugConfig.ts similarity index 91% rename from src/data/debugConfig.ts rename to src/data/debug/debugConfig.ts index 6f5e4fc..618ddc6 100644 --- a/src/data/debugConfig.ts +++ b/src/data/debug/debugConfig.ts @@ -2,8 +2,6 @@ export const INTERACTION_DEBUG_SPHERE_SEGMENTS = 16; export const INTERACTION_DEBUG_SPHERE_COLOR = "#facc15"; export const INTERACTION_DEBUG_SPHERE_OPACITY = 0.25; -export const MAP_DEBUG_BOX_HELPER_COLOR = 0x00ff88; - export const DEBUG_CAMERA_DAMPING_FACTOR = 0.05; export const DEBUG_CAMERA_MIN_DISTANCE = 100; export const DEBUG_CAMERA_MAX_DISTANCE = 1000; diff --git a/src/data/testSceneConfig.ts b/src/data/debug/testSceneConfig.ts similarity index 94% rename from src/data/testSceneConfig.ts rename to src/data/debug/testSceneConfig.ts index 0cd23bf..da1edc2 100644 --- a/src/data/testSceneConfig.ts +++ b/src/data/debug/testSceneConfig.ts @@ -1,4 +1,4 @@ -import type { Vector3Tuple } from "@/types/3d"; +import type { Vector3Tuple } from "@/types/three"; export const TEST_SCENE_FLOOR_POSITION: Vector3Tuple = [0, -0.5, 0]; export const TEST_SCENE_FLOOR_SIZE: Vector3Tuple = [200, 1, 200]; diff --git a/src/data/docs/docsSections.ts b/src/data/docs/docsSections.ts new file mode 100644 index 0000000..2fd4f9e --- /dev/null +++ b/src/data/docs/docsSections.ts @@ -0,0 +1,60 @@ +interface DocSection { + path: string; + title: string; + subtitle: string; + meta: string; +} + +interface DocGroup { + label: string; + sections: DocSection[]; +} + +export const docGroups: DocGroup[] = [ + { + label: "Technical", + sections: [ + { + path: "/docs", + title: "README", + subtitle: "Project overview", + meta: "01", + }, + { + path: "/docs/architecture", + title: "Current Architecture", + subtitle: "Runtime structure", + meta: "02", + }, + { + path: "/docs/target-architecture", + title: "Target Architecture", + subtitle: "Next direction", + meta: "03", + }, + { + path: "/docs/technical-editor", + title: "Editor Technical Notes", + subtitle: "Implementation details", + meta: "04", + }, + ], + }, + { + label: "User", + sections: [ + { + path: "/docs/features", + title: "Features", + subtitle: "Implemented scope", + meta: "05", + }, + { + path: "/docs/editor", + title: "Editor User Guide", + subtitle: "Editing workflow", + meta: "06", + }, + ], + }, +]; diff --git a/src/data/docs/docsTranslations.ts b/src/data/docs/docsTranslations.ts new file mode 100644 index 0000000..82d45ec --- /dev/null +++ b/src/data/docs/docsTranslations.ts @@ -0,0 +1,329 @@ +export const readmeFr = `# La-Fabrik + +Une expérience web 3D interactive pour La Fabrik Durable, un service low-tech de réparation et de transformation situé à Altera, une ville post-capitaliste reconstruite en 2039. Les joueurs incarnent un technicien fraîchement intégré et vivent une journée de service : réparer un vélo électrique, remettre en état un réseau d'énergie et améliorer le système d'irrigation d'une ferme verticale. + +Construit avec React, Three.js et Vite. Fonctionne dans le navigateur, sans installation côté utilisateur. + +## Stack technique + +### Build et langage + +| Package | +| -------------------------------------------------- | +| [TypeScript](https://www.typescriptlang.org/docs/) | +| [React](https://react.dev/learn) | +| [Vite](https://vite.dev/guide/) | +| [ESLint](https://eslint.org/docs/latest/) | +| [Prettier](https://prettier.io/docs/) | + +### Moteur 3D + +| Package | +| ----------------------------------------------------------------------------------------- | +| [Three.js](https://threejs.org/docs/) | +| [@react-three/fiber](https://docs.pmnd.rs/react-three-fiber/getting-started/introduction) | +| [@react-three/drei](https://pmndrs.github.io/drei) | +| [@react-three/rapier](https://rapier.rs/docs/) | +| [@react-three/postprocessing](https://github.com/pmndrs/postprocessing) | +| [GSAP](https://gsap.com/docs/v3/Installation/) | + +### Performance et effets + +| Package | +| --------------------------------------------------------------------------- | +| [r3f-perf](https://github.com/utsuboco/r3f-perf) | +| [AnimationMixer](https://threejs.org/docs/#api/en/animation/AnimationMixer) | + +## Structure du projet + +\`\`\` +la-fabrik/ +├── public/ +│ ├── models/ +│ │ ├── map/ # Carte de base, chargée au démarrage +│ │ ├── workshop/ +│ │ ├── powerGrid/ +│ │ └── farm/ +│ ├── textures/ +│ └── sounds/ +│ +└── src/ + ├── world/ # Monde 3D persistant + │ ├── World.tsx # Composition principale de la scène + │ ├── Map.tsx # Carte de base, toujours montée + │ ├── Lighting.tsx # Lumières ambiante, directionnelle et ponctuelles + │ ├── Environment.tsx # HDRI, brouillard, ciel + │ ├── PostFX.tsx # Bloom, SSAO, aberration chromatique + │ ├── zones/ # Zones spatiales, LOD par zone + │ └── player/ # Contrôleur joueur et caméra + │ + ├── components/ + │ ├── 3d/ # Éléments 3D réutilisables + │ └── ui/ # Overlays HTML hors Canvas + │ + ├── managers/ # Logique, état et orchestration + ├── hooks/ # Hooks React autour des managers + ├── data/ # Configuration statique + ├── shaders/ # Shaders GLSL + └── utils/ # Utilitaires partagés et debug +\`\`\` + +## Démarrage + +\`\`\`bash +git clone https://github.com/La-Fabrik-Durable/La-Fabrik.git +cd La-Fabrik +npm install +npm run dev +\`\`\` + +- application : \`http://localhost:5173\` +- mode debug : \`http://localhost:5173?debug\` + +## Licence + +Voir le fichier [LICENSE](./LICENSE). +`; + +export const architectureFr = `# Architecture actuelle + +Ce document décrit le code réellement présent aujourd'hui dans le dépôt. + +## Structure runtime + +- \`src/App.tsx\` monte le \`RouterProvider\`, qui pilote l'affichage des vues de l'application. +- \`src/pages/page.tsx\` monte le \`Canvas\`, le \`World\` 3D, l'overlay de performance debug et les overlays HTML. +- \`src/world/World.tsx\` compose la scène active avec : + - l'environnement et l'éclairage + - les helpers debug et le mode caméra debug + - soit la carte principale, soit la scène de test physique debug + - le rig joueur quand le mode caméra actif est \`player\` +- \`src/world/GameMap.tsx\` charge les modèles de carte disponibles et construit l'octree de collision. +- \`src/world/debug/TestScene.tsx\` fournit une scène orientée debug pour les interactions et la physique. +- \`src/world/player/Player.tsx\` monte la caméra et le contrôleur. +- \`src/world/player/PlayerController.tsx\` gère le mouvement pointer lock, le saut et les inputs d'interaction. + +## Modèle d'interaction + +- \`src/managers/InteractionManager.ts\` est la source d'état actuelle des interactions. +- \`src/components/three/InteractableObject.tsx\` gère la détection de focus par distance et raycasting. +- \`src/components/three/TriggerObject.tsx\` implémente les interactions de type trigger. +- \`src/components/three/GrabbableObject.tsx\` implémente les interactions saisir / relâcher. +- \`src/hooks/useInteraction.ts\` expose un snapshot d'interaction à l'UI React. +- \`src/components/ui/InteractPrompt.tsx\` affiche le prompt \`E\` pour les interactions trigger. + +## Audio + +- \`src/managers/AudioManager.ts\` fournit actuellement une lecture de sons one-shot avec pool. +- Les interactions trigger peuvent lancer directement un son via \`AudioManager\`. + +## Système debug + +- Le mode debug est activé avec \`?debug\`. +- \`src/utils/debug/Debug.ts\` possède l'instance \`lil-gui\` et les contrôles debug. +- \`src/hooks/debug/useCameraMode.ts\` et \`src/hooks/debug/useSceneMode.ts\` s'abonnent à l'état debug. +- \`src/components/debug/DebugPerf.tsx\` monte \`r3f-perf\` en lazy uniquement en mode debug. +- \`src/components/debug/scene/DebugHelpers.tsx\` monte les helpers debug. +- \`src/components/debug/scene/DebugCameraControls.tsx\` monte la caméra libre debug. + +## Limites actuelles + +- Le dépôt est encore un prototype, pas le runtime complet du jeu. +- \`src/world/debug/TestScene.tsx\` fait encore partie de la composition active. +- Il n'existe pas encore d'orchestrateur gameplay central comme \`GameManager\`. +- Les systèmes de missions, zones, cinématiques et dialogues ne sont pas implémentés. +- Le joueur utilise une collision octree et des règles simples, pas une pile physique gameplay complète. +`; + +export const targetArchitectureFr = `# Architecture cible + +Ce document décrit l'architecture visée à moyen terme pour le projet. + +## Relation avec le code actuel + +- \`docs/technical/architecture.md\` reste la source de vérité de ce qui existe maintenant. +- Ce document est volontairement aspirational. +- Si ce document contredit l'implémentation actuelle, l'implémentation actuelle gagne. + +## Objectifs + +- Garder \`App.tsx\` petit et centré sur l'orchestration. +- Séparer le code de production du monde des chemins runtime uniquement debug. +- Garder une source de vérité claire par responsabilité. +- Faire grandir les systèmes gameplay progressivement, sans préconstruire une architecture vide. + +## Couches prévues + +### Couche App + +- \`App.tsx\` monte la scène canvas et les overlays HTML de premier niveau. +- Il doit rester fin et éviter la logique gameplay. + +### Couche World + +- \`src/world/\` doit contenir la composition de scène de production et les objets de scène de production. +- Responsabilités attendues : + - composition du monde + - carte, environnement, éclairage + - contrôleur joueur + - ancres d'interaction de production + - post-processing de production si nécessaire + +### Couche Debug + +- Les scènes et outils uniquement debug doivent être isolés du chemin de production. +- Responsabilités attendues : + - \`lil-gui\` + - overlay de performance + - helpers de scène + - caméra libre et contrôles de calibration + - scènes temporaires de test utilisées pendant le développement + +### Couche UI + +- \`src/components/ui/\` doit contenir les overlays HTML visibles par le joueur. +- Exemples futurs : + - crosshair + - flow de chargement + - HUD de mission + - overlays narratifs + +### Couche Gameplay + +- À mesure que le projet grandit, l'état gameplay peut évoluer vers une couche d'orchestration plus claire. +- Sujets probables : + - missions + - zones + - cinématiques + - dialogues + - audio + - interactions + +## Règles + +- Préférer du code direct et fonctionnel plutôt qu'un échafaudage spéculatif. +- Les types partagés doivent rester proches de leur domaine jusqu'à avoir plusieurs vrais consommateurs. +- Éviter de créer de nouveaux managers ou services sans besoin runtime actif. +- Les chemins runtime uniquement debug doivent être clairement marqués et faciles à retirer plus tard. +`; + +export const featuresFr = `# Fonctionnalités implémentées + +Ce document liste les fonctionnalités présentes dans le code actuel. + +## Scène + +- Scène React Three Fiber plein écran +- Carte principale chargée depuis \`public/models/map/model.gltf\` +- Scène de test physique debug sélectionnable depuis le panneau debug +- Éclairage ambiant et directionnel +- Configuration de l'environnement de fond + +## Joueur + +- Mode caméra joueur +- Orientation souris avec pointer lock +- Déplacement avec \`ZQSD\` +- Saut +- Collision basée sur une octree contre la carte chargée + +## Interactions + +- Détection de focus par distance et raycast +- Interactions trigger activées avec \`E\` +- Interactions grab activées avec le bouton principal de la souris +- Prompt d'interaction affiché pour les interactions trigger + +## Audio + +- Lecture de sons one-shot pour les interactions trigger +- Pool simple par son via \`AudioManager\` + +## Outils debug + +- Le paramètre \`?debug\` active le panneau debug +- Contrôles \`lil-gui\` pour le mode caméra, le mode scène et les sphères d'interaction +- Helpers de scène debug +- Caméra libre debug +- Overlay \`r3f-perf\` + +## Pas encore implémenté + +- système de missions +- système de zones +- système de cinématiques +- système de dialogues +- flow de chargement +- minimap et HUD de mission +- séparation complète production / debug pour les scènes gameplay +`; + +export const editorFr = `# Éditeur de carte + +L'éditeur de carte est disponible sur "/editor". Il permet d'inspecter et d'ajuster les objets déclarés dans "/public/map.json" directement depuis le navigateur. + +## Ce qui est édité + +L'éditeur travaille sur la liste de nodes stockée dans "/public/map.json". + +Chaque node décrit un objet de la scène : + +- "name" : nom du dossier modèle dans "/public/models/{name}/model.gltf" +- "type" : catégorie de l'objet +- "position" : "[x, y, z]" +- "rotation" : "[x, y, z]" +- "scale" : "[x, y, z]" + +Les modèles sont chargés depuis "/public/models". Si un modèle manque, l'éditeur affiche un cube gris de remplacement pour que le node reste sélectionnable et déplaçable. + +## Workflow de base + +1. Ouvrir "/editor". +2. Sélectionner un objet dans la vue 3D. +3. Choisir un mode de transformation : translation, rotation ou scale. +4. Déplacer la gizmo de transformation. +5. Utiliser undo ou redo si nécessaire. +6. Exporter le JSON mis à jour ou le sauvegarder sur le serveur de dev. + +## Contrôles + +| Action | Input | +| --- | --- | +| Sélectionner un objet | Clic sur l'objet | +| Désélectionner | "Esc" ou clic dans le vide | +| Mode translation | "T" | +| Mode rotation | "R" | +| Mode scale | "S" | +| Undo | "Ctrl+Z" | +| Redo | "Ctrl+Y" | +| Déplacement en vue verrouillée | "WASD", "ZQSD", flèches | +| Monter / descendre | "Space", "Shift" | + +## Actions fichier + +### Export JSON + +"Export JSON" télécharge la liste actuelle des nodes sous le nom "map.json". À utiliser pour remplacer manuellement "/public/map.json". + +### Save to server + +"Save to server" est disponible uniquement en développement local. L'action écrit la carte modifiée dans "/public/map.json" via l'endpoint du serveur de dev Vite. + +Cette action est masquée dans les builds de production car il n'existe pas encore d'API de persistance production. + +## Inspecteur JSON + +Le panneau latéral affiche le JSON brut de la carte : + +- sans sélection, il affiche toute la liste des nodes +- avec un objet sélectionné, il met en évidence les lignes du node sélectionné + +Utilise-le pour vérifier les valeurs numériques exactes avant export ou sauvegarde. + +## Limites actuelles + +- L'éditeur modifie uniquement les nodes existants. +- Il n'y a pas encore d'interface pour créer ou supprimer des objets. +- La sauvegarde production n'est pas implémentée. +- Les modèles manquants s'affichent comme cubes de fallback au lieu de bloquer tout l'éditeur. +`; diff --git a/src/data/keybindings.ts b/src/data/input/keybindings.ts similarity index 100% rename from src/data/keybindings.ts rename to src/data/input/keybindings.ts diff --git a/src/data/grabConfig.ts b/src/data/interaction/grabConfig.ts similarity index 100% rename from src/data/grabConfig.ts rename to src/data/interaction/grabConfig.ts diff --git a/src/data/interactionConfig.ts b/src/data/interaction/interactionConfig.ts similarity index 100% rename from src/data/interactionConfig.ts rename to src/data/interaction/interactionConfig.ts diff --git a/src/data/triggerConfig.ts b/src/data/interaction/triggerConfig.ts similarity index 80% rename from src/data/triggerConfig.ts rename to src/data/interaction/triggerConfig.ts index 304ef82..46bf396 100644 --- a/src/data/triggerConfig.ts +++ b/src/data/interaction/triggerConfig.ts @@ -1,4 +1,4 @@ -import type { Vector3Tuple } from "@/types/3d"; +import type { Vector3Tuple } from "@/types/three"; export const TRIGGER_DEFAULT_COLLIDERS = "ball"; export const TRIGGER_DEFAULT_LABEL = "Interagir"; diff --git a/src/data/playerConfig.ts b/src/data/player/playerConfig.ts similarity index 90% rename from src/data/playerConfig.ts rename to src/data/player/playerConfig.ts index 43e183e..a8b60ea 100644 --- a/src/data/playerConfig.ts +++ b/src/data/player/playerConfig.ts @@ -1,4 +1,4 @@ -import type { Vector3Tuple } from "@/types/3d"; +import type { Vector3Tuple } from "@/types/three"; export const PLAYER_EYE_HEIGHT = 1.75; export const PLAYER_CAPSULE_RADIUS = 0.35; diff --git a/src/data/environmentConfig.ts b/src/data/world/environmentConfig.ts similarity index 100% rename from src/data/environmentConfig.ts rename to src/data/world/environmentConfig.ts diff --git a/src/data/lightingConfig.ts b/src/data/world/lightingConfig.ts similarity index 100% rename from src/data/lightingConfig.ts rename to src/data/world/lightingConfig.ts diff --git a/src/hooks/docs/useDocsLanguage.ts b/src/hooks/docs/useDocsLanguage.ts new file mode 100644 index 0000000..664b68d --- /dev/null +++ b/src/hooks/docs/useDocsLanguage.ts @@ -0,0 +1,12 @@ +import { useContext } from "react"; +import { DocsLanguageContext } from "@/contexts/docs/DocsLanguageContext"; + +export function useDocsLanguage() { + const context = useContext(DocsLanguageContext); + + if (!context) { + throw new Error("useDocsLanguage must be used inside DocsLanguageProvider"); + } + + return context; +} diff --git a/src/features/editor/hooks/useEditorHistory.ts b/src/hooks/editor/useEditorHistory.ts similarity index 100% rename from src/features/editor/hooks/useEditorHistory.ts rename to src/hooks/editor/useEditorHistory.ts diff --git a/src/features/editor/hooks/useEditorSceneData.ts b/src/hooks/editor/useEditorSceneData.ts similarity index 100% rename from src/features/editor/hooks/useEditorSceneData.ts rename to src/hooks/editor/useEditorSceneData.ts diff --git a/src/hooks/useInteraction.ts b/src/hooks/useInteraction.ts index 58fabc2..34a2a86 100644 --- a/src/hooks/useInteraction.ts +++ b/src/hooks/useInteraction.ts @@ -1,5 +1,5 @@ import { useSyncExternalStore } from "react"; -import { InteractionManager } from "@/stateManager/InteractionManager"; +import { InteractionManager } from "@/managers/InteractionManager"; import type { InteractionSnapshot } from "@/types/interaction"; const manager = InteractionManager.getInstance(); diff --git a/src/hooks/useOctreeGraphNode.ts b/src/hooks/useOctreeGraphNode.ts index c03458c..3a4f3a3 100644 --- a/src/hooks/useOctreeGraphNode.ts +++ b/src/hooks/useOctreeGraphNode.ts @@ -2,14 +2,19 @@ import { useEffect, useRef } from "react"; import type { RefObject } from "react"; import type { Object3D } from "three"; import { Octree } from "three/addons/math/Octree.js"; -import type { OctreeReadyHandler } from "@/types/3d"; +import type { OctreeReadyHandler } from "@/types/three"; export function useOctreeGraphNode( graphNodeRef: RefObject, onOctreeReady: OctreeReadyHandler, + rebuildKey: string | number = 0, ): void { const octreeBuilt = useRef(false); + useEffect(() => { + octreeBuilt.current = false; + }, [rebuildKey]); + useEffect(() => { const graphNode = graphNodeRef.current; if (octreeBuilt.current || !graphNode) return; @@ -20,5 +25,5 @@ export function useOctreeGraphNode( const octree = new Octree(); octree.fromGraphNode(graphNode); onOctreeReady(octree); - }, [graphNodeRef, onOctreeReady]); + }, [graphNodeRef, onOctreeReady, rebuildKey]); } diff --git a/src/index.css b/src/index.css index d59a38b..a32c152 100644 --- a/src/index.css +++ b/src/index.css @@ -1,6 +1,8 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); + :root { color-scheme: dark; - font-family: Inter; + font-family: "Helvetica Neue", Helvetica, Inter, Arial, sans-serif; } html, @@ -27,6 +29,315 @@ canvas { display: block; } +.docs-page { + display: grid; + grid-template-columns: 300px minmax(0, 1fr); + width: 100vw; + height: 100vh; + overflow: hidden; + background: #050505; + color: #f4efe7; + font-family: "Helvetica Neue", Helvetica, Inter, Arial, sans-serif; +} + +.docs-sidebar { + border-right: 2px solid #d8d0c4; + background: #050505; + overflow-y: auto; +} + +.docs-sidebar__header, +.docs-content__header { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 78px; + padding: 0 18px; + border-bottom: 2px solid #d8d0c4; +} + +.docs-sidebar__header h1, +.docs-content__header span { + margin: 0; + color: #f4efe7; + font-size: 21px; + font-weight: 700; + letter-spacing: -0.04em; +} + +.docs-sidebar nav { + display: grid; +} + +.docs-nav-group { + display: grid; + border-bottom: 2px solid #d8d0c4; +} + +.docs-nav-group h2 { + margin: 0; + padding: 13px 16px 8px; + color: #a9a196; + font-size: 10px; + font-weight: 800; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.docs-sidebar a { + color: #f4efe7; + text-decoration: none; +} + +.docs-nav-item { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 46px; + padding: 0 16px; + border-top: 1px solid rgba(216, 208, 196, 0.35); + color: #f4efe7; + transition: + background 160ms ease, + color 160ms ease; +} + +.docs-home-link { + display: grid; + place-items: center; + width: 34px; + height: 34px; + border: 2px solid currentColor; + border-radius: 999px; + transition: + background 160ms ease, + color 160ms ease; +} + +.docs-nav-item span:first-child { + display: grid; + gap: 2px; +} + +.docs-nav-item strong { + font-size: 13px; + font-weight: 700; + letter-spacing: -0.03em; +} + +.docs-nav-item small, +.docs-nav-item__meta { + color: #a9a196; + font-size: 11px; + font-weight: 600; + letter-spacing: -0.01em; +} + +.docs-sidebar a:hover, +.docs-sidebar a:focus-visible, +.docs-nav-item--active { + background: #f4efe7; + color: #050505; + outline: none; +} + +.docs-sidebar a:hover small, +.docs-sidebar a:hover .docs-nav-item__meta, +.docs-sidebar a:focus-visible small, +.docs-sidebar a:focus-visible .docs-nav-item__meta, +.docs-nav-item--active small, +.docs-nav-item--active .docs-nav-item__meta { + color: #050505; +} + +.docs-content { + overflow-y: auto; + scroll-behavior: smooth; + background: #050505; +} + +.docs-content__header { + position: sticky; + top: 0; + z-index: 2; + background: #050505; +} + +.docs-language-toggle { + display: inline-flex; + align-items: center; + gap: 0; + padding: 2px; + border: 2px solid #d8d0c4; + border-radius: 999px; + background: transparent; + color: #f4efe7; + cursor: pointer; +} + +.docs-language-toggle span { + display: grid; + place-items: center; + min-width: 36px; + min-height: 26px; + border-radius: 999px; + color: #a9a196; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.04em; +} + +.docs-language-toggle .is-active { + background: #f4efe7; + color: #050505; +} + +.docs-language-toggle:hover, +.docs-language-toggle:focus-visible { + outline: none; + background: rgba(216, 208, 196, 0.1); +} + +.docs-section { + max-width: 920px; + margin: 0 auto; + padding: 34px clamp(18px, 4vw, 56px) 48px; +} + +.docs-section__eyebrow { + display: flex; + justify-content: space-between; + margin-bottom: 22px; + color: #a9a196; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.docs-section h1, +.docs-section h2, +.docs-section h3 { + color: #f4efe7; + letter-spacing: -0.06em; + line-height: 1.05; +} + +.docs-section h1 { + margin-top: 0; + margin-bottom: 20px; + font-size: clamp(46px, 7vw, 88px); + font-weight: 700; +} + +.docs-section h2 { + margin-top: 44px; + margin-bottom: 12px; + padding-bottom: 10px; + border-bottom: 2px solid #d8d0c4; + font-size: clamp(28px, 4vw, 44px); + font-weight: 700; +} + +.docs-section h3 { + margin-top: 30px; + margin-bottom: 10px; + font-size: 18px; + font-weight: 700; + letter-spacing: -0.03em; +} + +.docs-section p, +.docs-section li { + color: #d8d0c4; + font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 15px; + line-height: 1.75; +} + +.docs-section ul, +.docs-section ol { + padding-left: 24px; +} + +.docs-section a { + color: #f4efe7; + text-underline-offset: 4px; +} + +.docs-section code { + border: 0; + border-radius: 2px; + padding: 2px 5px; + background: rgba(216, 208, 196, 0.22); + color: #f4efe7; + font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + font-size: 0.92em; +} + +.docs-section pre { + overflow-x: auto; + padding: 18px; + border: 0; + border-radius: 0; + background: #0d0d0d; +} + +.docs-section pre code { + display: block; + padding: 0; + border: 0; + background: transparent; + color: #f4efe7; + line-height: 1.45; + white-space: pre; +} + +.docs-section table { + display: block; + width: 100%; + margin: 20px 0; + overflow-x: auto; + border-collapse: collapse; +} + +.docs-section th, +.docs-section td { + padding: 10px 12px; + border: 2px solid #d8d0c4; + text-align: left; +} + +.docs-section th { + background: #111; + color: #f4efe7; + font-weight: 700; +} + +.docs-section blockquote { + margin-left: 0; + padding-left: 18px; + border-left: 2px solid #d8d0c4; + color: #a9a196; +} + +@media (max-width: 760px) { + .docs-page { + display: block; + overflow-y: auto; + } + + .docs-sidebar { + border-right: 0; + border-bottom: 2px solid #d8d0c4; + } + + .docs-content { + overflow: visible; + padding: 24px 16px; + } +} + .crosshair { position: fixed; top: 50%; diff --git a/src/main.tsx b/src/main.tsx index b3c2e37..c2a145c 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,13 +1,10 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import { BrowserRouter } from "react-router-dom"; import App from "./App"; import "./index.css"; createRoot(document.getElementById("root")!).render( - - - + , ); diff --git a/src/stateManager/AudioManager.ts b/src/managers/AudioManager.ts similarity index 100% rename from src/stateManager/AudioManager.ts rename to src/managers/AudioManager.ts diff --git a/src/stateManager/InteractionManager.ts b/src/managers/InteractionManager.ts similarity index 100% rename from src/stateManager/InteractionManager.ts rename to src/managers/InteractionManager.ts diff --git a/src/pages/docs/architecture/page.tsx b/src/pages/docs/architecture/page.tsx new file mode 100644 index 0000000..f876316 --- /dev/null +++ b/src/pages/docs/architecture/page.tsx @@ -0,0 +1,14 @@ +import architecture from "../../../../docs/technical/architecture.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; +import { architectureFr } from "@/data/docs/docsTranslations"; + +export function DocsArchitecturePage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/editor/page.tsx b/src/pages/docs/editor/page.tsx new file mode 100644 index 0000000..53c7236 --- /dev/null +++ b/src/pages/docs/editor/page.tsx @@ -0,0 +1,14 @@ +import editor from "../../../../docs/user/editor.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; +import { editorFr } from "@/data/docs/docsTranslations"; + +export function DocsEditorPage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/features/page.tsx b/src/pages/docs/features/page.tsx new file mode 100644 index 0000000..4b41580 --- /dev/null +++ b/src/pages/docs/features/page.tsx @@ -0,0 +1,14 @@ +import features from "../../../../docs/user/features.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; +import { featuresFr } from "@/data/docs/docsTranslations"; + +export function DocsFeaturesPage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/page.tsx b/src/pages/docs/page.tsx new file mode 100644 index 0000000..c1f4380 --- /dev/null +++ b/src/pages/docs/page.tsx @@ -0,0 +1,14 @@ +import readme from "../../../README.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; +import { readmeFr } from "@/data/docs/docsTranslations"; + +export function DocsReadmePage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/target-architecture/page.tsx b/src/pages/docs/target-architecture/page.tsx new file mode 100644 index 0000000..b99c6d1 --- /dev/null +++ b/src/pages/docs/target-architecture/page.tsx @@ -0,0 +1,14 @@ +import targetArchitecture from "../../../../docs/technical/target-architecture.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; +import { targetArchitectureFr } from "@/data/docs/docsTranslations"; + +export function DocsTargetArchitecturePage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/docs/technical-editor/page.tsx b/src/pages/docs/technical-editor/page.tsx new file mode 100644 index 0000000..f03c609 --- /dev/null +++ b/src/pages/docs/technical-editor/page.tsx @@ -0,0 +1,13 @@ +import technicalEditor from "../../../../docs/technical/editor.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; + +export function DocsTechnicalEditorPage(): React.JSX.Element { + return ( + + ); +} diff --git a/src/pages/editor/EditorPage.tsx b/src/pages/editor/page.tsx similarity index 88% rename from src/pages/editor/EditorPage.tsx rename to src/pages/editor/page.tsx index 2e3cfe1..e06cc51 100644 --- a/src/pages/editor/EditorPage.tsx +++ b/src/pages/editor/page.tsx @@ -1,10 +1,16 @@ import { useCallback, useState } from "react"; import { Canvas } from "@react-three/fiber"; -import { EditorControls } from "@/features/editor/components/EditorControls"; -import { useEditorHistory } from "@/features/editor/hooks/useEditorHistory"; -import { useEditorSceneData } from "@/features/editor/hooks/useEditorSceneData"; -import { EditorScene } from "@/features/editor/scene/EditorScene"; -import type { MapNode, TransformMode } from "@/types/editor"; +import { EditorControls } from "@/components/editor/EditorControls"; +import { EditorScene } from "@/components/editor/scene/EditorScene"; +import { useEditorHistory } from "@/hooks/editor/useEditorHistory"; +import { useEditorSceneData } from "@/hooks/editor/useEditorSceneData"; +import type { MapNode, SceneData, TransformMode } from "@/types/editor"; + +const SAVE_ERROR_MESSAGE = "Erreur lors de l'enregistrement"; + +function serializeMapNodes(sceneData: SceneData): string { + return JSON.stringify(sceneData.mapNodes, null, 2); +} export function EditorPage(): React.JSX.Element { const { @@ -46,7 +52,7 @@ export function EditorPage(): React.JSX.Element { const handleSaveToServer = useCallback(async () => { if (!sceneData) return; - const json = JSON.stringify(sceneData.mapNodes, null, 2); + const json = serializeMapNodes(sceneData); try { const response = await fetch("/api/save-map", { @@ -58,17 +64,17 @@ export function EditorPage(): React.JSX.Element { if (response.ok) { alert("Map enregistrée avec succès!"); } else { - alert("Erreur lors de l'enregistrement"); + alert(SAVE_ERROR_MESSAGE); } } catch (err) { console.error("Error saving map:", err); - alert("Erreur lors de l'enregistrement"); + alert(SAVE_ERROR_MESSAGE); } }, [sceneData]); const handleExportJson = useCallback(() => { if (!sceneData) return; - const json = JSON.stringify(sceneData.mapNodes, null, 2); + const json = serializeMapNodes(sceneData); const blob = new Blob([json], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); diff --git a/src/pages/page.tsx b/src/pages/page.tsx new file mode 100644 index 0000000..2635e63 --- /dev/null +++ b/src/pages/page.tsx @@ -0,0 +1,21 @@ +import { Suspense } from "react"; +import { Canvas } from "@react-three/fiber"; +import { Crosshair } from "@/components/ui/Crosshair"; +import { InteractPrompt } from "@/components/ui/InteractPrompt"; +import { DebugPerf } from "@/components/debug/DebugPerf"; +import { World } from "@/world/World"; + +export function HomePage(): React.JSX.Element { + return ( + <> + + + + + + + + + + ); +} diff --git a/src/providers/docs/DocsLanguageProvider.tsx b/src/providers/docs/DocsLanguageProvider.tsx new file mode 100644 index 0000000..0f081a2 --- /dev/null +++ b/src/providers/docs/DocsLanguageProvider.tsx @@ -0,0 +1,25 @@ +import { useState } from "react"; +import { + DocsLanguageContext, + type DocsLanguage, +} from "@/contexts/docs/DocsLanguageContext"; + +interface DocsLanguageProviderProps { + children: React.ReactNode; +} + +export function DocsLanguageProvider({ + children, +}: DocsLanguageProviderProps): React.JSX.Element { + const [language, setLanguage] = useState("en"); + + function toggleLanguage(): void { + setLanguage((currentLanguage) => (currentLanguage === "en" ? "fr" : "en")); + } + + return ( + + {children} + + ); +} diff --git a/src/router.tsx b/src/router.tsx new file mode 100644 index 0000000..4bb68a2 --- /dev/null +++ b/src/router.tsx @@ -0,0 +1,68 @@ +import { + Outlet, + createRootRoute, + createRoute, + createRouter, +} from "@tanstack/react-router"; +import { HomePage } from "@/pages/page"; +import { EditorPage } from "@/pages/editor/page"; +import { + DocsArchitectureRoute, + DocsEditorRoute, + DocsFeaturesRoute, + DocsLayoutRoute, + DocsReadmeRoute, + DocsTargetArchitectureRoute, + DocsTechnicalEditorRoute, +} from "@/routes/docs/DocsRouteComponents"; + +const rootRoute = createRootRoute({ + component: Outlet, +}); + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/", + component: HomePage, +}); + +const editorRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/editor", + component: EditorPage, +}); + +const docsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/docs", + component: DocsLayoutRoute, +}); + +const docsChildRoutes = [ + { path: "/", component: DocsReadmeRoute }, + { path: "architecture", component: DocsArchitectureRoute }, + { path: "target-architecture", component: DocsTargetArchitectureRoute }, + { path: "technical-editor", component: DocsTechnicalEditorRoute }, + { path: "features", component: DocsFeaturesRoute }, + { path: "editor", component: DocsEditorRoute }, +].map(({ path, component }) => + createRoute({ + getParentRoute: () => docsRoute, + path, + component, + }), +); + +const routeTree = rootRoute.addChildren([ + indexRoute, + editorRoute, + docsRoute.addChildren(docsChildRoutes), +]); + +export const router = createRouter({ routeTree }); + +declare module "@tanstack/react-router" { + interface Register { + router: typeof router; + } +} diff --git a/src/routes/docs/DocsRouteComponents.tsx b/src/routes/docs/DocsRouteComponents.tsx new file mode 100644 index 0000000..01ef28c --- /dev/null +++ b/src/routes/docs/DocsRouteComponents.tsx @@ -0,0 +1,99 @@ +import { Suspense, lazy } from "react"; + +const LazyDocsLayout = lazy(() => + import("@/components/docs/DocsLayout").then((module) => ({ + default: module.DocsLayout, + })), +); + +const LazyDocsReadmePage = lazy(() => + import("@/pages/docs/page").then((module) => ({ + default: module.DocsReadmePage, + })), +); + +const LazyDocsArchitecturePage = lazy(() => + import("@/pages/docs/architecture/page").then((module) => ({ + default: module.DocsArchitecturePage, + })), +); + +const LazyDocsTargetArchitecturePage = lazy(() => + import("@/pages/docs/target-architecture/page").then((module) => ({ + default: module.DocsTargetArchitecturePage, + })), +); + +const LazyDocsTechnicalEditorPage = lazy(() => + import("@/pages/docs/technical-editor/page").then((module) => ({ + default: module.DocsTechnicalEditorPage, + })), +); + +const LazyDocsFeaturesPage = lazy(() => + import("@/pages/docs/features/page").then((module) => ({ + default: module.DocsFeaturesPage, + })), +); + +const LazyDocsEditorPage = lazy(() => + import("@/pages/docs/editor/page").then((module) => ({ + default: module.DocsEditorPage, + })), +); + +export function DocsLayoutRoute(): React.JSX.Element { + return ( + + + + ); +} + +export function DocsReadmeRoute(): React.JSX.Element { + return ( + + + + ); +} + +export function DocsArchitectureRoute(): React.JSX.Element { + return ( + + + + ); +} + +export function DocsTargetArchitectureRoute(): React.JSX.Element { + return ( + + + + ); +} + +export function DocsTechnicalEditorRoute(): React.JSX.Element { + return ( + + + + ); +} + +export function DocsFeaturesRoute(): React.JSX.Element { + return ( + + + + ); +} + +export function DocsEditorRoute(): React.JSX.Element { + return ( + + + + ); +} diff --git a/src/types/editor.ts b/src/types/editor.ts index 89b601f..73306c4 100644 --- a/src/types/editor.ts +++ b/src/types/editor.ts @@ -1,4 +1,4 @@ -import type { Vector3Tuple } from "@/types/3d"; +import type { Vector3Tuple } from "./three"; export interface MapNode { name: string; diff --git a/src/types/3d.ts b/src/types/three.ts similarity index 100% rename from src/types/3d.ts rename to src/types/three.ts diff --git a/src/utils/editor/loadEditorScene.ts b/src/utils/editor/loadEditorScene.ts index 1ca8896..62e8e4c 100644 --- a/src/utils/editor/loadEditorScene.ts +++ b/src/utils/editor/loadEditorScene.ts @@ -1,4 +1,5 @@ -import type { MapNode, SceneData } from "@/types/editor"; +import type { SceneData } from "@/types/editor"; +import { parseMapNodes } from "@/utils/mapNodeValidation"; const MAP_JSON_PATH = "/map.json"; @@ -16,7 +17,7 @@ export async function createSceneDataFromFiles( throw new Error("Fichier map.json manquant à la racine du dossier"); } - const mapNodes: MapNode[] = JSON.parse(await mapFile.text()); + const mapNodes = parseMapNodes(JSON.parse(await mapFile.text())); const models = new Map(); for (const [path, file] of fileMap.entries()) { diff --git a/src/utils/loadMapSceneData.ts b/src/utils/loadMapSceneData.ts index 9ba2f8f..4735f07 100644 --- a/src/utils/loadMapSceneData.ts +++ b/src/utils/loadMapSceneData.ts @@ -1,7 +1,9 @@ import type { MapNode, SceneData } from "@/types/editor"; +import { parseMapNodes } from "@/utils/mapNodeValidation"; const MAP_JSON_PATH = "/map.json"; const MODEL_FILE_NAME = "model.gltf"; +type ModelEntry = [modelName: string, modelUrl: string]; export async function loadMapSceneData(): Promise { const response = await fetch(MAP_JSON_PATH); @@ -10,7 +12,7 @@ export async function loadMapSceneData(): Promise { return null; } - const mapNodes: MapNode[] = await response.json(); + const mapNodes = parseMapNodes(await response.json()); return createSceneData(mapNodes); } @@ -29,7 +31,8 @@ async function loadMapModelUrls( try { const response = await fetch(modelUrl, { method: "HEAD" }); - return response.ok ? ([modelName, modelUrl] as const) : null; + const modelEntry: ModelEntry = [modelName, modelUrl]; + return response.ok ? modelEntry : null; } catch { return null; } diff --git a/src/utils/mapNodeValidation.ts b/src/utils/mapNodeValidation.ts new file mode 100644 index 0000000..cbe8973 --- /dev/null +++ b/src/utils/mapNodeValidation.ts @@ -0,0 +1,32 @@ +import type { MapNode } from "../types/editor"; + +function isVector3Tuple(value: unknown): value is [number, number, number] { + return ( + Array.isArray(value) && + value.length === 3 && + value.every((item) => typeof item === "number" && Number.isFinite(item)) + ); +} + +export function isMapNode(value: unknown): value is MapNode { + if (typeof value !== "object" || value === null) { + return false; + } + + const node = value as Record; + return ( + typeof node.name === "string" && + typeof node.type === "string" && + isVector3Tuple(node.position) && + isVector3Tuple(node.rotation) && + isVector3Tuple(node.scale) + ); +} + +export function parseMapNodes(value: unknown): MapNode[] { + if (!Array.isArray(value) || !value.every(isMapNode)) { + throw new Error("Invalid map node data"); + } + + return value; +} diff --git a/src/world/Environment.tsx b/src/world/Environment.tsx index 72c12b6..b81a4ef 100644 --- a/src/world/Environment.tsx +++ b/src/world/Environment.tsx @@ -2,7 +2,7 @@ import { Environment as DreiEnvironment } from "@react-three/drei"; import { GAME_SCENE_SKYBOX_PATH, PHYSICS_SCENE_BACKGROUND_COLOR, -} from "@/data/environmentConfig"; +} from "@/data/world/environmentConfig"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; export function Environment(): React.JSX.Element { diff --git a/src/world/GameMap.tsx b/src/world/GameMap.tsx index 967c0a8..dd7faaa 100644 --- a/src/world/GameMap.tsx +++ b/src/world/GameMap.tsx @@ -3,7 +3,7 @@ import { useGLTF } from "@react-three/drei"; import * as THREE from "three"; import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode"; import { loadMapSceneData } from "@/utils/loadMapSceneData"; -import type { OctreeReadyHandler } from "@/types/3d"; +import type { OctreeReadyHandler } from "@/types/three"; import type { MapNode } from "@/types/editor"; interface GameMapProps { @@ -15,7 +15,7 @@ export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { const [isLoading, setIsLoading] = useState(true); const groupRef = useRef(null); - useOctreeGraphNode(groupRef, onOctreeReady); + useOctreeGraphNode(groupRef, onOctreeReady, mapNodes.length); useEffect(() => { const loadMap = async () => { @@ -27,9 +27,19 @@ export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { return; } - setMapNodes( - sceneData.mapNodes.filter((node) => sceneData.models.has(node.name)), + const loadedMapNodes = sceneData.mapNodes.filter((node) => + sceneData.models.has(node.name), ); + const missingModelCount = + sceneData.mapNodes.length - loadedMapNodes.length; + + if (missingModelCount > 0) { + console.warn( + `${missingModelCount} map nodes were skipped because their model files are missing.`, + ); + } + + setMapNodes(loadedMapNodes); } catch (error) { console.error("Error loading map:", error); } finally { @@ -40,15 +50,12 @@ export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { loadMap(); }, []); - if (isLoading) { - return <>; - } - return ( - {mapNodes.map((node, index) => ( - - ))} + {!isLoading && + mapNodes.map((node, index) => ( + + ))} ); } diff --git a/src/world/Lighting.tsx b/src/world/Lighting.tsx index 37be86f..a7905b3 100644 --- a/src/world/Lighting.tsx +++ b/src/world/Lighting.tsx @@ -20,7 +20,7 @@ import { SUN_Z_MAX, SUN_Z_MIN, SUN_Z_STEP, -} from "@/data/lightingConfig"; +} from "@/data/world/lightingConfig"; import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; type LightingState = { diff --git a/src/world/Map.tsx b/src/world/Map.tsx deleted file mode 100644 index 17a7d8f..0000000 --- a/src/world/Map.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useEffect, useRef } from "react"; -import { useThree } from "@react-three/fiber"; -import { useGLTF } from "@react-three/drei"; -import * as THREE from "three"; -import { MAP_DEBUG_BOX_HELPER_COLOR } from "@/data/debugConfig"; -import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode"; -import type { OctreeReadyHandler } from "@/types/3d"; -import { Debug } from "@/utils/debug/Debug"; - -const MAP_PATH = "/models/map/model.gltf"; - -interface MapProps { - onOctreeReady: OctreeReadyHandler; -} - -export function Map({ onOctreeReady }: MapProps): React.JSX.Element { - const { scene: gltfScene } = useGLTF(MAP_PATH); - const groupRef = useRef(null); - const boxHelpersRef = useRef([]); - const { scene } = useThree(); - - useOctreeGraphNode(groupRef, onOctreeReady); - - useEffect(() => { - const debug = Debug.getInstance(); - if (!debug.active || !groupRef.current) return; - - const helpers: THREE.BoxHelper[] = []; - - groupRef.current.traverse((child) => { - if (!(child instanceof THREE.Mesh)) return; - const helper = new THREE.BoxHelper(child, MAP_DEBUG_BOX_HELPER_COLOR); - scene.add(helper); - helpers.push(helper); - }); - - boxHelpersRef.current = helpers; - - return () => { - helpers.forEach((h) => { - scene.remove(h); - h.dispose(); - }); - boxHelpersRef.current = []; - }; - }, [scene]); - - return ( - - - - ); -} - -useGLTF.preload(MAP_PATH); diff --git a/src/world/World.tsx b/src/world/World.tsx index 3798050..b945c38 100644 --- a/src/world/World.tsx +++ b/src/world/World.tsx @@ -3,15 +3,15 @@ import type { Octree } from "three/addons/math/Octree.js"; import { PLAYER_SPAWN_POSITION_GAME, PLAYER_SPAWN_POSITION_PHYSICS, -} from "@/data/playerConfig"; +} from "@/data/player/playerConfig"; import { useCameraMode } from "@/hooks/debug/useCameraMode"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; -import { DebugCameraControls } from "@/utils/debug/scene/DebugCameraControls"; -import { DebugHelpers } from "@/utils/debug/scene/DebugHelpers"; +import { DebugCameraControls } from "@/components/debug/scene/DebugCameraControls"; +import { DebugHelpers } from "@/components/debug/scene/DebugHelpers"; import { Environment } from "@/world/Environment"; import { Lighting } from "@/world/Lighting"; import { GameMap } from "@/world/GameMap"; -import { PlayerComponent } from "@/world/player/PlayerComponent"; +import { Player } from "@/world/player/Player"; import { TestScene } from "@/world/debug/TestScene"; export function World(): React.JSX.Element { @@ -37,7 +37,7 @@ export function World(): React.JSX.Element { )} {cameraMode !== "debug" ? ( - + ) : null} ); diff --git a/src/world/debug/TestScene.tsx b/src/world/debug/TestScene.tsx index 1bf51cf..edf612f 100644 --- a/src/world/debug/TestScene.tsx +++ b/src/world/debug/TestScene.tsx @@ -1,8 +1,8 @@ import { useRef } from "react"; import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier"; import * as THREE from "three"; -import { GrabbableObject } from "@/components/3d/GrabbableObject"; -import { TriggerObject } from "@/components/3d/TriggerObject"; +import { GrabbableObject } from "@/components/three/GrabbableObject"; +import { TriggerObject } from "@/components/three/TriggerObject"; import { TEST_SCENE_FLOOR_COLLIDER_HALF_EXTENTS, TEST_SCENE_FLOOR_POSITION, @@ -19,9 +19,9 @@ import { TEST_SCENE_TRIGGER_ROUGHNESS, TEST_SCENE_TRIGGER_SEGMENTS, TEST_SCENE_TRIGGER_SOUND_PATH, -} from "@/data/testSceneConfig"; +} from "@/data/debug/testSceneConfig"; import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode"; -import type { OctreeReadyHandler } from "@/types/3d"; +import type { OctreeReadyHandler } from "@/types/three"; interface TestSceneProps { onOctreeReady: OctreeReadyHandler; diff --git a/src/world/player/PlayerComponent.tsx b/src/world/player/Player.tsx similarity index 79% rename from src/world/player/PlayerComponent.tsx rename to src/world/player/Player.tsx index 3b950e0..8fdc489 100644 --- a/src/world/player/PlayerComponent.tsx +++ b/src/world/player/Player.tsx @@ -1,19 +1,19 @@ import { useEffect } from "react"; import { useThree } from "@react-three/fiber"; import type { Octree } from "three/addons/math/Octree.js"; -import type { Vector3Tuple } from "@/types/3d"; +import type { Vector3Tuple } from "@/types/three"; import { PlayerCamera } from "@/world/player/PlayerCamera"; import { PlayerController } from "@/world/player/PlayerController"; -interface PlayerComponentProps { +interface PlayerProps { octree: Octree | null; spawnPosition: Vector3Tuple; } -export function PlayerComponent({ +export function Player({ spawnPosition, octree, -}: PlayerComponentProps): React.JSX.Element { +}: PlayerProps): React.JSX.Element { const camera = useThree((state) => state.camera); useEffect(() => { diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 341c6d9..b9b714b 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -11,7 +11,7 @@ import { MOVE_LEFT_KEY, MOVE_RIGHT_KEY, PRIMARY_INTERACT_MOUSE_BUTTON, -} from "@/data/keybindings"; +} from "@/data/input/keybindings"; import { PLAYER_ACCELERATION_MULTIPLIER, PLAYER_AIR_CONTROL_FACTOR, @@ -22,9 +22,9 @@ import { PLAYER_MAX_DELTA, PLAYER_WALK_SPEED, PLAYER_XZ_DAMPING_FACTOR, -} from "@/data/playerConfig"; -import { InteractionManager } from "@/stateManager/InteractionManager"; -import type { Vector3Tuple } from "@/types/3d"; +} from "@/data/player/playerConfig"; +import { InteractionManager } from "@/managers/InteractionManager"; +import type { Vector3Tuple } from "@/types/three"; type Keys = { forward: boolean; @@ -54,6 +54,25 @@ const _up = new THREE.Vector3(0, 1, 0); const _translateVec = new THREE.Vector3(); const _collisionCorrection = new THREE.Vector3(); +function setMovementKey(keys: Keys, key: string, pressed: boolean): boolean { + switch (key.toLowerCase()) { + case MOVE_FORWARD_KEY: + keys.forward = pressed; + return true; + case MOVE_BACKWARD_KEY: + keys.backward = pressed; + return true; + case MOVE_LEFT_KEY: + keys.left = pressed; + return true; + case MOVE_RIGHT_KEY: + keys.right = pressed; + return true; + default: + return false; + } +} + export function PlayerController({ octree, spawnPosition, @@ -89,51 +108,29 @@ export function PlayerController({ const interaction = InteractionManager.getInstance(); const handleKeyDown = (event: KeyboardEvent): void => { - switch (event.key.toLowerCase()) { - case MOVE_FORWARD_KEY: - keys.current.forward = true; - break; - case MOVE_BACKWARD_KEY: - keys.current.backward = true; - break; - case MOVE_LEFT_KEY: - keys.current.left = true; - break; - case MOVE_RIGHT_KEY: - keys.current.right = true; - break; - case JUMP_KEY: - wantsJump.current = true; - break; - case INTERACT_KEY: - if (interaction.getState().focused?.kind === "trigger") { - interaction.pressInteract(); - } - break; - default: - return; + if (setMovementKey(keys.current, event.key, true)) { + event.preventDefault(); + return; + } + + if (event.key === JUMP_KEY) { + wantsJump.current = true; + event.preventDefault(); + return; + } + + if (event.key.toLowerCase() === INTERACT_KEY) { + if (interaction.getState().focused?.kind === "trigger") { + interaction.pressInteract(); + } + event.preventDefault(); } - event.preventDefault(); }; const handleKeyUp = (event: KeyboardEvent): void => { - switch (event.key.toLowerCase()) { - case MOVE_FORWARD_KEY: - keys.current.forward = false; - break; - case MOVE_BACKWARD_KEY: - keys.current.backward = false; - break; - case MOVE_LEFT_KEY: - keys.current.left = false; - break; - case MOVE_RIGHT_KEY: - keys.current.right = false; - break; - default: - return; + if (setMovementKey(keys.current, event.key, false)) { + event.preventDefault(); } - event.preventDefault(); }; const handleMouseDown = (event: MouseEvent): void => { diff --git a/vite.config.ts b/vite.config.ts index e91d93a..4a65037 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,7 @@ import fs from "node:fs"; import { fileURLToPath } from "node:url"; import type { ServerResponse } from "node:http"; import type { Plugin } from "vite"; +import { parseMapNodes } from "./src/utils/mapNodeValidation"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); @@ -22,34 +23,6 @@ function sendJson( .end(JSON.stringify(body)); } -function isVector3(value: unknown): value is [number, number, number] { - return ( - Array.isArray(value) && - value.length === 3 && - value.every((item) => typeof item === "number" && Number.isFinite(item)) - ); -} - -function isMapNode(value: unknown): value is Record { - if (typeof value !== "object" || value === null) { - return false; - } - - const node = value as Record; - - return ( - typeof node.name === "string" && - typeof node.type === "string" && - isVector3(node.position) && - isVector3(node.rotation) && - isVector3(node.scale) - ); -} - -function isMapPayload(value: unknown): boolean { - return Array.isArray(value) && value.every(isMapNode); -} - const saveMapPlugin = (): Plugin => ({ name: "save-map-api", configureServer(server) { @@ -75,7 +48,9 @@ const saveMapPlugin = (): Plugin => ({ try { const data = JSON.parse(Buffer.concat(chunks).toString()); - if (!isMapPayload(data)) { + try { + parseMapNodes(data); + } catch { sendJson(res, 400, { error: "Invalid map payload" }); return; }