diff --git a/package-lock.json b/package-lock.json
index 2a5d0f3..5b633d6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,9 +10,7 @@
"dependencies": {
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.0",
- "@react-three/postprocessing": "^3.0.4",
"@react-three/rapier": "^2.2.0",
- "gsap": "^3.15.0",
"r3f-perf": "^7.2.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
@@ -25,12 +23,11 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"eslint": "^9.39.4",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"lil-gui": "^0.21.0",
+ "madge": "^8.0.0",
"prettier": "^3.8.2",
"typescript": "~6.0.2",
"typescript-eslint": "^8.58.0",
@@ -286,6 +283,20 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@dependents/detective-less": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.1.tgz",
+ "integrity": "sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "gonzales-pe": "^4.3.0",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@dimforge/rapier3d-compat": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.19.2.tgz",
@@ -632,19 +643,6 @@
"url": "https://github.com/sponsors/Boshen"
}
},
- "node_modules/@pkgr/core": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
- "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/pkgr"
- }
- },
"node_modules/@radix-ui/react-icons": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz",
@@ -754,32 +752,6 @@
}
}
},
- "node_modules/@react-three/postprocessing": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-3.0.4.tgz",
- "integrity": "sha512-e4+F5xtudDYvhxx3y0NtWXpZbwvQ0x1zdOXWTbXMK6fFLVDd4qucN90YaaStanZGS4Bd5siQm0lGL/5ogf8iDQ==",
- "license": "MIT",
- "dependencies": {
- "maath": "^0.6.0",
- "n8ao": "^1.9.4",
- "postprocessing": "^6.36.6"
- },
- "peerDependencies": {
- "@react-three/fiber": "^9.0.0",
- "react": "^19.0",
- "three": ">= 0.156.0"
- }
- },
- "node_modules/@react-three/postprocessing/node_modules/maath": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz",
- "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==",
- "license": "MIT",
- "peerDependencies": {
- "@types/three": ">=0.144.0",
- "three": ">=0.144.0"
- }
- },
"node_modules/@react-three/rapier": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@react-three/rapier/-/rapier-2.2.0.tgz",
@@ -1086,6 +1058,96 @@
"react": ">= 16.3.0"
}
},
+ "node_modules/@ts-graphviz/adapter": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ts-graphviz/adapter/-/adapter-2.0.6.tgz",
+ "integrity": "sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ts-graphviz/common": "^2.1.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ts-graphviz/ast": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@ts-graphviz/ast/-/ast-2.0.7.tgz",
+ "integrity": "sha512-e6+2qtNV99UT6DJSoLbHfkzfyqY84aIuoV8Xlb9+hZAjgpum8iVHprGeAMQ4rF6sKUAxrmY8rfF/vgAwoPc3gw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ts-graphviz/common": "^2.1.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ts-graphviz/common": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@ts-graphviz/common/-/common-2.1.5.tgz",
+ "integrity": "sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ts-graphviz/core": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@ts-graphviz/core/-/core-2.0.7.tgz",
+ "integrity": "sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ts-graphviz/ast": "^2.0.7",
+ "@ts-graphviz/common": "^2.1.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@tweenjs/tween.js": {
"version": "23.1.3",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
@@ -1143,6 +1205,7 @@
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -1556,6 +1619,67 @@
}
}
},
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.32",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.32.tgz",
+ "integrity": "sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@vue/shared": "3.5.32",
+ "entities": "^7.0.1",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.32",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.32.tgz",
+ "integrity": "sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.32",
+ "@vue/shared": "3.5.32"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.32",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.32.tgz",
+ "integrity": "sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@vue/compiler-core": "3.5.32",
+ "@vue/compiler-dom": "3.5.32",
+ "@vue/compiler-ssr": "3.5.32",
+ "@vue/shared": "3.5.32",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.8",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.32",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.32.tgz",
+ "integrity": "sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.32",
+ "@vue/shared": "3.5.32"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.32",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.32.tgz",
+ "integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@webgpu/types": {
"version": "0.1.69",
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz",
@@ -1602,6 +1726,16 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -1618,6 +1752,20 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/app-module-path": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz",
+ "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -1625,6 +1773,16 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/ast-module-types": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.1.tgz",
+ "integrity": "sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1674,6 +1832,43 @@
"require-from-string": "^2.0.2"
}
},
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bl/node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
@@ -1804,6 +1999,42 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/cli-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+ "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/clone": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1824,6 +2055,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1874,6 +2122,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/debug": {
@@ -1894,6 +2143,16 @@
}
}
},
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -1901,6 +2160,62 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/defaults": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
+ "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/dependency-tree": {
+ "version": "11.4.0",
+ "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-11.4.0.tgz",
+ "integrity": "sha512-r4wZ1pfv8eQrnoWbIGdrJTVmlb0dkXdwBjKsotKO4gmfqrOsAMG+0+cfA5EZ3NO8umc85twXOl1eO27E5pjTzw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^12.1.0",
+ "filing-cabinet": "^5.2.0",
+ "precinct": "^12.2.0",
+ "typescript": "^5.9.3"
+ },
+ "bin": {
+ "dependency-tree": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/dependency-tree/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/dependency-tree/node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/detect-gpu": {
"version": "5.0.70",
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
@@ -1920,6 +2235,147 @@
"node": ">=8"
}
},
+ "node_modules/detective-amd": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.0.1.tgz",
+ "integrity": "sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-module-types": "^6.0.1",
+ "escodegen": "^2.1.0",
+ "get-amd-module-type": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "bin": {
+ "detective-amd": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-cjs": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-6.1.0.tgz",
+ "integrity": "sha512-Qt3S4IddVNDb+71lm+jmt5NznIsgcKlibTnrw9Zr91rT9vRwKp+73+ImqLTNrQj4YuOxnzrC7GwIAVwF7136XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-module-types": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-es6": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-5.0.1.tgz",
+ "integrity": "sha512-XusTPuewnSUdoxRSx8OOI6xIA/uld/wMQwYsouvFN2LAg7HgP06NF1lHRV3x6BZxyL2Kkoih4ewcq8hcbGtwew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-postcss": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-7.0.1.tgz",
+ "integrity": "sha512-bEOVpHU9picRZux5XnwGsmCN4+8oZo7vSW0O0/Enq/TO5R2pIAP2279NsszpJR7ocnQt4WXU0+nnh/0JuK4KHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-url": "^1.2.4",
+ "postcss-values-parser": "^6.0.2"
+ },
+ "engines": {
+ "node": "^14.0.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.47"
+ }
+ },
+ "node_modules/detective-sass": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-6.0.1.tgz",
+ "integrity": "sha512-jSGPO8QDy7K7pztUmGC6aiHkexBQT4GIH+mBAL9ZyBmnUIOFbkfZnO8wPRRJFP/QP83irObgsZHCoDHZ173tRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "gonzales-pe": "^4.3.0",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-scss": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-5.0.1.tgz",
+ "integrity": "sha512-MAyPYRgS6DCiS6n6AoSBJXLGVOydsr9huwXORUlJ37K3YLyiN0vYHpzs3AdJOgHobBfispokoqrEon9rbmKacg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "gonzales-pe": "^4.3.0",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-stylus": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-5.0.1.tgz",
+ "integrity": "sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/detective-typescript": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-14.0.0.tgz",
+ "integrity": "sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "^8.23.0",
+ "ast-module-types": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "typescript": "^5.4.4"
+ }
+ },
+ "node_modules/detective-vue2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/detective-vue2/-/detective-vue2-2.2.0.tgz",
+ "integrity": "sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@dependents/detective-less": "^5.0.1",
+ "@vue/compiler-sfc": "^3.5.13",
+ "detective-es6": "^5.0.1",
+ "detective-sass": "^6.0.1",
+ "detective-scss": "^5.0.1",
+ "detective-stylus": "^5.0.1",
+ "detective-typescript": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "typescript": "^5.4.4"
+ }
+ },
"node_modules/draco3d": {
"version": "1.5.7",
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
@@ -1933,6 +2389,43 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
+ "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -1956,6 +2449,28 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
"node_modules/eslint": {
"version": "9.39.4",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
@@ -2016,53 +2531,6 @@
}
}
},
- "node_modules/eslint-config-prettier": {
- "version": "10.1.8",
- "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
- "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "eslint-config-prettier": "bin/cli.js"
- },
- "funding": {
- "url": "https://opencollective.com/eslint-config-prettier"
- },
- "peerDependencies": {
- "eslint": ">=7.0.0"
- }
- },
- "node_modules/eslint-plugin-prettier": {
- "version": "5.5.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
- "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "prettier-linter-helpers": "^1.0.1",
- "synckit": "^0.11.12"
- },
- "engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint-plugin-prettier"
- },
- "peerDependencies": {
- "@types/eslint": ">=8.0.0",
- "eslint": ">=8.0.0",
- "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
- "prettier": ">=3.0.0"
- },
- "peerDependenciesMeta": {
- "@types/eslint": {
- "optional": true
- },
- "eslint-config-prettier": {
- "optional": true
- }
- }
- },
"node_modules/eslint-plugin-react-hooks": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
@@ -2141,6 +2609,20 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/esquery": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
@@ -2177,6 +2659,13 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -2200,13 +2689,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/fast-diff": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
- "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
- "dev": true,
- "license": "Apache-2.0"
- },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -2258,6 +2740,56 @@
"node": ">=16.0.0"
}
},
+ "node_modules/filing-cabinet": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.3.0.tgz",
+ "integrity": "sha512-2EwtzdQkC37FJxDOrKuEOplTFzzaToCqzT008DrIWW27RQ6psxitfUi6hct5mUhMHO7C6xopOhxubyjyPCapbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "app-module-path": "^2.2.0",
+ "commander": "^12.1.0",
+ "enhanced-resolve": "^5.20.1",
+ "module-definition": "^6.0.1",
+ "module-lookup-amd": "^9.1.1",
+ "resolve": "^1.22.12",
+ "resolve-dependency-path": "^4.0.1",
+ "sass-lookup": "^6.1.1",
+ "stylus-lookup": "^6.1.1",
+ "tsconfig-paths": "^4.2.0",
+ "typescript": "^5.9.3"
+ },
+ "bin": {
+ "filing-cabinet": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/filing-cabinet/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/filing-cabinet/node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -2311,6 +2843,16 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -2321,6 +2863,27 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-amd-module-type": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-6.0.2.tgz",
+ "integrity": "sha512-7zShVYAYtMnj9S65CfN+hvpBCByfuB1OY8xID01nZEzXTZbx4YyysAfi+nMl95JSR6odt4q8TCj2W63KAoyVLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-module-types": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/get-own-enumerable-property-symbols": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
+ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2353,11 +2916,28 @@
"integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
"license": "MIT"
},
- "node_modules/gsap": {
- "version": "3.15.0",
- "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
- "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
- "license": "Standard 'no charge' license: https://gsap.com/standard-license."
+ "node_modules/gonzales-pe": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz",
+ "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "gonzales": "bin/gonzales.js"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
},
"node_modules/has-flag": {
"version": "4.0.0",
@@ -2369,6 +2949,19 @@
"node": ">=8"
}
},
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
@@ -2455,6 +3048,36 @@
"node": ">=0.8.19"
}
},
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -2478,12 +3101,75 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-interactive": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+ "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"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/is-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+ "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-url": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-url-superb": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz",
+ "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -2904,6 +3590,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -2936,6 +3639,55 @@
"three": ">=0.134.0"
}
},
+ "node_modules/madge": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/madge/-/madge-8.0.0.tgz",
+ "integrity": "sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "commander": "^7.2.0",
+ "commondir": "^1.0.1",
+ "debug": "^4.3.4",
+ "dependency-tree": "^11.0.0",
+ "ora": "^5.4.1",
+ "pluralize": "^8.0.0",
+ "pretty-ms": "^7.0.1",
+ "rc": "^1.2.8",
+ "stream-to-array": "^2.3.0",
+ "ts-graphviz": "^2.1.2",
+ "walkdir": "^0.4.1"
+ },
+ "bin": {
+ "madge": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://www.paypal.me/pahen"
+ },
+ "peerDependencies": {
+ "typescript": "^5.4.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/meshline": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
@@ -2951,6 +3703,16 @@
"integrity": "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==",
"license": "MIT"
},
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
@@ -2964,6 +3726,61 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/module-definition": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.1.tgz",
+ "integrity": "sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-module-types": "^6.0.1",
+ "node-source-walk": "^7.0.1"
+ },
+ "bin": {
+ "module-definition": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/module-lookup-amd": {
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-9.1.1.tgz",
+ "integrity": "sha512-JzXhQvud8K3yT9l24XTDMXMQ4/LD9a9oXBcbLP0ubdvBpVrGFsybm5+2PDIl0negUYP1l88fCgjQzoMMg247+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^12.1.0",
+ "requirejs": "^2.3.8",
+ "requirejs-config-file": "^4.0.0"
+ },
+ "bin": {
+ "lookup-amd": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/module-lookup-amd/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -2971,16 +3788,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/n8ao": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/n8ao/-/n8ao-1.10.1.tgz",
- "integrity": "sha512-hhI1pC+BfOZBV1KMwynBrVlIm8wqLxj/abAWhF2nZ0qQKyzTSQa1QtLVS2veRiuoBQXojxobcnp0oe+PUoxf/w==",
- "license": "ISC",
- "peerDependencies": {
- "postprocessing": ">=6.30.0",
- "three": ">=0.137"
- }
- },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -3014,6 +3821,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/node-source-walk": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-7.0.1.tgz",
+ "integrity": "sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.26.7"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -3023,6 +3843,22 @@
"node": ">=0.10.0"
}
},
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -3041,6 +3877,30 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/ora": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
+ "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bl": "^4.1.0",
+ "chalk": "^4.1.0",
+ "cli-cursor": "^3.1.0",
+ "cli-spinners": "^2.5.0",
+ "is-interactive": "^1.0.0",
+ "is-unicode-supported": "^0.1.0",
+ "log-symbols": "^4.1.0",
+ "strip-ansi": "^6.0.0",
+ "wcwidth": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -3086,6 +3946,16 @@
"node": ">=6"
}
},
+ "node_modules/parse-ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz",
+ "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -3105,6 +3975,13 @@
"node": ">=8"
}
},
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -3125,6 +4002,16 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.9",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
@@ -3154,13 +4041,22 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/postprocessing": {
- "version": "6.39.0",
- "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.39.0.tgz",
- "integrity": "sha512-/G6JY8hs426lcto/pBZlnFSkyEo1fHsh4gy7FPJtq1SaSUOzJgDW6f6f1K/+aMOYzK/eQEefyOb3++jPPIUeDA==",
- "license": "Zlib",
+ "node_modules/postcss-values-parser": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz",
+ "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "color-name": "^1.1.4",
+ "is-url-superb": "^4.0.0",
+ "quote-unquote": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
"peerDependencies": {
- "three": ">= 0.168.0 < 0.184.0"
+ "postcss": "^8.2.9"
}
},
"node_modules/potpack": {
@@ -3169,6 +4065,60 @@
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
"license": "ISC"
},
+ "node_modules/precinct": {
+ "version": "12.3.0",
+ "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.3.0.tgz",
+ "integrity": "sha512-xHjunvRKzrSOxXzMhNNkqZO4feTob3BO4A85uMpm3UWPC0+ZipVWZrSYbfKrjik8ynBjDrPtH+tdFFuKcuczrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@dependents/detective-less": "^5.0.1",
+ "commander": "^12.1.0",
+ "detective-amd": "^6.0.1",
+ "detective-cjs": "^6.1.0",
+ "detective-es6": "^5.0.1",
+ "detective-postcss": "^7.0.1",
+ "detective-sass": "^6.0.1",
+ "detective-scss": "^5.0.1",
+ "detective-stylus": "^5.0.1",
+ "detective-typescript": "^14.0.0",
+ "detective-vue2": "^2.2.0",
+ "module-definition": "^6.0.1",
+ "node-source-walk": "^7.0.1",
+ "postcss": "^8.5.9",
+ "typescript": "^5.9.3"
+ },
+ "bin": {
+ "precinct": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/precinct/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/precinct/node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -3195,17 +4145,20 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
- "node_modules/prettier-linter-helpers": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
- "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
+ "node_modules/pretty-ms": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
+ "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fast-diff": "^1.1.2"
+ "parse-ms": "^2.1.0"
},
"engines": {
- "node": ">=6.0.0"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/promise-worker-transferable": {
@@ -3239,6 +4192,13 @@
"node": ">=6"
}
},
+ "node_modules/quote-unquote": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz",
+ "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/r3f-perf": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/r3f-perf/-/r3f-perf-7.2.3.tgz",
@@ -3459,6 +4419,32 @@
}
}
},
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/rc/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/react": {
"version": "19.2.5",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
@@ -3501,6 +4487,21 @@
}
}
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -3510,6 +4511,66 @@
"node": ">=0.10.0"
}
},
+ "node_modules/requirejs": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.8.tgz",
+ "integrity": "sha512-7/cTSLOdYkNBNJcDMWf+luFvMriVm7eYxp4BcFCsAX0wF421Vyce5SXP17c+Jd5otXKGNehIonFlyQXSowL6Mw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "r_js": "bin/r.js",
+ "r.js": "bin/r.js"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/requirejs-config-file": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz",
+ "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esprima": "^4.0.0",
+ "stringify-object": "^3.2.1"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-dependency-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-4.0.1.tgz",
+ "integrity": "sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -3520,6 +4581,20 @@
"node": ">=4"
}
},
+ "node_modules/restore-cursor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+ "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/rolldown": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz",
@@ -3561,6 +4636,54 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/sass-lookup": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-6.1.1.tgz",
+ "integrity": "sha512-12dvZdQYTeKZ1ypjuiijZYuMZ1m0F+4+BkRX5yJi2WA9W3DBUrcdCt7bVuKlagHl11n8eYtalWDle+m98Ol2DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^12.1.0",
+ "enhanced-resolve": "^5.20.0"
+ },
+ "bin": {
+ "sass-lookup": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/sass-lookup/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
@@ -3598,6 +4721,24 @@
"node": ">=8"
}
},
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -3634,6 +4775,64 @@
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
"license": "MIT"
},
+ "node_modules/stream-to-array": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz",
+ "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.1.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/stringify-object": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "get-own-enumerable-property-symbols": "^3.0.0",
+ "is-obj": "^1.0.1",
+ "is-regexp": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -3647,6 +4846,32 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/stylus-lookup": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-6.1.1.tgz",
+ "integrity": "sha512-0+xmFLaqWksv5/pMiZtONG6gP82YNGVWgKiQXvw8cdKVFEJ++X9dySGR0hG+A+78PBtbHPqiJzXi2ZKoWr/7Sg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^12.1.0"
+ },
+ "bin": {
+ "stylus-lookup": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/stylus-lookup/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -3660,6 +4885,19 @@
"node": ">=8"
}
},
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/suspend-react": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
@@ -3669,20 +4907,18 @@
"react": ">=17.0"
}
},
- "node_modules/synckit": {
- "version": "0.11.12",
- "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
- "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
+ "node_modules/tapable": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
+ "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@pkgr/core": "^0.2.9"
- },
"engines": {
- "node": "^14.18.0 || >=16.0.0"
+ "node": ">=6"
},
"funding": {
- "url": "https://opencollective.com/synckit"
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
"node_modules/three": {
@@ -3783,6 +5019,47 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/ts-graphviz": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-2.1.6.tgz",
+ "integrity": "sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ts-graphviz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ts-graphviz"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ts-graphviz/adapter": "^2.0.6",
+ "@ts-graphviz/ast": "^2.0.7",
+ "@ts-graphviz/common": "^2.1.5",
+ "@ts-graphviz/core": "^2.0.7"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+ "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json5": "^2.2.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -3936,6 +5213,13 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/utility-types": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
@@ -4023,6 +5307,26 @@
}
}
},
+ "node_modules/walkdir": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz",
+ "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "defaults": "^1.0.3"
+ }
+ },
"node_modules/webgl-constants": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
diff --git a/package.json b/package.json
index 65ae60d..603256c 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"lil-gui": "^0.21.0",
+ "madge": "^8.0.0",
"prettier": "^3.8.2",
"typescript": "~6.0.2",
"typescript-eslint": "^8.58.0",
diff --git a/public/models/map/model.gltf b/public/models/map/model.gltf
index e1fd00c..f477d5c 100644
--- a/public/models/map/model.gltf
+++ b/public/models/map/model.gltf
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7f11632d7daa81f186cf7e9a79631c0aac929c7a1e68ee10676e83837436652f
-size 3220979
+oid sha256:b3535a67501bb43ccf233a25e98b20b3804e29f1fe7ef8ba821bbdd00b98f140
+size 3279070
diff --git a/public/sounds/fa.mp3 b/public/sounds/fa.mp3
new file mode 100644
index 0000000..c0a2b41
--- /dev/null
+++ b/public/sounds/fa.mp3
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5f714331f0f9ad760ae59d1c7cd4a6eb10b853c018f9764564e306b2f2444e56
+size 149972
diff --git a/src/App.tsx b/src/App.tsx
index f0f5d32..1a1b948 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,6 @@
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";
@@ -11,6 +12,7 @@ function App(): React.JSX.Element {
+
>
);
}
diff --git a/src/components/3d/InteractableObject.tsx b/src/components/3d/InteractableObject.tsx
new file mode 100644
index 0000000..c08845f
--- /dev/null
+++ b/src/components/3d/InteractableObject.tsx
@@ -0,0 +1,133 @@
+import { useEffect, useRef } from "react";
+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 type { RefObject } from "react";
+import { Debug } from "@/utils/debug/Debug";
+import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
+import {
+ InteractionManager,
+ type InteractableHandle,
+ type InteractableKind,
+} from "@/stateManager/InteractionManager";
+import { INTERACTION_RADIUS } from "@/data/interactionConfig";
+
+interface InteractableObjectProps {
+ kind: InteractableKind;
+ label: string;
+ position: [number, number, number];
+ rigidBodyType?: "dynamic" | "fixed";
+ colliders?: "cuboid" | "ball" | "hull";
+ rbRef?: RefObject;
+ onPress: () => void;
+ onRelease?: () => void;
+ children: React.ReactNode;
+}
+
+const _cameraPos = new THREE.Vector3();
+const _cameraDir = new THREE.Vector3();
+const _objectPos = new THREE.Vector3();
+const _raycaster = new THREE.Raycaster();
+
+export function InteractableObject({
+ kind,
+ label,
+ position,
+ rigidBodyType = "dynamic",
+ colliders = "cuboid",
+ rbRef,
+ onPress,
+ onRelease = () => {},
+ children,
+}: InteractableObjectProps): React.JSX.Element {
+ const camera = useThree((state) => state.camera);
+ const internalRef = useRef(null);
+ const bodyRef = rbRef ?? internalRef;
+ const groupRef = useRef(null);
+ const debugSphereRef = useRef(null);
+
+ const handle = useRef({
+ kind,
+ label,
+ onPress,
+ onRelease,
+ });
+
+ useEffect(() => {
+ handle.current.onPress = onPress;
+ handle.current.onRelease = onRelease;
+ });
+
+ useDebugFolder("Interaction", (folder) => {
+ folder
+ .add({ radius: INTERACTION_RADIUS }, "radius")
+ .name("Interaction radius")
+ .disable();
+ });
+
+ useFrame(() => {
+ const body = bodyRef.current;
+ const group = groupRef.current;
+ const debug = Debug.getInstance();
+ const manager = InteractionManager.getInstance();
+
+ if (debugSphereRef.current) {
+ debugSphereRef.current.visible =
+ debug.active && debug.getShowInteractionSpheres();
+ }
+
+ if (body) {
+ const t = body.translation();
+ _objectPos.set(t.x, t.y, t.z);
+ } else {
+ _objectPos.set(...position);
+ }
+
+ camera.getWorldPosition(_cameraPos);
+ const dist = _cameraPos.distanceTo(_objectPos);
+
+ if (dist > INTERACTION_RADIUS) {
+ if (manager.getState().focused === handle.current) {
+ manager.setFocused(null);
+ }
+ return;
+ }
+
+ camera.getWorldDirection(_cameraDir);
+ _raycaster.set(_cameraPos, _cameraDir);
+ _raycaster.far = INTERACTION_RADIUS;
+
+ const hits = group ? _raycaster.intersectObject(group, true) : [];
+
+ const validHit = hits.find((h) => h.object !== debugSphereRef.current);
+
+ if (validHit) {
+ manager.setFocused(handle.current);
+ } else if (manager.getState().focused === handle.current) {
+ manager.setFocused(null);
+ }
+ });
+
+ return (
+
+
+ {children}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ui/Crosshair.tsx b/src/components/ui/Crosshair.tsx
index 1b4b54f..dae485e 100644
--- a/src/components/ui/Crosshair.tsx
+++ b/src/components/ui/Crosshair.tsx
@@ -1,11 +1,16 @@
import { useCameraMode } from "@/hooks/debug/useCameraMode";
+import { useInteraction } from "@/hooks/useInteraction";
export function Crosshair(): React.JSX.Element | null {
const cameraMode = useCameraMode();
+ const { focused } = useInteraction();
- if (cameraMode !== "player") {
- return null;
- }
+ if (cameraMode !== "player") return null;
- return ;
+ return (
+
+ );
}
diff --git a/src/components/ui/InteractPrompt.tsx b/src/components/ui/InteractPrompt.tsx
new file mode 100644
index 0000000..8fff76d
--- /dev/null
+++ b/src/components/ui/InteractPrompt.tsx
@@ -0,0 +1,17 @@
+import { useCameraMode } from "@/hooks/debug/useCameraMode";
+import { useInteraction } from "@/hooks/useInteraction";
+
+export function InteractPrompt(): React.JSX.Element | null {
+ const cameraMode = useCameraMode();
+ const { focused, holding } = useInteraction();
+
+ if (cameraMode !== "player") return null;
+ if (!focused || holding || focused.kind !== "trigger") return null;
+
+ return (
+
+ E
+ {focused.label}
+
+ );
+}
diff --git a/src/data/interactionConfig.ts b/src/data/interactionConfig.ts
new file mode 100644
index 0000000..99ee8a8
--- /dev/null
+++ b/src/data/interactionConfig.ts
@@ -0,0 +1 @@
+export const INTERACTION_RADIUS = 3;
diff --git a/src/hooks/debug/useDebugFolder.ts b/src/hooks/debug/useDebugFolder.ts
new file mode 100644
index 0000000..7c19e96
--- /dev/null
+++ b/src/hooks/debug/useDebugFolder.ts
@@ -0,0 +1,17 @@
+import { useEffect } from "react";
+import type GUI from "lil-gui";
+import { Debug } from "@/utils/debug/Debug";
+
+export function useDebugFolder(
+ name: string,
+ setup: (folder: GUI) => void,
+): void {
+ useEffect(() => {
+ const debug = Debug.getInstance();
+ if (!debug.active) return;
+ const folder = debug.createFolder(name);
+ if (!folder) return;
+ setup(folder);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+}
diff --git a/src/hooks/useInteraction.ts b/src/hooks/useInteraction.ts
new file mode 100644
index 0000000..503c2a6
--- /dev/null
+++ b/src/hooks/useInteraction.ts
@@ -0,0 +1,18 @@
+import { useEffect, useState } from "react";
+import {
+ InteractionManager,
+ type InteractionSnapshot,
+} from "@/stateManager/InteractionManager";
+
+export function useInteraction(): InteractionSnapshot {
+ const manager = InteractionManager.getInstance();
+ const [state, setState] = useState(manager.getState());
+
+ useEffect(() => {
+ return manager.subscribe(() => {
+ setState({ ...manager.getState() });
+ });
+ }, [manager]);
+
+ return state;
+}
diff --git a/src/index.css b/src/index.css
index f0b4d3a..8e0c2e2 100644
--- a/src/index.css
+++ b/src/index.css
@@ -31,12 +31,51 @@ canvas {
position: fixed;
top: 50%;
left: 50%;
- width: 12px;
- height: 12px;
- border: 2px solid rgba(255, 255, 255, 0.92);
+ width: 6px;
+ height: 6px;
+ background: rgba(255, 255, 255, 0.92);
border-radius: 999px;
transform: translate(-50%, -50%);
- box-sizing: border-box;
pointer-events: none;
z-index: 10;
}
+
+.crosshair--interact {
+ width: 12px;
+ height: 12px;
+ background: transparent;
+ border: 2px solid rgba(255, 255, 255, 0.92);
+}
+
+.interact-prompt {
+ position: fixed;
+ bottom: 30%;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ pointer-events: none;
+ z-index: 10;
+}
+
+.interact-prompt__key {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ background: rgba(255, 255, 255, 0.15);
+ border: 1px solid rgba(255, 255, 255, 0.5);
+ border-radius: 4px;
+ font-size: 13px;
+ font-weight: 600;
+ color: white;
+ font-style: normal;
+}
+
+.interact-prompt__label {
+ font-size: 13px;
+ color: rgba(255, 255, 255, 0.85);
+ letter-spacing: 0.03em;
+}
diff --git a/src/stateManager/AudioManager.ts b/src/stateManager/AudioManager.ts
new file mode 100644
index 0000000..7d7d418
--- /dev/null
+++ b/src/stateManager/AudioManager.ts
@@ -0,0 +1,23 @@
+export class AudioManager {
+ private static _instance: AudioManager | null = null;
+
+ static getInstance(): AudioManager {
+ if (!AudioManager._instance) {
+ AudioManager._instance = new AudioManager();
+ }
+
+ return AudioManager._instance;
+ }
+
+ private constructor() {}
+
+ playSound(path: string, volume = 1): void {
+ const audio = new Audio(path);
+ audio.volume = Math.max(0, Math.min(1, volume));
+ void audio.play();
+ }
+
+ destroy(): void {
+ AudioManager._instance = null;
+ }
+}
diff --git a/src/stateManager/InteractionManager.ts b/src/stateManager/InteractionManager.ts
new file mode 100644
index 0000000..467a58c
--- /dev/null
+++ b/src/stateManager/InteractionManager.ts
@@ -0,0 +1,87 @@
+export type InteractableKind = "grab" | "trigger";
+
+export interface InteractableHandle {
+ kind: InteractableKind;
+ label: string;
+ onPress: () => void;
+ onRelease: () => void;
+}
+
+export interface InteractionSnapshot {
+ focused: InteractableHandle | null;
+ holding: boolean;
+}
+
+export class InteractionManager {
+ private static _instance: InteractionManager | null = null;
+
+ private _focused: InteractableHandle | null = null;
+ private _holding = false;
+ private _holdingHandle: InteractableHandle | null = null;
+ private readonly _listeners = new Set<() => void>();
+
+ static getInstance(): InteractionManager {
+ if (!InteractionManager._instance) {
+ InteractionManager._instance = new InteractionManager();
+ }
+
+ return InteractionManager._instance;
+ }
+
+ private constructor() {}
+
+ getState(): InteractionSnapshot {
+ return {
+ focused: this._focused,
+ holding: this._holding,
+ };
+ }
+
+ setFocused(handle: InteractableHandle | null): void {
+ if (this._focused === handle) return;
+ // Never interrupt an active grab via focus change
+ if (this._holding) {
+ this._focused = handle;
+ this._emit();
+ return;
+ }
+ this._focused = handle;
+ this._emit();
+ }
+
+ pressInteract(): void {
+ if (!this._focused) return;
+
+ this._holding = this._focused.kind === "grab";
+ if (this._holding) this._holdingHandle = this._focused;
+ this._focused.onPress();
+ this._emit();
+ }
+
+ releaseInteract(): void {
+ const handle = this._holdingHandle ?? this._focused;
+ if (!handle) return;
+
+ handle.onRelease();
+ this._holding = false;
+ this._holdingHandle = null;
+ this._emit();
+ }
+
+ subscribe(listener: () => void): () => void {
+ this._listeners.add(listener);
+
+ return () => {
+ this._listeners.delete(listener);
+ };
+ }
+
+ destroy(): void {
+ this._listeners.clear();
+ InteractionManager._instance = null;
+ }
+
+ private _emit(): void {
+ this._listeners.forEach((cb) => cb());
+ }
+}
diff --git a/src/utils/EventEmitter.ts b/src/utils/EventEmitter.ts
index 52c73a9..5f921a1 100644
--- a/src/utils/EventEmitter.ts
+++ b/src/utils/EventEmitter.ts
@@ -1,18 +1,36 @@
type Listener = (payload: TPayload) => void;
+// TypeScript cannot narrow mapped-type indexed access by a generic key TKey
+// (microsoft/TypeScript#30581). The helper below encapsulates the one necessary
+// cast so the rest of the class stays cast-free.
+type ListenerMap> = {
+ [TKey in keyof TEvents]?: Set>;
+};
+
+function getListeners<
+ TEvents extends Record,
+ TKey extends keyof TEvents,
+>(
+ map: ListenerMap,
+ key: TKey,
+): Set> | undefined {
+ return map[key] as Set> | undefined;
+}
+
export class EventEmitter> {
- private readonly listeners = new Map<
- keyof TEvents,
- Set>
- >();
+ private readonly listeners: ListenerMap = {};
on(
event: TKey,
listener: Listener,
): () => void {
- const currentListeners = this.listeners.get(event) ?? new Set();
- currentListeners.add(listener as Listener);
- this.listeners.set(event, currentListeners);
+ const existing = getListeners(this.listeners, event);
+
+ if (existing) {
+ existing.add(listener);
+ } else {
+ this.listeners[event] = new Set([listener]) as ListenerMap[TKey];
+ }
return () => {
this.off(event, listener);
@@ -23,32 +41,34 @@ export class EventEmitter> {
event: TKey,
listener: Listener,
): void {
- const currentListeners = this.listeners.get(event);
+ const currentListeners = getListeners(this.listeners, event);
if (!currentListeners) {
return;
}
- currentListeners.delete(listener as Listener);
+ currentListeners.delete(listener);
if (currentListeners.size === 0) {
- this.listeners.delete(event);
+ delete this.listeners[event];
}
}
emit(event: TKey, payload: TEvents[TKey]): void {
- const currentListeners = this.listeners.get(event);
+ const currentListeners = getListeners(this.listeners, event);
if (!currentListeners) {
return;
}
currentListeners.forEach((listener) => {
- listener(payload as TEvents[keyof TEvents]);
+ listener(payload);
});
}
clear(): void {
- this.listeners.clear();
+ for (const key of Object.keys(this.listeners) as (keyof TEvents)[]) {
+ delete this.listeners[key];
+ }
}
}
diff --git a/src/utils/debug/Debug.ts b/src/utils/debug/Debug.ts
index aae6b86..e172394 100644
--- a/src/utils/debug/Debug.ts
+++ b/src/utils/debug/Debug.ts
@@ -9,8 +9,12 @@ export class Debug {
private readonly folders = new Map();
private readonly registeredFolders = new Set();
private readonly listeners = new Set<() => void>();
- private readonly controls: { cameraMode: CameraMode } = {
+ private readonly controls: {
+ cameraMode: CameraMode;
+ showInteractionSpheres: boolean;
+ } = {
cameraMode: "player",
+ showInteractionSpheres: false,
};
static getInstance(): Debug {
@@ -28,9 +32,7 @@ export class Debug {
if (this.gui) {
const folder = this.createFolder("Debug");
- if (!folder) {
- return;
- }
+ if (!folder) return;
folder
.add(this.controls, "cameraMode", { Player: "player", Debug: "debug" })
@@ -39,6 +41,14 @@ export class Debug {
this.controls.cameraMode = value;
this.emit();
});
+
+ folder
+ .add(this.controls, "showInteractionSpheres")
+ .name("Interaction Spheres")
+ .onChange((value: boolean) => {
+ this.controls.showInteractionSpheres = value;
+ this.emit();
+ });
}
}
@@ -48,21 +58,15 @@ export class Debug {
* null is returned to avoid duplicating controls under StrictMode double-mount.
*/
createFolder(name: string): GUI | null {
- if (!this.gui) {
- return null;
- }
+ if (!this.gui) return null;
- if (this.registeredFolders.has(name)) {
- return null;
- }
+ if (this.registeredFolders.has(name)) return null;
this.registeredFolders.add(name);
- const existingFolder = this.folders.get(name);
+ const existing = this.folders.get(name);
- if (existingFolder) {
- return existingFolder;
- }
+ if (existing) return existing;
const folder = this.gui.addFolder(name);
this.folders.set(name, folder);
@@ -82,6 +86,10 @@ export class Debug {
return this.controls.cameraMode;
}
+ getShowInteractionSpheres(): boolean {
+ return this.controls.showInteractionSpheres;
+ }
+
private emit(): void {
this.listeners.forEach((listener) => listener());
}
diff --git a/src/world/Lighting.tsx b/src/world/Lighting.tsx
index 5ba14e6..00c5940 100644
--- a/src/world/Lighting.tsx
+++ b/src/world/Lighting.tsx
@@ -1,7 +1,7 @@
-import { useEffect, useRef } from "react";
+import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
import type { AmbientLight, DirectionalLight } from "three";
-import { Debug } from "@/utils/debug/Debug";
+import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
type LightingState = {
ambientIntensity: number;
@@ -23,26 +23,13 @@ export function Lighting(): React.JSX.Element {
const ambient = useRef(null);
const sun = useRef(null);
- useEffect(() => {
- const debug = Debug.getInstance();
-
- if (!debug.active) {
- return;
- }
-
- const folder = debug.createFolder("Lighting");
-
- // null = already registered (StrictMode double-mount), skip adding controls
- if (!folder) {
- return;
- }
-
+ useDebugFolder("Lighting", (folder) => {
folder.add(LIGHTING_STATE, "ambientIntensity", 0, 5, 0.1).name("Ambient");
folder.add(LIGHTING_STATE, "sunIntensity", 0, 8, 0.1).name("Sun Intensity");
folder.add(LIGHTING_STATE, "sunX", -100, 100, 1).name("Sun X");
folder.add(LIGHTING_STATE, "sunY", 0, 150, 1).name("Sun Y");
folder.add(LIGHTING_STATE, "sunZ", -100, 100, 1).name("Sun Z");
- }, []);
+ });
useFrame(() => {
if (ambient.current) {
diff --git a/src/world/World.tsx b/src/world/World.tsx
index 81aea1a..1776aa6 100644
--- a/src/world/World.tsx
+++ b/src/world/World.tsx
@@ -1,8 +1,11 @@
+import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier";
import { useCameraMode } from "@/hooks/debug/useCameraMode";
import { DebugCameraControls } from "@/utils/debug/scene/DebugCameraControls";
import { DebugHelpers } from "@/utils/debug/scene/DebugHelpers";
import { Environment } from "@/world/Environment";
import { Lighting } from "@/world/Lighting";
+import { GrabCube } from "@/world/objects/GrabCube";
+import { TriggerSphere } from "@/world/objects/TriggerSphere";
import { PlayerComponent } from "@/world/player/PlayerComponent";
export function World(): React.JSX.Element {
@@ -14,7 +17,14 @@ export function World(): React.JSX.Element {
{cameraMode === "debug" ? : null}
- {cameraMode === "debug" ? null : }
+
+
+
+
+
+
+ {cameraMode === "debug" ? null : }
+
>
);
}
diff --git a/src/world/objects/GrabCube.tsx b/src/world/objects/GrabCube.tsx
new file mode 100644
index 0000000..22fb707
--- /dev/null
+++ b/src/world/objects/GrabCube.tsx
@@ -0,0 +1,80 @@
+import { useRef } from "react";
+import { useFrame, useThree } from "@react-three/fiber";
+import type { RapierRigidBody } from "@react-three/rapier";
+import * as THREE from "three";
+import { InteractableObject } from "@/components/3d/InteractableObject";
+import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
+
+const CUBE_SIZE = 0.5;
+const HOLD_DISTANCE = 2;
+const SPAWN_POSITION: [number, number, number] = [0, 1, -3];
+
+const params = { stiffness: 15, throwBoost: 1.0 };
+
+const _holdTarget = new THREE.Vector3();
+const _currentPos = new THREE.Vector3();
+const _velocity = new THREE.Vector3();
+
+export function GrabCube(): React.JSX.Element {
+ const camera = useThree((state) => state.camera);
+ const rbRef = useRef(null);
+ const isHolding = useRef(false);
+
+ useDebugFolder("GrabCube", (folder) => {
+ folder.add(params, "stiffness", 1, 50, 1).name("Hold stiffness");
+ folder.add(params, "throwBoost", 0.5, 3.0, 0.1).name("Throw boost");
+ });
+
+ useFrame(() => {
+ if (!isHolding.current || !rbRef.current) return;
+
+ camera.getWorldDirection(_holdTarget);
+ _holdTarget.multiplyScalar(HOLD_DISTANCE).add(camera.position);
+
+ const t = rbRef.current.translation();
+ _currentPos.set(t.x, t.y, t.z);
+
+ _velocity
+ .subVectors(_holdTarget, _currentPos)
+ .multiplyScalar(params.stiffness);
+
+ rbRef.current.setLinvel(
+ { x: _velocity.x, y: _velocity.y, z: _velocity.z },
+ true,
+ );
+ rbRef.current.setAngvel({ x: 0, y: 0, z: 0 }, true);
+ });
+
+ return (
+ {
+ isHolding.current = true;
+ }}
+ onRelease={() => {
+ isHolding.current = false;
+ if (rbRef.current && params.throwBoost !== 1.0) {
+ const v = rbRef.current.linvel();
+ rbRef.current.setLinvel(
+ {
+ x: v.x * params.throwBoost,
+ y: v.y * params.throwBoost,
+ z: v.z * params.throwBoost,
+ },
+ true,
+ );
+ }
+ }}
+ >
+
+
+
+
+
+ );
+}
diff --git a/src/world/objects/TriggerSphere.tsx b/src/world/objects/TriggerSphere.tsx
new file mode 100644
index 0000000..8119dd7
--- /dev/null
+++ b/src/world/objects/TriggerSphere.tsx
@@ -0,0 +1,32 @@
+import { AudioManager } from "@/stateManager/AudioManager";
+import { InteractableObject } from "@/components/3d/InteractableObject";
+
+const SPHERE_RADIUS = 0.4;
+const SPAWN_POSITION: [number, number, number] = [3, 2, -3];
+const SOUND_PATH = "/sounds/fa.mp3";
+
+interface TriggerSphereProps {
+ soundPath?: string;
+}
+
+export function TriggerSphere({
+ soundPath = SOUND_PATH,
+}: TriggerSphereProps): React.JSX.Element {
+ return (
+ {
+ AudioManager.getInstance().playSound(soundPath);
+ }}
+ >
+
+
+
+
+
+ );
+}
diff --git a/src/world/player/PlayerCamera.tsx b/src/world/player/PlayerCamera.tsx
index 1984f8a..324e2da 100644
--- a/src/world/player/PlayerCamera.tsx
+++ b/src/world/player/PlayerCamera.tsx
@@ -6,7 +6,7 @@ export const PLAYER_EYE_HEIGHT = 1.75;
export function PlayerCamera(): React.JSX.Element {
useEffect(() => {
return () => {
- document.exitPointerLock?.();
+ document.exitPointerLock();
};
}, []);
diff --git a/src/world/player/PlayerComponent.tsx b/src/world/player/PlayerComponent.tsx
index f3f941c..9c8440e 100644
--- a/src/world/player/PlayerComponent.tsx
+++ b/src/world/player/PlayerComponent.tsx
@@ -3,13 +3,11 @@ import { useThree } from "@react-three/fiber";
import { PlayerCamera, PLAYER_EYE_HEIGHT } from "@/world/player/PlayerCamera";
import { PlayerController } from "@/world/player/PlayerController";
-const SPAWN_POSITION = { x: 0, y: PLAYER_EYE_HEIGHT, z: 0 };
-
export function PlayerComponent(): React.JSX.Element {
const camera = useThree((state) => state.camera);
useEffect(() => {
- camera.position.set(SPAWN_POSITION.x, SPAWN_POSITION.y, SPAWN_POSITION.z);
+ camera.position.set(0, PLAYER_EYE_HEIGHT, 0);
}, [camera]);
return (
diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx
index 4239bb7..99b1748 100644
--- a/src/world/player/PlayerController.tsx
+++ b/src/world/player/PlayerController.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import * as THREE from "three";
+import { InteractionManager } from "@/stateManager/InteractionManager";
import { PLAYER_EYE_HEIGHT } from "@/world/player/PlayerCamera";
const MOVE_SPEED = 5;
@@ -35,41 +36,89 @@ export function PlayerController(): null {
const up = useRef(new THREE.Vector3(0, 1, 0));
useEffect(() => {
- const handleKeyChange =
- (pressed: boolean) =>
- (event: KeyboardEvent): void => {
- switch (event.key.toLowerCase()) {
- case "z":
- keys.current.forward = pressed;
- break;
- case "s":
- keys.current.backward = pressed;
- break;
- case "q":
- keys.current.left = pressed;
- break;
- case "d":
- keys.current.right = pressed;
- break;
- case " ":
- if (pressed) keys.current.jump = true;
- break;
- default:
- return;
- }
+ const interaction = InteractionManager.getInstance();
- event.preventDefault();
- };
+ const handleKeyDown = (event: KeyboardEvent): void => {
+ switch (event.key.toLowerCase()) {
+ case "z":
+ keys.current.forward = true;
+ break;
+ case "s":
+ keys.current.backward = true;
+ break;
+ case "q":
+ keys.current.left = true;
+ break;
+ case "d":
+ keys.current.right = true;
+ break;
+ case " ":
+ keys.current.jump = true;
+ break;
+ case "e":
+ if (interaction.getState().focused?.kind === "trigger") {
+ interaction.pressInteract();
+ }
+ break;
+ default:
+ return;
+ }
- const handleKeyDown = handleKeyChange(true);
- const handleKeyUp = handleKeyChange(false);
+ event.preventDefault();
+ };
+
+ const handleKeyUp = (event: KeyboardEvent): void => {
+ switch (event.key.toLowerCase()) {
+ case "z":
+ keys.current.forward = false;
+ break;
+ case "s":
+ keys.current.backward = false;
+ break;
+ case "q":
+ keys.current.left = false;
+ break;
+ case "d":
+ keys.current.right = false;
+ break;
+ case "e":
+ if (interaction.getState().focused?.kind === "trigger") {
+ interaction.releaseInteract();
+ }
+ break;
+ default:
+ return;
+ }
+
+ event.preventDefault();
+ };
+
+ const handleMouseDown = (event: MouseEvent): void => {
+ if (event.button !== 0) return;
+
+ if (interaction.getState().focused?.kind === "grab") {
+ interaction.pressInteract();
+ }
+ };
+
+ const handleMouseUp = (event: MouseEvent): void => {
+ if (event.button !== 0) return;
+
+ if (interaction.getState().holding) {
+ interaction.releaseInteract();
+ }
+ };
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
+ document.addEventListener("mousedown", handleMouseDown);
+ document.addEventListener("mouseup", handleMouseUp);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
+ document.removeEventListener("mousedown", handleMouseDown);
+ document.removeEventListener("mouseup", handleMouseUp);
keys.current = { ...DEFAULT_KEYS };
};
}, []);