diff --git a/package-lock.json b/package-lock.json
index 5b13faf..1dda233 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,18 +8,26 @@
"name": "doormile-next",
"version": "0.1.0",
"dependencies": {
+ "@react-three/drei": "^10.7.7",
+ "@react-three/fiber": "^9.6.1",
+ "@react-three/postprocessing": "^3.0.4",
+ "framer-motion": "^12.40.0",
"gsap": "^3.15.0",
+ "lenis": "^1.3.23",
"next": "16.2.6",
"react": "19.2.4",
- "react-dom": "19.2.4"
+ "react-dom": "19.2.4",
+ "three": "^0.171.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
+ "@types/three": "^0.171.0",
"eslint": "^9",
"eslint-config-next": "16.2.6",
+ "puppeteer-core": "^23.11.1",
"tailwindcss": "^4",
"typescript": "^5"
}
@@ -229,6 +237,15 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
@@ -1036,6 +1053,24 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@mediapipe/tasks-vision": {
+ "version": "0.10.17",
+ "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
+ "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@monogrid/gainmap-js": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
+ "integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
+ "license": "MIT",
+ "dependencies": {
+ "promise-worker-transferable": "^1.0.4"
+ },
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
@@ -1247,6 +1282,156 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@puppeteer/browsers": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz",
+ "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "extract-zip": "^2.0.1",
+ "progress": "^2.0.3",
+ "proxy-agent": "^6.5.0",
+ "semver": "^7.6.3",
+ "tar-fs": "^3.0.6",
+ "unbzip2-stream": "^1.4.3",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "browsers": "lib/cjs/main-cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@puppeteer/browsers/node_modules/semver": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
+ "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@react-three/drei": {
+ "version": "10.7.7",
+ "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
+ "integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mediapipe/tasks-vision": "0.10.17",
+ "@monogrid/gainmap-js": "^3.0.6",
+ "@use-gesture/react": "^10.3.1",
+ "camera-controls": "^3.1.0",
+ "cross-env": "^7.0.3",
+ "detect-gpu": "^5.0.56",
+ "glsl-noise": "^0.0.0",
+ "hls.js": "^1.5.17",
+ "maath": "^0.10.8",
+ "meshline": "^3.3.1",
+ "stats-gl": "^2.2.8",
+ "stats.js": "^0.17.0",
+ "suspend-react": "^0.1.3",
+ "three-mesh-bvh": "^0.8.3",
+ "three-stdlib": "^2.35.6",
+ "troika-three-text": "^0.52.4",
+ "tunnel-rat": "^0.1.2",
+ "use-sync-external-store": "^1.4.0",
+ "utility-types": "^3.11.0",
+ "zustand": "^5.0.1"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": "^9.0.0",
+ "react": "^19",
+ "react-dom": "^19",
+ "three": ">=0.159"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/fiber": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz",
+ "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.17.8",
+ "@types/webxr": "*",
+ "base64-js": "^1.5.1",
+ "buffer": "^6.0.3",
+ "its-fine": "^2.0.0",
+ "react-use-measure": "^2.1.7",
+ "scheduler": "^0.27.0",
+ "suspend-react": "^0.1.3",
+ "use-sync-external-store": "^1.4.0",
+ "zustand": "^5.0.3"
+ },
+ "peerDependencies": {
+ "expo": ">=43.0",
+ "expo-asset": ">=8.4",
+ "expo-file-system": ">=11.0",
+ "expo-gl": ">=11.0",
+ "react": ">=19 <19.3",
+ "react-dom": ">=19 <19.3",
+ "react-native": ">=0.78",
+ "three": ">=0.156"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ },
+ "expo-asset": {
+ "optional": true
+ },
+ "expo-file-system": {
+ "optional": true
+ },
+ "expo-gl": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/postprocessing": {
+ "version": "3.0.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/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1534,6 +1719,19 @@
"tailwindcss": "4.3.0"
}
},
+ "node_modules/@tootallnate/quickjs-emscripten": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
+ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tweenjs/tween.js": {
+ "version": "23.1.3",
+ "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
+ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
+ "license": "MIT"
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
@@ -1545,6 +1743,12 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@types/draco3d": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
+ "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
@@ -1576,11 +1780,16 @@
"undici-types": "~6.21.0"
}
},
+ "node_modules/@types/offscreencanvas": {
+ "version": "2019.7.3",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
+ "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
+ "license": "MIT"
+ },
"node_modules/@types/react": {
"version": "19.2.15",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
"integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -1596,6 +1805,52 @@
"@types/react": "^19.2.0"
}
},
+ "node_modules/@types/react-reconciler": {
+ "version": "0.28.9",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
+ "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/stats.js": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
+ "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/three": {
+ "version": "0.171.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.171.0.tgz",
+ "integrity": "sha512-oLuT1SAsT+CUg/wxUTFHo0K3NtJLnx9sJhZWQJp/0uXqFpzSk1hRHmvWvpaAWSfvx2db0lVKZ5/wV0I0isD2mQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tweenjs/tween.js": "~23.1.3",
+ "@types/stats.js": "*",
+ "@types/webxr": "*",
+ "@webgpu/types": "*",
+ "fflate": "~0.8.2",
+ "meshoptimizer": "~0.18.1"
+ }
+ },
+ "node_modules/@types/webxr": {
+ "version": "0.5.24",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
+ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.60.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz",
@@ -2204,6 +2459,30 @@
"win32"
]
},
+ "node_modules/@use-gesture/core": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
+ "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
+ "license": "MIT"
+ },
+ "node_modules/@use-gesture/react": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
+ "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@use-gesture/core": "10.3.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
+ "node_modules/@webgpu/types": {
+ "version": "0.1.70",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.70.tgz",
+ "integrity": "sha512-LFiNHHKMvmAEvwVew3JLJmTdShhbdwRFSImUshGhE2mGE8ybQzIo63l5uRp+YKnNx+8Qno8Kf6gN+DKMreIJCA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
@@ -2227,6 +2506,16 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ajv": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
@@ -2244,6 +2533,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",
@@ -2437,6 +2736,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/ast-types": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
+ "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/ast-types-flow": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
@@ -2490,6 +2802,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/b4a": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
+ "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2497,6 +2824,123 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/bare-events": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz",
+ "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "bare-abort-controller": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-fs": {
+ "version": "4.7.1",
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz",
+ "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.5.4",
+ "bare-path": "^3.0.0",
+ "bare-stream": "^2.6.4",
+ "bare-url": "^2.2.2",
+ "fast-fifo": "^1.3.2"
+ },
+ "engines": {
+ "bare": ">=1.16.0"
+ },
+ "peerDependencies": {
+ "bare-buffer": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-buffer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-os": {
+ "version": "3.9.1",
+ "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz",
+ "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "bare": ">=1.14.0"
+ }
+ },
+ "node_modules/bare-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
+ "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-os": "^3.0.1"
+ }
+ },
+ "node_modules/bare-stream": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz",
+ "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "streamx": "^2.25.0",
+ "teex": "^1.0.1"
+ },
+ "peerDependencies": {
+ "bare-abort-controller": "*",
+ "bare-buffer": "*",
+ "bare-events": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ },
+ "bare-buffer": {
+ "optional": true
+ },
+ "bare-events": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-url": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz",
+ "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/baseline-browser-mapping": {
"version": "2.10.32",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz",
@@ -2509,6 +2953,25 @@
"node": ">=6.0.0"
}
},
+ "node_modules/basic-ftp": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+ "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
@@ -2567,6 +3030,40 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
@@ -2627,6 +3124,19 @@
"node": ">=6"
}
},
+ "node_modules/camera-controls": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.0.tgz",
+ "integrity": "sha512-w5oULNpijgTRH0ARFJJ0R5ct1nUM3R3WP7/b8A6j9uTGpRfnsypc/RBMPQV8JQDPayUe37p/TZZY1PcUr4czOQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.11.0",
+ "npm": ">=10.8.2"
+ },
+ "peerDependencies": {
+ "three": ">=0.126.1"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001793",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
@@ -2664,12 +3174,51 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chromium-bidi": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz",
+ "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mitt": "3.0.1",
+ "zod": "3.23.8"
+ },
+ "peerDependencies": {
+ "devtools-protocol": "*"
+ }
+ },
+ "node_modules/chromium-bidi/node_modules/zod": {
+ "version": "3.23.8",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2704,11 +3253,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -2723,7 +3289,6 @@
"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/damerau-levenshtein": {
@@ -2733,6 +3298,16 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
+ "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/data-view-buffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -2848,6 +3423,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/degenerator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
+ "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.13.4",
+ "escodegen": "^2.1.0",
+ "esprima": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/detect-gpu": {
+ "version": "5.0.70",
+ "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
+ "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
+ "license": "MIT",
+ "dependencies": {
+ "webgl-constants": "^1.1.1"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2858,6 +3457,13 @@
"node": ">=8"
}
},
+ "node_modules/devtools-protocol": {
+ "version": "0.0.1367902",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz",
+ "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -2871,6 +3477,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/draco3d": {
+ "version": "1.5.7",
+ "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
+ "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2900,6 +3512,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/enhanced-resolve": {
"version": "5.22.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz",
@@ -3114,6 +3736,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",
@@ -3474,6 +4118,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",
@@ -3520,6 +4178,37 @@
"node": ">=0.10.0"
}
},
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3527,6 +4216,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fast-glob": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
@@ -3581,6 +4277,22 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
+ "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
+ "license": "MIT"
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -3661,6 +4373,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.40.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz",
+ "integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.40.0",
+ "motion-utils": "^12.39.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3722,6 +4461,16 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -3761,6 +4510,22 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-symbol-description": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
@@ -3792,6 +4557,21 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/get-uri": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
+ "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "basic-ftp": "^5.0.2",
+ "data-uri-to-buffer": "^6.0.2",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -3835,6 +4615,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/glsl-noise": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
+ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
+ "license": "MIT"
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -3972,6 +4758,60 @@
"hermes-estree": "0.25.1"
}
},
+ "node_modules/hls.js": {
+ "version": "1.6.16",
+ "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz",
+ "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3982,6 +4822,12 @@
"node": ">= 4"
}
},
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -4024,6 +4870,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -4208,6 +5064,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-generator-function": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
@@ -4294,6 +5160,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "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-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -4450,7 +5322,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
"license": "ISC"
},
"node_modules/iterator.prototype": {
@@ -4471,6 +5342,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/its-fine": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
+ "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.9"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
"node_modules/jiti": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
@@ -4594,6 +5477,37 @@
"node": ">=0.10"
}
},
+ "node_modules/lenis": {
+ "version": "1.3.23",
+ "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.23.tgz",
+ "integrity": "sha512-YxYq3TJqj9sJNv0V9SkyQHejt14xwyIwgDaaMK89Uf9SxQfIszu+gTQSSphh6BWlLTNVKvvXAGkg+Zf+oFIevg==",
+ "license": "MIT",
+ "workspaces": [
+ "packages/*",
+ "playground",
+ "playground/*"
+ ],
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/darkroomengineering"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": ">=3.0.0",
+ "react": ">=17.0.0",
+ "vue": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "vue": {
+ "optional": true
+ }
+ }
+ },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -4608,6 +5522,15 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -4915,6 +5838,16 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/maath": {
+ "version": "0.10.8",
+ "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
+ "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/three": ">=0.134.0",
+ "three": ">=0.134.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -4945,6 +5878,21 @@
"node": ">= 8"
}
},
+ "node_modules/meshline": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
+ "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.137"
+ }
+ },
+ "node_modules/meshoptimizer": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
+ "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
+ "license": "MIT"
+ },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -4982,6 +5930,28 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/motion-dom": {
+ "version": "12.40.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz",
+ "integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.39.0"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.39.0",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
+ "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -4989,6 +5959,16 @@
"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.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
@@ -5030,6 +6010,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/netmask": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz",
+ "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/next": {
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz",
@@ -5263,6 +6253,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5331,6 +6331,40 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pac-proxy-agent": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
+ "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/quickjs-emscripten": "^0.23.0",
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "get-uri": "^6.0.1",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.6",
+ "pac-resolver": "^7.0.1",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/pac-resolver": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "degenerator": "^5.0.0",
+ "netmask": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -5358,7 +6392,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5371,6 +6404,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -5429,6 +6469,21 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postprocessing": {
+ "version": "6.39.1",
+ "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.39.1.tgz",
+ "integrity": "sha512-R2dG2zy+BAx3USl5EHw+PvnrlbT5PKnZVp3se0HCR0pWH8WQdh742yNG4YWOsq6c0bFpffk0Gd2RqPeoP/wKng==",
+ "license": "Zlib",
+ "peerDependencies": {
+ "three": ">= 0.168.0 < 0.185.0"
+ }
+ },
+ "node_modules/potpack": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
+ "license": "ISC"
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5439,6 +6494,26 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/promise-worker-transferable": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
+ "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "is-promise": "^2.1.0",
+ "lie": "^3.0.2"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5451,6 +6526,54 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-agent": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
+ "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "http-proxy-agent": "^7.0.1",
+ "https-proxy-agent": "^7.0.6",
+ "lru-cache": "^7.14.1",
+ "pac-proxy-agent": "^7.1.0",
+ "proxy-from-env": "^1.1.0",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/proxy-agent/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5461,6 +6584,24 @@
"node": ">=6"
}
},
+ "node_modules/puppeteer-core": {
+ "version": "23.11.1",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.1.tgz",
+ "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.6.1",
+ "chromium-bidi": "0.11.0",
+ "debug": "^4.4.0",
+ "devtools-protocol": "0.0.1367902",
+ "typed-query-selector": "^2.12.0",
+ "ws": "^8.18.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -5510,6 +6651,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/react-use-measure": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
+ "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.13",
+ "react-dom": ">=16.13"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -5554,6 +6710,25 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/resolve": {
"version": "2.0.0-next.7",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz",
@@ -5815,7 +6990,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -5828,7 +7002,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5910,6 +7083,58 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
+ "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.1.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
+ "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "socks": "^2.8.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "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",
@@ -5926,6 +7151,32 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/stats-gl": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
+ "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/three": "*",
+ "three": "^0.170.0"
+ },
+ "peerDependencies": {
+ "@types/three": "*",
+ "three": "*"
+ }
+ },
+ "node_modules/stats-gl/node_modules/three": {
+ "version": "0.170.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
+ "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
+ "license": "MIT"
+ },
+ "node_modules/stats.js": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
+ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
+ "license": "MIT"
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -5940,6 +7191,40 @@
"node": ">= 0.4"
}
},
+ "node_modules/streamx": {
+ "version": "2.26.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.26.0.tgz",
+ "integrity": "sha512-VvNG1K72Po/xwJzxZFnZ++Tbrv4lwSptsbkFuzXCJAYZvCK5nnxsvXU6ajqkv7chyiI1Y0YXq2Jh8Iy8Y7NF/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/string.prototype.includes": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
@@ -6053,6 +7338,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "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",
@@ -6125,6 +7423,15 @@
"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",
+ "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=17.0"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
@@ -6146,6 +7453,99 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/tar-fs": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
+ "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0",
+ "tar-stream": "^3.1.5"
+ },
+ "optionalDependencies": {
+ "bare-fs": "^4.0.1",
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz",
+ "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "bare-fs": "^4.5.5",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
+ "node_modules/teex": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
+ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "streamx": "^2.12.5"
+ }
+ },
+ "node_modules/text-decoder": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
+ "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
+ "node_modules/three": {
+ "version": "0.171.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.171.0.tgz",
+ "integrity": "sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==",
+ "license": "MIT"
+ },
+ "node_modules/three-mesh-bvh": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz",
+ "integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
+ "node_modules/three-stdlib": {
+ "version": "2.36.1",
+ "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
+ "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/draco3d": "^1.4.0",
+ "@types/offscreencanvas": "^2019.6.4",
+ "@types/webxr": "^0.5.2",
+ "draco3d": "^1.4.1",
+ "fflate": "^0.6.9",
+ "potpack": "^1.0.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.128.0"
+ }
+ },
+ "node_modules/three-stdlib/node_modules/fflate": {
+ "version": "0.6.10",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
+ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
+ "license": "MIT"
+ },
+ "node_modules/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
@@ -6207,6 +7607,36 @@
"node": ">=8.0"
}
},
+ "node_modules/troika-three-text": {
+ "version": "0.52.4",
+ "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
+ "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==",
+ "license": "MIT",
+ "dependencies": {
+ "bidi-js": "^1.0.2",
+ "troika-three-utils": "^0.52.4",
+ "troika-worker-utils": "^0.52.0",
+ "webgl-sdf-generator": "1.1.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-three-utils": {
+ "version": "0.52.4",
+ "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz",
+ "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-worker-utils": {
+ "version": "0.52.0",
+ "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
+ "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
+ "license": "MIT"
+ },
"node_modules/ts-api-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
@@ -6252,6 +7682,43 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tunnel-rat": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
+ "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
+ "license": "MIT",
+ "dependencies": {
+ "zustand": "^4.3.2"
+ }
+ },
+ "node_modules/tunnel-rat/node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -6343,6 +7810,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/typed-query-selector": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz",
+ "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -6400,6 +7874,42 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/unbzip2-stream": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
+ "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer": "^5.2.1",
+ "through": "^2.3.8"
+ }
+ },
+ "node_modules/unbzip2-stream/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/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -6486,11 +7996,39 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/utility-types": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
+ "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/webgl-constants": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
+ "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
+ },
+ "node_modules/webgl-sdf-generator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
+ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
+ "license": "MIT"
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -6601,6 +8139,63 @@
"node": ">=0.10.0"
}
},
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -6608,6 +8203,46 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -6643,6 +8278,35 @@
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
+ },
+ "node_modules/zustand": {
+ "version": "5.0.14",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz",
+ "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/package.json b/package.json
index a20b1ee..366566c 100644
--- a/package.json
+++ b/package.json
@@ -9,18 +9,26 @@
"lint": "eslint"
},
"dependencies": {
+ "@react-three/drei": "^10.7.7",
+ "@react-three/fiber": "^9.6.1",
+ "@react-three/postprocessing": "^3.0.4",
+ "framer-motion": "^12.40.0",
"gsap": "^3.15.0",
+ "lenis": "^1.3.23",
"next": "16.2.6",
"react": "19.2.4",
- "react-dom": "19.2.4"
+ "react-dom": "19.2.4",
+ "three": "^0.171.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
+ "@types/three": "^0.171.0",
"eslint": "^9",
"eslint-config-next": "16.2.6",
+ "puppeteer-core": "^23.11.1",
"tailwindcss": "^4",
"typescript": "^5"
}
diff --git a/public/images/home-bg-1.png b/public/images/home-bg-1.png
new file mode 100644
index 0000000..1e7768c
Binary files /dev/null and b/public/images/home-bg-1.png differ
diff --git a/public/images/truck.png b/public/images/truck.png
new file mode 100644
index 0000000..e887162
Binary files /dev/null and b/public/images/truck.png differ
diff --git a/src/app/miletruth/page.tsx b/src/app/miletruth/page.tsx
index d9718e9..b302009 100644
--- a/src/app/miletruth/page.tsx
+++ b/src/app/miletruth/page.tsx
@@ -3,6 +3,8 @@ import MileTruthHero from "../../components/sections/MileTruthHero";
import Workflow1 from "../../components/sections/Workflow1";
import Workflow2 from "../../components/sections/Workflow2";
import Workflow3 from "../../components/sections/Workflow3";
+import OptimizationSection from "../../components/optimization/OptimizationSection";
+import PerformanceSection from "../../components/performance/PerformanceSection";
export const metadata = {
title: "MileTruth – Doormile",
@@ -16,9 +18,11 @@ export default function MileTruthPage() {
diff --git a/src/components/optimization/AICore.tsx b/src/components/optimization/AICore.tsx
new file mode 100644
index 0000000..4aa25f5
--- /dev/null
+++ b/src/components/optimization/AICore.tsx
@@ -0,0 +1,158 @@
+"use client";
+
+import React, { useMemo, useRef } from "react";
+import { useFrame } from "@react-three/fiber";
+import * as THREE from "three";
+import { COLORS } from "./constants";
+import { seeded, smoothstep } from "./math";
+
+type Props = {
+ progress: React.RefObject;
+ reduced?: boolean;
+};
+
+const CORE_Y = 3.4;
+
+type CalcBeamProps = {
+ offset: THREE.Vector3;
+ index: number;
+ progress: React.RefObject;
+ color: string;
+};
+
+function CalculationBeam({ offset, index, progress, color }: CalcBeamProps) {
+ const lineRef = useRef(null);
+ const lineMatRef = useRef(null);
+ const packetRefs = useRef<(THREE.Mesh | null)[]>([]);
+
+ const packetCount = 3;
+ const offsets = useMemo(() => Array.from({ length: packetCount }, (_, k) => k / packetCount), []);
+
+ useFrame((state) => {
+ const p = progress.current ?? 0;
+ const t = state.clock.elapsedTime;
+
+ // Beams are active during AI Scan and Route Generation phases (progress 0.22 -> 0.76)
+ const activeFactor = smoothstep(0.20, 0.40, p) * (1 - smoothstep(0.72, 0.85, p));
+ const isVisible = activeFactor > 0.01;
+
+ if (lineRef.current) lineRef.current.visible = isVisible;
+ if (lineMatRef.current && isVisible) {
+ lineMatRef.current.opacity = activeFactor * (0.15 + Math.sin(t * 12 + index) * 0.08);
+ }
+
+ packetRefs.current.forEach((mesh, k) => {
+ if (!mesh) return;
+ mesh.visible = isVisible;
+ if (!isVisible) return;
+
+ const speed = 0.8 + seeded(index * 5 + k) * 0.4;
+ const u = (offsets[k] + t * (speed / 3)) % 1;
+
+ // Linearly interpolate from center [0,0,0] to hub offset
+ mesh.position.copy(offset).multiplyScalar(u);
+
+ const mat = mesh.material as THREE.MeshBasicMaterial;
+ mat.opacity = activeFactor * Math.sin(u * Math.PI) * 0.8;
+
+ const s = 0.04 + Math.sin(t * 8 + k) * 0.015;
+ mesh.scale.setScalar(s / 0.06);
+ });
+ });
+
+ // Pre-generated geometry for the static connecting line
+ const lineGeo = useMemo(() => {
+ const geo = new THREE.BufferGeometry();
+ const pts = new Float32Array([0, 0, 0, offset.x, offset.y, offset.z]);
+ geo.setAttribute("position", new THREE.BufferAttribute(pts, 3));
+ return geo;
+ }, [offset]);
+
+ return (
+
+ {/* Connector line segment */}
+
+
+
+
+ {/* Streams of flowing energy data packets */}
+ {Array.from({ length: packetCount }).map((_, k) => (
+ {
+ packetRefs.current[k] = el;
+ }}
+ visible={false}
+ >
+
+
+
+ ))}
+
+ );
+}
+
+const CalculationBeamMemo = React.memo(CalculationBeam);
+
+/**
+ * The Doormile AI core: a clean floating holographic orb with energy rings,
+ * a particle shell and neural links. It powers up during the AI-scan phase,
+ * fires an expanding radar scan, then a route-optimization burst.
+ */
+function AICore({ progress, reduced = false }: Props) {
+ const root = useRef(null);
+
+ const hubOffsets = useMemo(() => {
+ return Array.from({ length: 5 }, (_, c) => {
+ const baseAngle = (c / 5) * Math.PI * 2 + 0.3;
+ const radius = 7 + seeded(c * 7 + 1) * 3;
+ const cx = Math.cos(baseAngle) * radius;
+ const cz = Math.sin(baseAngle) * radius;
+ return new THREE.Vector3(cx, 0.1 - CORE_Y, cz);
+ });
+ }, []);
+
+ useFrame((state) => {
+ const t = state.clock.elapsedTime;
+
+ if (root.current) {
+ root.current.position.y = CORE_Y + Math.sin(t * 0.8) * 0.12;
+ }
+ });
+
+ return (
+
+ {/* Sleek, subtle dispatch beacon directly above warehouse tower */}
+
+
+
+
+
+ {/* Route Calculation/Analysis Beams */}
+ {hubOffsets.map((offset, i) => (
+
+ ))}
+
+ );
+}
+
+export default React.memo(AICore);
diff --git a/src/components/optimization/HologramCity.tsx b/src/components/optimization/HologramCity.tsx
new file mode 100644
index 0000000..cb24050
--- /dev/null
+++ b/src/components/optimization/HologramCity.tsx
@@ -0,0 +1,191 @@
+"use client";
+
+import React, { useMemo, useRef } from "react";
+import { useFrame } from "@react-three/fiber";
+import * as THREE from "three";
+import { COLORS } from "./constants";
+import { damp, lerp, seeded, smoothstep } from "./math";
+
+type Props = {
+ progress: React.RefObject;
+ count?: number;
+ reduced?: boolean;
+};
+
+const CYAN = new THREE.Color(COLORS.cyan);
+const RED = new THREE.Color(COLORS.red);
+const GREEN = new THREE.Color(COLORS.green);
+
+// Generate deterministic cluster centers matching routes.ts
+const clusterCount = 5;
+const clusterCenters = Array.from({ length: clusterCount }, (_, c) => {
+ const baseAngle = (c / clusterCount) * Math.PI * 2 + 0.3;
+ const radius = 7 + seeded(c * 7 + 1) * 3;
+ const cx = Math.cos(baseAngle) * radius;
+ const cz = Math.sin(baseAngle) * radius;
+ return new THREE.Vector3(cx, 0.1, cz);
+});
+
+function HologramCity({ progress, reduced = false }: Props) {
+ const mainWarehouseRef = useRef(null);
+ const radarRefs = useRef<(THREE.Group | null)[]>([]);
+ const corridorMats = useRef<(THREE.LineBasicMaterial | null)[]>([]);
+ const zoneMats = useRef<(THREE.MeshBasicMaterial | null)[]>([]);
+ const eased = useRef(0);
+
+ // Pre-generate corridor geometries (Warehouse [0, 0, 0] to Cluster Centers)
+ const corridorGeometries = useMemo(() => {
+ return clusterCenters.map((center) => {
+ const pts = [
+ new THREE.Vector3(0, 0.15, 0),
+ new THREE.Vector3(center.x * 0.5, 0.6, center.z * 0.5), // arch up slightly in the middle
+ new THREE.Vector3(center.x, 0.15, center.z),
+ ];
+ return new THREE.CatmullRomCurve3(pts).getPoints(24);
+ });
+ }, []);
+
+ useFrame((state, dt) => {
+ const p = progress.current ?? 0;
+ eased.current = damp(eased.current, p, 3, dt);
+ const e = eased.current;
+ const t = state.clock.elapsedTime;
+
+ // Animate Main Warehouse central beacon rotation
+ if (mainWarehouseRef.current) {
+ mainWarehouseRef.current.position.y = Math.sin(t * 1.2) * 0.04;
+ }
+
+ // Rotate Radar dishes on top of the Regional Hubs
+ radarRefs.current.forEach((radar, idx) => {
+ if (radar) {
+ radar.rotation.y = t * (0.8 + idx * 0.2);
+ }
+ });
+
+ // Animate corridor line opacity and color:
+ // Under unoptimized states (chaos/scan) they are faint or red.
+ // As optimization settles, they light up with active cyan/green.
+ corridorMats.current.forEach((mat, idx) => {
+ if (mat) {
+ const activeColor = idx % 2 === 0 ? CYAN : GREEN;
+ const colorFactor = smoothstep(0.45, 0.8, e);
+ mat.color.copy(RED).lerp(activeColor, colorFactor);
+ mat.opacity = lerp(0.12, 0.7, colorFactor) * (0.8 + Math.sin(t * 4 - idx * 0.5) * 0.2);
+ }
+ });
+
+ // Animate ground delivery zone circles
+ zoneMats.current.forEach((mat, idx) => {
+ if (mat) {
+ const activeColor = idx % 2 === 0 ? CYAN : GREEN;
+ const colorFactor = smoothstep(0.5, 0.82, e);
+ mat.color.copy(RED).lerp(activeColor, colorFactor);
+ mat.opacity = lerp(0.06, 0.22, colorFactor) * (0.85 + Math.sin(t * 3 - idx) * 0.15);
+ }
+ });
+ });
+
+ return (
+
+ {/* Sleek Dark Cyber Ground Grid */}
+
+
+
+
+ {/* Primary Transport Corridors (Warehouse to Cluster Hubs) */}
+ {corridorGeometries.map((points, i) => (
+
+
+ [p.x, p.y, p.z])), 3]}
+ />
+
+ {
+ corridorMats.current[i] = el;
+ }}
+ transparent
+ opacity={0.15}
+ depthWrite={false}
+ />
+
+ ))}
+
+ {/* CENTRAL LOGISTICS ASSET: Main Warehouse Hub */}
+
+ {/* Core foundation structure */}
+
+
+
+
+
+
+
+
+
+ {/* Loading Docks */}
+ {[-0.6, 0, 0.6].map((offset, i) => (
+
+
+
+
+ ))}
+
+ {/* Upper Level / Control Deck */}
+
+
+
+
+
+
+
+
+
+ {/* Rooftop Solar Panels */}
+ {[-0.4, 0.4].map((offset, i) => (
+
+
+
+
+ ))}
+
+ {/* Central Communications Array / Tower */}
+
+ {/* Main rod */}
+
+
+
+
+ {/* Glowing Beacon */}
+
+
+
+
+
+
+ {/* Pulsing ground base glow */}
+
+
+
+
+
+
+ );
+}
+
+export default React.memo(HologramCity);
diff --git a/src/components/optimization/MetricsPanel.tsx b/src/components/optimization/MetricsPanel.tsx
new file mode 100644
index 0000000..8c64bd5
--- /dev/null
+++ b/src/components/optimization/MetricsPanel.tsx
@@ -0,0 +1,152 @@
+"use client";
+
+import React from "react";
+import { motion, useTransform, type MotionValue } from "framer-motion";
+import { COLORS, KPIS, Kpi, rgba } from "./constants";
+
+type Props = {
+ /** shared scroll progress (0 → 1) */
+ scroll: MotionValue;
+};
+
+const COUNT_START = 0.7;
+const COUNT_END = 0.97;
+
+function Metric({ kpi, scroll, index }: { kpi: Kpi; scroll: MotionValue; index: number }) {
+ const [jitter, setJitter] = React.useState(0);
+
+ React.useEffect(() => {
+ const interval = setInterval(() => {
+ // Tiny micro-fluctuation between -0.4% and +0.4%
+ const val = (Math.random() - 0.5) * 0.008;
+ setJitter(val);
+ }, 800 + Math.random() * 600); // slightly staggered updates
+ return () => clearInterval(interval);
+ }, []);
+
+ // Single reactive string — updates without re-rendering React.
+ const value = useTransform(scroll, (v) => {
+ const t = Math.min(1, Math.max(0, (v - COUNT_START) / (COUNT_END - COUNT_START)));
+ let n = kpi.before + (kpi.after - kpi.before) * t;
+
+ // Apply high-frequency live operational fluctuations on settled state
+ if (t > 0.95) {
+ n = n * (1 + jitter);
+ }
+
+ const afterSide = t > 0.5;
+ const prefix = afterSide ? kpi.prefixAfter ?? "" : kpi.prefixBefore ?? "";
+
+ if (kpi.key === "delayed" && kpi.after === 0 && t > 0.95) {
+ return `0`;
+ }
+
+ return `${prefix}${Math.round(n)}${kpi.suffix ?? ""}`;
+ });
+
+ const label = useTransform(scroll, (v) =>
+ v > (COUNT_START + COUNT_END) / 2 ? kpi.labelAfter : kpi.labelBefore,
+ );
+
+ const fromColor = kpi.key === "orders" ? COLORS.cyan : COLORS.red;
+ const toColor = COLORS.green;
+ const accent = useTransform(
+ scroll,
+ [COUNT_START, COUNT_END],
+ [rgba(fromColor, 1), rgba(toColor, 1)],
+ );
+ const glow = useTransform(
+ scroll,
+ [COUNT_START, COUNT_END],
+ [rgba(fromColor, 0.28), rgba(toColor, 0.32)],
+ );
+ const boxShadow = useTransform(glow, (g) => `0 10px 40px -12px ${g}, inset 0 1px 0 rgba(255,255,255,0.06)`);
+ const borderColor = useTransform(
+ scroll,
+ [COUNT_START, COUNT_END],
+ [rgba(fromColor, 0.4), rgba(toColor, 0.45)],
+ );
+
+ // Improvement arrow appears once optimized.
+ const arrowOpacity = useTransform(scroll, [COUNT_END - 0.12, COUNT_END], [0, 1]);
+ // Bar fills as the counter morphs from before → after.
+ const fillScale = useTransform(scroll, [COUNT_START, COUNT_END], [0.12, 1]);
+
+ const trendColor = kpi.goodWhenLower ? COLORS.green : (kpi.key === "orders" ? COLORS.cyan : COLORS.green);
+
+ const sparklinePath = React.useMemo(() => {
+ switch (kpi.key) {
+ case "distance":
+ case "vehicles":
+ return "M 0,3 C 16,3 32,17 64,17"; // Downward trend
+ case "orders":
+ return "M 0,17 C 16,6 32,19 64,8"; // Stable high wavy trend
+ case "delayed":
+ case "cost":
+ return "M 0,1 C 16,1 28,19 64,19"; // Sharp drop
+ default:
+ return "M 0,10 L 64,10";
+ }
+ }, [kpi.key]);
+
+ return (
+
+
+ {label}
+
+ {kpi.goodWhenLower ? "▼" : "▲"}
+
+
+
+ {value}
+
+
+ {/* Sparkline trend graphic */}
+
+
+
+
+
+
+ );
+}
+
+const MetricCard = React.memo(Metric);
+
+function MetricsPanel({ scroll }: Props) {
+ return (
+
+ {KPIS.map((kpi, i) => (
+
+ ))}
+
+ );
+}
+
+export default React.memo(MetricsPanel);
diff --git a/src/components/optimization/OptimizationCanvas.tsx b/src/components/optimization/OptimizationCanvas.tsx
new file mode 100644
index 0000000..c654582
--- /dev/null
+++ b/src/components/optimization/OptimizationCanvas.tsx
@@ -0,0 +1,85 @@
+"use client";
+
+import React, { useMemo, useRef } from "react";
+import { Canvas, useFrame } from "@react-three/fiber";
+import { EffectComposer, Bloom, Vignette } from "@react-three/postprocessing";
+import { KernelSize } from "postprocessing";
+import * as THREE from "three";
+import { COLORS } from "./constants";
+import { damp, lerp, seeded } from "./math";
+import HologramCity from "./HologramCity";
+import RouteSystem from "./RouteSystem";
+import VehicleFleet from "./VehicleFleet";
+import AICore from "./AICore";
+
+type Props = {
+ progress: React.RefObject;
+ reduced?: boolean;
+ isMobile?: boolean;
+ /** Pause the render loop when the section is scrolled off-screen. */
+ active?: boolean;
+};
+
+/** Slow cinematic camera move from a high chaotic view to a settled framing. */
+function CameraRig({ progress }: { progress: React.RefObject }) {
+ const eased = useRef(0);
+ useFrame((state, dt) => {
+ const p = progress.current ?? 0;
+ eased.current = damp(eased.current, p, 1.5, dt);
+ const e = eased.current;
+ const t = state.clock.elapsedTime;
+
+ const radius = lerp(17, 13, e);
+ const angle = lerp(-0.5, 0.45, e) + t * 0.02;
+ const height = lerp(9, 6.5, e) + Math.sin(t * 0.4) * 0.3;
+
+ const cam = state.camera;
+ cam.position.x = Math.sin(angle) * radius;
+ cam.position.z = Math.cos(angle) * radius;
+ cam.position.y = height;
+ cam.lookAt(0, 2.4, 0);
+ });
+ return null;
+}
+
+function OptimizationCanvas({ progress, reduced = false, isMobile = false, active = true }: Props) {
+ const cityCount = isMobile ? 48 : 90;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {/* Bloom is what turns the additive-blended wireframes/beams into a real
+ glowing hologram. Skipped under reduced-motion; lighter on mobile. */}
+ {!reduced && (
+
+
+
+
+ )}
+
+ );
+}
+
+export default React.memo(OptimizationCanvas);
diff --git a/src/components/optimization/OptimizationSection.tsx b/src/components/optimization/OptimizationSection.tsx
new file mode 100644
index 0000000..94150c1
--- /dev/null
+++ b/src/components/optimization/OptimizationSection.tsx
@@ -0,0 +1,881 @@
+"use client";
+
+import React, { useEffect, useRef, useState } from "react";
+import dynamic from "next/dynamic";
+import { motion, AnimatePresence, useMotionValue, useTransform } from "framer-motion";
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+import Lenis from "lenis";
+import {
+ COLORS,
+ PHASE_LABELS,
+ PhaseKey,
+ phaseFromProgress,
+ rgba,
+} from "./constants";
+import MetricsPanel from "./MetricsPanel";
+
+// 3D scene is client-only and code-split so it never blocks first paint.
+const OptimizationCanvas = dynamic(() => import("./OptimizationCanvas"), {
+ ssr: false,
+});
+
+const PHASE_ORDER: PhaseKey[] = [
+ "chaos",
+ "scan",
+ "dissolve",
+ "optimize",
+ "reorganize",
+ "metrics",
+];
+
+const WORKFLOW_STEPS = [
+ { label: "Analyze", icon: "🔍", activateAt: 0 },
+ { label: "Optimize", icon: "⚡", activateAt: 2 },
+ { label: "Assign", icon: "🚛", activateAt: 3 },
+ { label: "Execute", icon: "📡", activateAt: 4 },
+ { label: "Monitor", icon: "📊", activateAt: 5 },
+];
+
+export default function OptimizationSection() {
+ const containerRef = useRef(null);
+ const progressRef = useRef(0);
+ const scroll = useMotionValue(0);
+
+ const [phase, setPhase] = useState("chaos");
+ const [pinState, setPinState] = useState<"before" | "pinned" | "after">("before");
+ const [mountScene, setMountScene] = useState(false);
+ const [sceneActive, setSceneActive] = useState(false);
+ const [isMobile, setIsMobile] = useState(false);
+ const [reduced, setReduced] = useState(false);
+
+ const [orders, setOrders] = useState(59);
+ const [accuracy, setAccuracy] = useState(98.7);
+ const [activeVehicles, setActiveVehicles] = useState(5);
+ const [carbon, setCarbon] = useState(-12.0);
+ const [routeHealth, setRouteHealth] = useState(99.4);
+
+ // Interval timers for high-fidelity live dashboard fluctuations
+ useEffect(() => {
+ const ordersInterval = setInterval(() => {
+ setOrders((prev) => prev + (Math.random() > 0.4 ? 1 : 0));
+ }, 4500);
+
+ const accuracyInterval = setInterval(() => {
+ setAccuracy((prev) => {
+ const delta = (Math.random() - 0.5) * 0.15;
+ const next = prev + delta;
+ return parseFloat(Math.min(99.1, Math.max(98.4, next)).toFixed(2));
+ });
+ }, 2800);
+
+ const vehicleInterval = setInterval(() => {
+ setActiveVehicles((prev) => (prev === 5 ? (Math.random() > 0.5 ? 4 : 5) : (Math.random() > 0.3 ? 5 : 4)));
+ }, 3500);
+
+ const carbonInterval = setInterval(() => {
+ setCarbon((prev) => {
+ const delta = (Math.random() - 0.5) * 0.2;
+ const next = prev + delta;
+ return parseFloat(Math.min(-11.5, Math.max(-12.8, next)).toFixed(1));
+ });
+ }, 3200);
+
+ const healthInterval = setInterval(() => {
+ setRouteHealth((prev) => {
+ const delta = (Math.random() - 0.5) * 0.12;
+ const next = prev + delta;
+ return parseFloat(Math.min(99.9, Math.max(98.8, next)).toFixed(2));
+ });
+ }, 2500);
+
+ return () => {
+ clearInterval(ordersInterval);
+ clearInterval(accuracyInterval);
+ clearInterval(vehicleInterval);
+ clearInterval(carbonInterval);
+ clearInterval(healthInterval);
+ };
+ }, []);
+
+ // Environment detection (client only).
+ useEffect(() => {
+ const mqMobile = window.matchMedia("(max-width: 767px)");
+ const mqReduce = window.matchMedia("(prefers-reduced-motion: reduce)");
+ const sync = () => {
+ setIsMobile(mqMobile.matches);
+ setReduced(mqReduce.matches);
+ };
+ sync();
+ mqMobile.addEventListener("change", sync);
+ mqReduce.addEventListener("change", sync);
+ return () => {
+ mqMobile.removeEventListener("change", sync);
+ mqReduce.removeEventListener("change", sync);
+ };
+ }, []);
+
+ // Mount the WebGL scene when the section approaches the viewport, and pause
+ // its render loop entirely once it scrolls off-screen (battery / 60fps).
+ useEffect(() => {
+ const el = containerRef.current;
+ if (!el) return;
+ const mountIo = new IntersectionObserver(
+ (entries) => {
+ if (entries.some((e) => e.isIntersecting)) {
+ setMountScene(true);
+ // Activate immediately on mount so the render loop starts even on a
+ // direct scroll-jump (the activeIo below only fires on later changes).
+ setSceneActive(true);
+ mountIo.disconnect();
+ }
+ },
+ { rootMargin: "120% 0px" },
+ );
+ const activeIo = new IntersectionObserver(
+ (entries) => setSceneActive(entries.some((e) => e.isIntersecting)),
+ { rootMargin: "10% 0px" },
+ );
+ mountIo.observe(el);
+ activeIo.observe(el);
+ return () => {
+ mountIo.disconnect();
+ activeIo.disconnect();
+ };
+ }, []);
+
+ // Drive the shared scroll progress with GSAP ScrollTrigger.
+ // We pin via our own `position: fixed` (toggled by `pinState`) rather than
+ // GSAP's pin or CSS `position: sticky`. CSS sticky is broken by an ancestor
+ // (.body-container) using `overflow: hidden`, and GSAP's pin offsets the
+ // element by the fixed site-header height. A self-managed fixed element
+ // pins to the viewport top deterministically and ignores ancestor overflow.
+ useEffect(() => {
+ const el = containerRef.current;
+ if (!el) return;
+ gsap.registerPlugin(ScrollTrigger);
+
+ // Smooth scroll (Lenis), driven by a SINGLE rAF source — GSAP's ticker.
+ // Previously lenis.raf() was called from both a manual requestAnimationFrame
+ // loop AND gsap.ticker, double-stepping the integrator every frame, which is
+ // what made scrolling stutter. One source keeps Lenis + ScrollTrigger locked.
+ const lenis = new Lenis({
+ duration: 1.05,
+ easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
+ orientation: "vertical",
+ gestureOrientation: "vertical",
+ smoothWheel: true,
+ });
+
+ lenis.on("scroll", ScrollTrigger.update);
+ const tickerCb = (time: number) => lenis.raf(time * 1000); // ticker is in seconds, Lenis wants ms
+ gsap.ticker.add(tickerCb);
+ gsap.ticker.lagSmoothing(0);
+
+ let lastPhase: PhaseKey = "chaos";
+ let lastPin: "before" | "pinned" | "after" = "before";
+ const st = ScrollTrigger.create({
+ trigger: el,
+ start: "top top",
+ end: "bottom bottom",
+ scrub: 0.4,
+ invalidateOnRefresh: true,
+ onUpdate: (self) => {
+ const p = self.progress;
+ progressRef.current = p;
+ scroll.set(p);
+ const next = phaseFromProgress(p);
+ if (next !== lastPhase) {
+ lastPhase = next;
+ setPhase(next);
+ }
+ const ns = p <= 0.0002 ? "before" : p >= 0.9998 ? "after" : "pinned";
+ if (ns !== lastPin) {
+ lastPin = ns;
+ setPinState(ns);
+ }
+ },
+ });
+
+ const refresh = setTimeout(() => ScrollTrigger.refresh(), 300);
+ return () => {
+ clearTimeout(refresh);
+ st.kill();
+ gsap.ticker.remove(tickerCb);
+ lenis.destroy();
+ };
+ }, [scroll]);
+
+ // Overlay reactions to scroll (no React re-render — direct DOM updates).
+ const leftOpacity = useTransform(scroll, [0.3, 0.55], [1, 0.32]);
+ const leftBlur = useTransform(scroll, [0.3, 0.55], [0, 3]);
+ const leftFilter = useTransform(leftBlur, (b) => `blur(${b}px)`);
+ const rightOpacity = useTransform(scroll, [0.42, 0.66], [0.36, 1]);
+ const scanWidth = useTransform(scroll, [0, 1], ["0%", "100%"]);
+ const scanLineY = useTransform(scroll, [0.2, 0.42], ["8%", "92%"]);
+ const scanLineOpacity = useTransform(scroll, [0.18, 0.22, 0.42, 0.46], [0, 1, 1, 0]);
+ const dividerOpacity = useTransform(scroll, [0.45, 0.6], [0.15, 0.75]);
+
+ const phaseIndex = PHASE_ORDER.indexOf(phase);
+
+ return (
+
+
+
+ {/* Static backdrop (also the canvas loading state) */}
+
+
+ {/* 3D scene */}
+ {mountScene && (
+
+
+
+ )}
+
+ {/* Depth vignette */}
+
+
+ {/* Floating AI Status Badge replacing giant abstract sphere centerpiece */}
+
+ {phase !== "chaos" && (
+
+
+
+
+ {PHASE_LABELS[phase]}
+
+
+
+ )}
+
+
+ {/* Center scan divider */}
+
+
+ {/* AI scan sweep line */}
+
+
+ {/* UI overlay */}
+
+ {/* Header */}
+
+
+ Doormile AI Control Tower
+
+
+ AI Logistics Optimization Engine
+
+
+ Watch Doormile's AI engine transform chaotic logistics into precision-optimized delivery networks — reducing distance, fleet size, delays, and cost in real time.
+
+
+ {/* Workflow step indicators */}
+
+ {WORKFLOW_STEPS.map((step, i) => {
+ const isActive = phaseIndex >= step.activateAt;
+ const isCurrent = phaseIndex === step.activateAt;
+ return (
+
+ {i > 0 && }
+
+ {step.icon}
+ {step.label}
+
+
+ );
+ })}
+
+
+ {/* Scan progress bar */}
+
+
+
+ {/* Side comparison panels */}
+
+
+
+ System: Congested
+
+ Without Optimization
+
+ ✖ Chaotic overlapping routes
+ ✖ Duplicate & idle trips
+ ✖ 8 vehicles required
+ ✖ 23 delivery delays
+ ✖ +18% cost overrun
+
+
+
+
+
+ System: Optimized
+
+ With Doormile AI
+
+ ✔ Optimized route clusters
+ ✔ Intelligent vehicle assignment
+ ✔ Multi-trip & EV planning
+ ✔ Zero delivery delays
+ ✔ 18% cost saved
+ ✔ Carbon footprint reduced
+
+
+
+
+ {/* KPI metrics */}
+
+
+ {/* Bottom insight bar */}
+
+
+ Live Analytics: {orders} Orders
+
+ AI Accuracy: {accuracy}%
+
+ Fleet: {activeVehicles}/5 EV Active
+
+ Route Health: {routeHealth}%
+
+ Carbon: {carbon}%
+
+
+
+
+
+
+
+
+ );
+}
+
+const styles = `
+/* ===== OUTER SECTION: Transparent so the card floats ===== */
+.dm-opt {
+ position: relative;
+ height: 230vh;
+ background: transparent;
+ margin-bottom: 120px;
+}
+.dm-opt-sticky {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ overflow: hidden;
+ background: transparent;
+}
+.dm-opt.is-pinned .dm-opt-sticky { position: fixed; top: 0; left: 0; }
+.dm-opt.is-after .dm-opt-sticky { position: absolute; top: auto; bottom: 0; }
+
+/* ===== FLOATING CARD — the only colored surface ===== */
+.dm-opt-card {
+ position: absolute !important;
+ top: 110px !important;
+ left: 40px !important;
+ right: 40px !important;
+ bottom: 24px !important;
+ border-radius: 60px !important;
+ overflow: hidden !important;
+ background: linear-gradient(165deg, #06101f 0%, #020617 35%, #040d1c 70%, #030a18 100%) !important;
+ border: 1.5px solid ${rgba("#ffffff", 0.08)} !important;
+ box-shadow:
+ 0 0 0 1px ${rgba(COLORS.cyan, 0.04)},
+ 0 4px 30px -4px rgba(0, 0, 0, 0.7),
+ 0 20px 80px -20px rgba(0, 0, 0, 0.6),
+ 0 0 120px -30px ${rgba(COLORS.cyan, 0.08)},
+ inset 0 1px 0 ${rgba("#ffffff", 0.06)},
+ inset 0 -1px 0 ${rgba("#ffffff", 0.02)} !important;
+ box-sizing: border-box !important;
+}
+/* Animated subtle grid pattern */
+.dm-opt-card::before {
+ content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
+ opacity: 0.035;
+ background-image:
+ linear-gradient(${rgba(COLORS.cyan, 0.5)} 1px, transparent 1px),
+ linear-gradient(90deg, ${rgba(COLORS.cyan, 0.5)} 1px, transparent 1px);
+ background-size: 60px 60px;
+ animation: dmOptGridDrift 25s linear infinite;
+}
+@keyframes dmOptGridDrift {
+ 0% { background-position: 0 0; }
+ 100% { background-position: 60px 60px; }
+}
+/* Radial center glow behind 3D scene */
+.dm-opt-card::after {
+ content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
+ background:
+ radial-gradient(ellipse 50% 45% at 50% 48%, ${rgba(COLORS.cyan, 0.08)} 0%, transparent 70%),
+ radial-gradient(ellipse 60% 40% at 50% 90%, ${rgba(COLORS.green, 0.05)} 0%, transparent 60%),
+ radial-gradient(ellipse 80% 50% at 50% 10%, ${rgba(COLORS.cyan, 0.04)} 0%, transparent 50%);
+}
+
+@media (max-width: 1024px) {
+ .dm-opt-card {
+ top: 96px !important;
+ left: 20px !important;
+ right: 20px !important;
+ bottom: 16px !important;
+ border-radius: 42px !important;
+ }
+}
+@media (max-width: 767px) {
+ .dm-opt-card {
+ top: 86px !important;
+ left: 10px !important;
+ right: 10px !important;
+ bottom: 10px !important;
+ border-radius: 28px !important;
+ }
+}
+
+/* ===== INNER LAYERS ===== */
+.dm-opt-backdrop {
+ position: absolute;
+ inset: 0; z-index: 0;
+ background:
+ radial-gradient(100% 70% at 50% 6%, ${rgba(COLORS.cyan, 0.06)} 0%, transparent 55%),
+ radial-gradient(80% 60% at 50% 100%, ${rgba(COLORS.green, 0.05)} 0%, transparent 55%);
+}
+.dm-opt-canvas { position: absolute; inset: 0; z-index: 1; }
+.dm-opt-canvas canvas { display: block; }
+.dm-opt-vignette {
+ position: absolute; inset: 0; z-index: 2; pointer-events: none;
+ background:
+ radial-gradient(110% 90% at 50% 50%, transparent 48%, ${rgba("#020617", 0.88)} 100%),
+ linear-gradient(180deg, ${rgba("#020617", 0.6)} 0%, transparent 20%, transparent 65%, ${rgba("#020617", 0.92)} 100%);
+}
+.dm-opt-divider {
+ position: absolute; left: 50%; top: 14%; bottom: 28%;
+ width: 1px; z-index: 3; pointer-events: none; transform: translateX(-0.5px);
+ background: linear-gradient(180deg, transparent, ${rgba(COLORS.cyan, 0.6)}, transparent);
+ box-shadow: 0 0 16px ${rgba(COLORS.cyan, 0.4)};
+}
+.dm-opt-scanline {
+ position: absolute; left: 6%; right: 6%; height: 2px; z-index: 3; pointer-events: none;
+ background: linear-gradient(90deg, transparent, ${COLORS.cyan}, transparent);
+ box-shadow: 0 0 22px ${rgba(COLORS.cyan, 0.8)};
+}
+
+/* ===== FLOATING AI STATUS BADGE ===== */
+.dm-opt-floating-badge {
+ pointer-events: none;
+}
+.dm-opt-floating-badge__inner {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 18px;
+ border-radius: 999px;
+ background: ${rgba(COLORS.ink, 0.85)};
+ border: 1.5px solid ${rgba(COLORS.cyan, 0.3)};
+ box-shadow:
+ 0 10px 30px -5px rgba(0, 0, 0, 0.65),
+ 0 0 24px -2px ${rgba(COLORS.cyan, 0.18)},
+ inset 0 1px 0 rgba(255, 255, 255, 0.08);
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+ transition: border-color 0.4s ease, box-shadow 0.4s ease;
+}
+.dm-opt-floating-badge__dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: ${COLORS.cyan};
+ box-shadow: 0 0 10px ${COLORS.cyan};
+ animation: dmOptPulse 1.4s ease-in-out infinite;
+ transition: background 0.4s ease, box-shadow 0.4s ease;
+}
+.dm-opt-floating-badge__dot.is-scan {
+ background: ${COLORS.cyan};
+ box-shadow: 0 0 10px ${COLORS.cyan};
+}
+.dm-opt-floating-badge__dot.is-dissolve,
+.dm-opt-floating-badge__dot.is-optimize {
+ background: ${COLORS.amber};
+ box-shadow: 0 0 10px ${COLORS.amber};
+}
+.dm-opt-floating-badge__dot.is-reorganize {
+ background: #C084FC;
+ box-shadow: 0 0 10px #C084FC;
+}
+.dm-opt-floating-badge__dot.is-metrics {
+ background: ${COLORS.green};
+ box-shadow: 0 0 10px ${COLORS.green};
+}
+.dm-opt-floating-badge__text {
+ font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0.16em;
+ text-transform: uppercase;
+ color: #F8FAFC;
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+}
+
+/* ===== UI OVERLAY ===== */
+.dm-opt-ui { position: absolute; inset: 0; z-index: 4; pointer-events: none; }
+.dm-opt-ui h2, .dm-opt-ui h3, .dm-opt-metric__value, .dm-opt-eyebrow, .dm-opt-phase {
+ font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
+}
+
+/* ===== HEADER — compact, no dead space ===== */
+.dm-opt-head {
+ position: absolute; top: clamp(18px, 3vh, 36px); left: 50%;
+ transform: translateX(-50%); width: min(640px, 90vw); text-align: center;
+}
+.dm-opt-eyebrow {
+ display: inline-flex; align-items: center; gap: 7px;
+ font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase;
+ color: ${COLORS.cyan}; padding: 5px 14px; border-radius: 999px;
+ background: ${rgba(COLORS.cyan, 0.06)}; border: 1px solid ${rgba(COLORS.cyan, 0.25)};
+ backdrop-filter: blur(8px);
+}
+.dm-opt-dot { width: 6px; height: 6px; border-radius: 50%; background: ${COLORS.cyan}; box-shadow: 0 0 10px ${COLORS.cyan}; }
+.dm-opt .dm-opt-head h2 {
+ margin: 8px 0 4px !important; padding: 0 !important; color: #F8FAFC !important;
+ font-weight: 700 !important; text-transform: none !important;
+ font-size: clamp(22px, 2.4vw, 36px) !important; line-height: 1.1 !important;
+ letter-spacing: -0.015em !important;
+}
+.dm-opt .dm-opt-head p {
+ margin: 0 auto !important; padding: 0 !important; color: ${COLORS.textDim} !important;
+ max-width: 440px; font-size: clamp(11px, 1vw, 13px) !important; line-height: 1.45 !important;
+}
+
+/* ===== WORKFLOW STEPS ===== */
+.dm-opt-steps {
+ display: flex; align-items: center; justify-content: center; gap: 0;
+ margin-top: 12px; flex-wrap: wrap;
+}
+.dm-opt-steps__pill {
+ display: inline-flex; align-items: center; gap: 4px;
+ padding: 4px 10px; border-radius: 999px; font-weight: 600;
+ letter-spacing: 0.04em; text-transform: uppercase;
+ color: ${COLORS.slate};
+ background: ${rgba(COLORS.ink, 0.5)};
+ border: 1px solid ${rgba(COLORS.slate, 0.2)};
+ backdrop-filter: blur(6px);
+ transition: all 0.45s cubic-bezier(0.22, 1, 0.36, 1);
+}
+.dm-opt-steps__pill.is-active {
+ color: #E2E8F0;
+ background: ${rgba(COLORS.cyan, 0.10)};
+ border-color: ${rgba(COLORS.cyan, 0.35)};
+ box-shadow: 0 0 18px -6px ${rgba(COLORS.cyan, 0.5)};
+}
+.dm-opt-steps__pill.is-current {
+ color: ${COLORS.cyan};
+ border-color: ${rgba(COLORS.cyan, 0.6)};
+ box-shadow: 0 0 24px -4px ${rgba(COLORS.cyan, 0.6)};
+}
+.dm-opt-steps__icon { font-size: 11px; }
+.dm-opt-steps__text { font-size: 9.5px; }
+.dm-opt-steps__line {
+ display: block; width: 16px; height: 1px; margin: 0 2px;
+ background: ${rgba(COLORS.slate, 0.3)};
+ transition: background 0.45s ease;
+}
+.dm-opt-steps__line.is-active {
+ background: linear-gradient(90deg, ${COLORS.cyan}, ${COLORS.green});
+ box-shadow: 0 0 6px ${rgba(COLORS.cyan, 0.5)};
+}
+
+/* ===== PROGRESS BAR ===== */
+.dm-opt-progress { margin-top: 10px; }
+.dm-opt-progress__track {
+ height: 2px; border-radius: 999px; overflow: hidden;
+ background: ${rgba(COLORS.cyan, 0.10)}; max-width: 420px; margin: 0 auto;
+}
+.dm-opt-progress__fill {
+ height: 100%; border-radius: 999px;
+ background: linear-gradient(90deg, ${COLORS.cyan}, ${COLORS.green});
+ box-shadow: 0 0 12px ${rgba(COLORS.cyan, 0.6)};
+}
+.dm-opt-status {
+ display: inline-flex; align-items: center; gap: 8px; margin-top: 8px;
+ padding: 4px 11px; border-radius: 999px;
+ background: ${rgba(COLORS.ink, 0.55)}; border: 1px solid ${rgba(COLORS.cyan, 0.18)};
+ backdrop-filter: blur(8px);
+}
+.dm-opt-status__dot {
+ width: 5px; height: 5px; border-radius: 50%; background: ${COLORS.cyan};
+ box-shadow: 0 0 8px ${COLORS.cyan}; animation: dmOptPulse 1.4s ease-in-out infinite;
+}
+.dm-opt-status__label {
+ font-size: 9.5px; letter-spacing: 0.14em; text-transform: uppercase; color: #E2E8F0; font-weight: 600;
+}
+.dm-opt-status__step { font-size: 9.5px; color: ${COLORS.cyan}; font-weight: 600; }
+
+/* ===== COMPARE PANELS — tighter, stronger ===== */
+.dm-opt-compare {
+ position: absolute; top: 50%; left: 0; right: 0; transform: translateY(-50%);
+ display: flex; justify-content: space-between; align-items: center;
+ gap: 12px; padding: 0 clamp(12px, 2.5vw, 36px);
+}
+.dm-opt-panel {
+ pointer-events: auto; width: clamp(230px, 26vw, 340px);
+ padding: 18px 20px; border-radius: 20px;
+ background: ${rgba(COLORS.ink, 0.72)};
+ border: 1px solid ${rgba(COLORS.slate, 0.22)};
+ backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
+}
+.dm-opt-panel--bad {
+ border-color: ${rgba(COLORS.red, 0.45)};
+ box-shadow:
+ 0 0 30px -8px ${rgba(COLORS.red, 0.35)},
+ 0 20px 50px -20px ${rgba(COLORS.red, 0.25)},
+ inset 0 1px 0 ${rgba(COLORS.red, 0.08)};
+}
+.dm-opt-panel--good {
+ border-color: ${rgba(COLORS.green, 0.45)};
+ box-shadow:
+ 0 0 30px -8px ${rgba(COLORS.green, 0.35)},
+ 0 20px 50px -20px ${rgba(COLORS.green, 0.25)},
+ inset 0 1px 0 ${rgba(COLORS.green, 0.08)};
+}
+.dm-opt .dm-opt-panel h3 {
+ margin: 10px 0 12px !important; padding: 0 !important; color: #F1F5F9 !important;
+ font-size: clamp(15px, 1.4vw, 19px) !important; font-weight: 600 !important;
+ line-height: 1.15 !important; text-transform: none !important; letter-spacing: -0.01em !important;
+}
+.dm-opt .dm-opt-panel ul { list-style: none !important; margin: 0 !important; padding: 0 !important; display: grid; gap: 7px; }
+.dm-opt .dm-opt-panel li {
+ position: relative; padding-left: 0 !important; margin: 0 !important;
+ color: ${COLORS.textDim} !important; font-size: 12.5px !important; line-height: 1.35 !important;
+ display: flex; align-items: center; gap: 8px;
+}
+.dm-opt .dm-opt-panel li::marker { content: "" !important; }
+/* Remove old dot pseudo — now using ✖/✔ markers */
+.dm-opt .dm-opt-panel li::before { content: none !important; display: none !important; }
+
+/* ✖/✔ markers */
+.dm-opt-marker {
+ display: inline-flex; align-items: center; justify-content: center;
+ width: 18px; height: 18px; border-radius: 6px; flex-shrink: 0;
+ font-size: 10px; font-weight: 700; line-height: 1;
+}
+.dm-opt-marker--x {
+ background: ${rgba(COLORS.red, 0.15)};
+ color: ${COLORS.red};
+ border: 1px solid ${rgba(COLORS.red, 0.35)};
+ box-shadow: 0 0 8px ${rgba(COLORS.red, 0.3)};
+}
+.dm-opt-marker--ok {
+ background: ${rgba(COLORS.green, 0.15)};
+ color: ${COLORS.green};
+ border: 1px solid ${rgba(COLORS.green, 0.35)};
+ box-shadow: 0 0 8px ${rgba(COLORS.green, 0.3)};
+}
+
+.dm-opt-panel__badge {
+ display: inline-flex; align-items: center; gap: 7px;
+ font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; font-weight: 700;
+ color: ${COLORS.red}; padding: 5px 10px; border-radius: 999px;
+ background: ${rgba(COLORS.red, 0.1)}; border: 1px solid ${rgba(COLORS.red, 0.35)};
+}
+.dm-opt-panel__badge--good { color: ${COLORS.green}; background: ${rgba(COLORS.green, 0.1)}; border-color: ${rgba(COLORS.green, 0.35)}; }
+.dm-opt-pulse { width: 6px; height: 6px; border-radius: 50%; animation: dmOptPulse 1.4s ease-in-out infinite; }
+.dm-opt-pulse--red { background: ${COLORS.red}; box-shadow: 0 0 10px ${COLORS.red}; }
+.dm-opt-pulse--green { background: ${COLORS.green}; box-shadow: 0 0 10px ${COLORS.green}; }
+@keyframes dmOptPulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.5); opacity: 0.5; } }
+
+/* ===== METRICS ===== */
+.dm-opt-foot {
+ position: absolute; left: 0; right: 0; bottom: clamp(12px, 2.5vh, 28px);
+ padding: 0 clamp(12px, 3vw, 36px);
+}
+.dm-opt-metrics {
+ pointer-events: auto; display: grid; grid-template-columns: repeat(5, 1fr);
+ gap: 10px; max-width: 1100px; margin: 0 auto;
+}
+.dm-opt-metric {
+ position: relative; padding: 14px 14px 12px; border-radius: 16px;
+ background: ${rgba(COLORS.ink, 0.72)};
+ border: 1px solid ${rgba(COLORS.slate, 0.25)};
+ backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
+ overflow: hidden;
+ opacity: 0; transform: translateY(22px);
+ animation: dmOptCardIn 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
+ box-shadow: 0 8px 32px -8px rgba(0, 0, 0, 0.5), inset 0 1px 0 ${rgba("#ffffff", 0.04)};
+ transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1), border-color 0.4s ease, box-shadow 0.4s ease, background 0.4s ease !important;
+ cursor: pointer;
+}
+.dm-opt-metric:hover {
+ transform: translateY(-5px) scale(1.028) !important;
+ background: ${rgba(COLORS.ink, 0.9)} !important;
+ border-color: ${rgba(COLORS.cyan, 0.52)} !important;
+ box-shadow:
+ 0 18px 48px -8px ${rgba(COLORS.cyan, 0.25)},
+ 0 4px 12px -2px ${rgba(COLORS.cyan, 0.12)},
+ inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
+}
+.dm-opt-metric:hover .dm-opt-metric__sparkline {
+ opacity: 0.72 !important;
+}
+.dm-opt-metric__sparkline {
+ transition: opacity 0.4s ease;
+}
+@keyframes dmOptCardIn { to { opacity: 1; transform: translateY(0); } }
+.dm-opt-metric__top { display: flex; align-items: center; justify-content: space-between; }
+.dm-opt-metric__label {
+ font-size: 10px; letter-spacing: 0.04em; color: ${COLORS.textDim}; text-transform: uppercase;
+}
+.dm-opt-metric__arrow { font-size: 11px; }
+.dm-opt-metric__value { margin-top: 6px; font-size: clamp(20px, 2.8vw, 32px); font-weight: 700; line-height: 1; }
+.dm-opt-metric__bar { margin-top: 10px; height: 3px; border-radius: 999px; background: ${rgba(COLORS.slate, 0.2)}; overflow: hidden; }
+.dm-opt-metric__fill { height: 100%; border-radius: 999px; transform-origin: left center; }
+
+/* ===== BOTTOM INSIGHT BAR ===== */
+.dm-opt-insight {
+ display: flex; align-items: center; justify-content: center; gap: 8px;
+ max-width: 760px; margin: 10px auto 0;
+ padding: 7px 18px; border-radius: 999px;
+ background: ${rgba(COLORS.ink, 0.6)};
+ border: 1px solid ${rgba(COLORS.cyan, 0.12)};
+ backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
+ pointer-events: auto;
+}
+.dm-opt-insight__dot {
+ width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
+ background: ${COLORS.green};
+ box-shadow: 0 0 12px ${COLORS.green};
+ animation: dmOptPulse 2s ease-in-out infinite;
+}
+.dm-opt-insight__text {
+ font-size: 10.5px; color: ${COLORS.textDim}; line-height: 1.3;
+ letter-spacing: 0.03em; font-weight: 500;
+}
+.dm-opt-insight__text strong {
+ color: #E2E8F0; font-weight: 700;
+}
+.dm-opt-insight__sep {
+ width: 1px; height: 12px; flex-shrink: 0;
+ background: ${rgba(COLORS.slate, 0.35)};
+}
+
+/* ===== RESPONSIVE ===== */
+@media (max-width: 1024px) {
+ .dm-opt-panel { width: clamp(190px, 30vw, 280px); padding: 14px 16px; }
+ .dm-opt-panel li { font-size: 11.5px; }
+ .dm-opt-marker { width: 16px; height: 16px; font-size: 9px; }
+}
+@media (max-width: 767px) {
+ .dm-opt { height: 200vh; }
+ .dm-opt-card {
+ top: 8px !important; left: 8px !important; right: 8px !important; bottom: 8px !important;
+ border-radius: 24px !important;
+ }
+ .dm-opt-compare {
+ top: auto; bottom: 148px; transform: none;
+ flex-direction: row; align-items: stretch; gap: 6px; padding: 0 10px;
+ }
+ .dm-opt-panel { width: 50%; padding: 10px 11px; border-radius: 14px; }
+ .dm-opt-panel ul { gap: 4px; }
+ .dm-opt-panel li { font-size: 10px; }
+ .dm-opt-panel li::before { display: none !important; }
+ .dm-opt-marker { width: 14px; height: 14px; font-size: 8px; border-radius: 4px; }
+ .dm-opt-panel h3 { margin: 6px 0 5px; font-size: 14px; }
+ .dm-opt-panel__badge { font-size: 8px; padding: 3px 7px; }
+ .dm-opt-metrics { grid-template-columns: repeat(5, 1fr); gap: 4px; }
+ .dm-opt-metric { padding: 7px 5px; border-radius: 10px; }
+ .dm-opt-metric__label { font-size: 7.5px; letter-spacing: 0; }
+ .dm-opt-metric__value { font-size: 14px; }
+ .dm-opt-metric__bar { margin-top: 6px; }
+ .dm-opt-head h2 { font-size: 22px; margin: 6px 0 4px; }
+ .dm-opt-head p { font-size: 10.5px; }
+ .dm-opt-phases { display: none; }
+ .dm-opt-steps { gap: 0; }
+ .dm-opt-steps__pill { padding: 3px 6px; }
+ .dm-opt-steps__icon { font-size: 9px; }
+ .dm-opt-steps__text { font-size: 7.5px; }
+ .dm-opt-steps__line { width: 6px; }
+ .dm-opt-insight { padding: 5px 10px; gap: 5px; }
+ .dm-opt-insight__text { font-size: 8.5px; }
+ .dm-opt-insight__sep { height: 10px; }
+}
+@media (prefers-reduced-motion: reduce) {
+ .dm-opt-pulse { animation: none; }
+ .dm-opt-metric { animation: none; opacity: 1; transform: none; }
+ .dm-opt-insight__dot { animation: none; }
+ .dm-opt-card::before { animation: none; }
+}
+`;
diff --git a/src/components/optimization/RouteSystem.tsx b/src/components/optimization/RouteSystem.tsx
new file mode 100644
index 0000000..3552132
--- /dev/null
+++ b/src/components/optimization/RouteSystem.tsx
@@ -0,0 +1,514 @@
+"use client";
+
+import React, { useMemo, useRef } from "react";
+import { useFrame } from "@react-three/fiber";
+import * as THREE from "three";
+import { COLORS } from "./constants";
+import { buildRoutes } from "./routes";
+import { smoothstep, seeded } from "./math";
+
+type Props = {
+ progress: React.RefObject;
+ reduced?: boolean;
+ isMobile?: boolean;
+};
+
+const RADIAL = 6;
+const TUBULAR = 120;
+
+type TubeHandle = { geo: THREE.TubeGeometry; mat: THREE.MeshBasicMaterial };
+
+type PulsingRingProps = {
+ position: THREE.Vector3;
+ color: string;
+ maxScale?: number;
+ pulseSpeed?: number;
+};
+
+/**
+ * Clean floating radar ring that pulses outwards and fades.
+ */
+function PulsingRing({ position, color, maxScale = 2.2, pulseSpeed = 2.4 }: PulsingRingProps) {
+ const ref = useRef(null);
+ const matRef = useRef(null);
+
+ useFrame((state) => {
+ if (!ref.current || !matRef.current) return;
+ const t = state.clock.elapsedTime;
+ const cycle = (t * (pulseSpeed / 4)) % 1; // 0 -> 1 loop
+ const s = 0.4 + cycle * (maxScale - 0.4);
+ ref.current.scale.setScalar(s);
+ matRef.current.opacity = Math.sin((1 - cycle) * Math.PI) * 0.72;
+ });
+
+ return (
+
+
+
+
+ );
+}
+
+const PulsingRingMemo = React.memo(PulsingRing);
+
+/* ── Chaotic Route Flow Particles & Chevrons ── */
+const CHAOS_TMP_POS = new THREE.Vector3();
+const CHAOS_TMP_TAN = new THREE.Vector3();
+
+function ChaoticFlow({ progress, chaoticCurves }: {
+ progress: React.RefObject;
+ chaoticCurves: THREE.CatmullRomCurve3[];
+}) {
+ const particlesPerCurve = 4;
+ const count = chaoticCurves.length * particlesPerCurve;
+ const particleRefs = useRef<(THREE.Mesh | null)[]>([]);
+ const chevronRefs = useRef<(THREE.Group | null)[]>([]);
+
+ const particleOffsets = useMemo(
+ () => Array.from({ length: count }, (_, i) => seeded(i * 13 + 3)),
+ [count]
+ );
+ const chevronOffsets = useMemo(
+ () => Array.from({ length: chaoticCurves.length * 2 }, (_, i) => (i % 2) / 2),
+ [chaoticCurves.length]
+ );
+
+ useFrame((state) => {
+ const p = progress.current ?? 0;
+ const vis = 1 - smoothstep(0.48, 0.60, p);
+
+ const isVisible = vis > 0.01;
+ particleRefs.current.forEach(m => { if (m) m.visible = isVisible; });
+ chevronRefs.current.forEach(g => { if (g) g.visible = isVisible; });
+ if (!isVisible) return;
+
+ const t = state.clock.elapsedTime;
+
+ // Animate tiny red flow particles
+ for (let i = 0; i < count; i++) {
+ const mesh = particleRefs.current[i];
+ if (!mesh) continue;
+ const curveIdx = Math.floor(i / particlesPerCurve);
+ const curve = chaoticCurves[curveIdx];
+
+ const speed = 0.05 + seeded(i * 9) * 0.03;
+ const u = (particleOffsets[i] + t * speed) % 1;
+
+ curve.getPointAt(u, CHAOS_TMP_POS);
+ mesh.position.copy(CHAOS_TMP_POS);
+
+ const mat = mesh.material as THREE.MeshBasicMaterial;
+ mat.opacity = vis * 0.6 * Math.sin(u * Math.PI);
+ }
+
+ // Animate red chevrons (erratic flow)
+ for (let i = 0; i < chaoticCurves.length * 2; i++) {
+ const group = chevronRefs.current[i];
+ if (!group) continue;
+ const curveIdx = Math.floor(i / 2);
+ const curve = chaoticCurves[curveIdx];
+
+ const u = (chevronOffsets[i] + t * 0.04) % 1;
+ curve.getPointAt(u, CHAOS_TMP_POS);
+ curve.getTangentAt(u, CHAOS_TMP_TAN);
+
+ group.position.copy(CHAOS_TMP_POS);
+ group.rotation.y = Math.atan2(CHAOS_TMP_TAN.x, CHAOS_TMP_TAN.z);
+
+ group.traverse((o) => {
+ const m = (o as THREE.Mesh).material as THREE.MeshBasicMaterial | undefined;
+ if (m && "opacity" in m) {
+ m.opacity = vis * 0.5 * Math.sin(u * Math.PI);
+ }
+ });
+ }
+ });
+
+ return (
+
+ {/* Chaotic Red Flow Particles */}
+ {Array.from({ length: count }).map((_, i) => (
+ { particleRefs.current[i] = el; }}
+ visible={false}
+ >
+
+
+
+ ))}
+
+ {/* Chaotic Red Chevrons */}
+ {Array.from({ length: chaoticCurves.length * 2 }).map((_, i) => (
+ { chevronRefs.current[i] = el; }}
+ visible={false}
+ >
+
+
+
+
+
+ ))}
+
+ );
+}
+
+const ChaoticFlowMemo = React.memo(ChaoticFlow);
+
+/* ── Optimized Route Flow Particles, Chevrons & Delivery Pulses ── */
+const OPT_TMP_POS = new THREE.Vector3();
+const OPT_TMP_TAN = new THREE.Vector3();
+
+function OptimizedFlow({ progress, optimizedCurves }: {
+ progress: React.RefObject;
+ optimizedCurves: THREE.CatmullRomCurve3[];
+}) {
+ const particlesPerCurve = 6;
+ const count = optimizedCurves.length * particlesPerCurve;
+ const particleRefs = useRef<(THREE.Mesh | null)[]>([]);
+ const chevronRefs = useRef<(THREE.Group | null)[]>([]);
+ const pulseRefs = useRef<(THREE.Mesh | null)[]>([]);
+
+ const particleOffsets = useMemo(
+ () => Array.from({ length: count }, (_, i) => seeded(i * 17 + 4)),
+ [count]
+ );
+ const chevronOffsets = useMemo(
+ () => Array.from({ length: optimizedCurves.length * 3 }, (_, i) => (i % 3) / 3),
+ [optimizedCurves.length]
+ );
+
+ useFrame((state) => {
+ const p = progress.current ?? 0;
+ const vis = smoothstep(0.52, 0.70, p);
+
+ const isVisible = vis > 0.01;
+ particleRefs.current.forEach(m => { if (m) m.visible = isVisible; });
+ chevronRefs.current.forEach(g => { if (g) g.visible = isVisible; });
+ pulseRefs.current.forEach(m => { if (m) m.visible = isVisible; });
+ if (!isVisible) return;
+
+ const t = state.clock.elapsedTime;
+
+ // 1. Animate smooth green/cyan flow particles
+ for (let i = 0; i < count; i++) {
+ const mesh = particleRefs.current[i];
+ if (!mesh) continue;
+ const curveIdx = Math.floor(i / particlesPerCurve);
+ const curve = optimizedCurves[curveIdx];
+
+ const speed = 0.08 + seeded(i * 11) * 0.04;
+ const u = (particleOffsets[i] + t * speed) % 1;
+
+ curve.getPointAt(u, OPT_TMP_POS);
+ mesh.position.copy(OPT_TMP_POS);
+
+ const mat = mesh.material as THREE.MeshBasicMaterial;
+ mat.opacity = vis * 0.75 * Math.sin(u * Math.PI);
+ const s = 0.04 + Math.sin(t * 6 + i) * 0.012;
+ mesh.scale.setScalar(s / 0.05);
+ }
+
+ // 2. Animate direction chevrons (fast, steady green chevrons)
+ for (let i = 0; i < optimizedCurves.length * 3; i++) {
+ const group = chevronRefs.current[i];
+ if (!group) continue;
+ const curveIdx = Math.floor(i / 3);
+ const curve = optimizedCurves[curveIdx];
+
+ const u = (chevronOffsets[i] + t * 0.16) % 1;
+ curve.getPointAt(u, OPT_TMP_POS);
+ curve.getTangentAt(u, OPT_TMP_TAN);
+
+ group.position.copy(OPT_TMP_POS);
+ group.rotation.y = Math.atan2(OPT_TMP_TAN.x, OPT_TMP_TAN.z);
+ group.rotation.x = Math.atan2(OPT_TMP_TAN.y, Math.sqrt(OPT_TMP_TAN.x * OPT_TMP_TAN.x + OPT_TMP_TAN.z * OPT_TMP_TAN.z)) - Math.PI / 2;
+
+ group.traverse((o) => {
+ const m = (o as THREE.Mesh).material as THREE.MeshBasicMaterial | undefined;
+ if (m && "opacity" in m) {
+ m.opacity = vis * 0.85 * Math.sin(u * Math.PI);
+ }
+ });
+ }
+
+ // 3. Animate expanding delivery pulses (shooting data pulses)
+ for (let i = 0; i < optimizedCurves.length; i++) {
+ const mesh = pulseRefs.current[i];
+ if (!mesh) continue;
+ const curve = optimizedCurves[i];
+
+ const u = (t * 0.55 + i * 0.2) % 1;
+ curve.getPointAt(u, OPT_TMP_POS);
+ mesh.position.copy(OPT_TMP_POS);
+
+ const mat = mesh.material as THREE.MeshBasicMaterial;
+ mat.opacity = vis * 0.9 * Math.sin(u * Math.PI);
+ const s = 0.08 + Math.sin(t * 12 + i) * 0.02;
+ mesh.scale.setScalar(s / 0.07);
+ }
+ });
+
+ return (
+
+ {/* Tiny Cyan/Green Flow Particles */}
+ {Array.from({ length: count }).map((_, i) => {
+ const color = (Math.floor(i / particlesPerCurve) % 2 === 0) ? COLORS.cyan : COLORS.green;
+ return (
+ { particleRefs.current[i] = el; }}
+ visible={false}
+ >
+
+
+
+ );
+ })}
+
+ {/* Glowing Chevrons */}
+ {Array.from({ length: optimizedCurves.length * 3 }).map((_, i) => {
+ const color = (Math.floor(i / 3) % 2 === 0) ? COLORS.cyan : COLORS.green;
+ return (
+ { chevronRefs.current[i] = el; }}
+ visible={false}
+ >
+
+
+
+
+
+ );
+ })}
+
+ {/* Large Delivery Pulses */}
+ {optimizedCurves.map((_, i) => {
+ const color = (i % 2 === 0) ? COLORS.cyan : COLORS.green;
+ return (
+ { pulseRefs.current[i] = el; }}
+ visible={false}
+ >
+
+
+
+ );
+ })}
+
+ );
+}
+
+const OptimizedFlowMemo = React.memo(OptimizedFlow);
+
+/**
+ * Glowing route network. Chaotic red routes are pre-drawn then "erased" via
+ * geometry draw-range during the dissolve phase; optimized cyan/green routes
+ * draw themselves in during the optimize phase. Delivery nodes flip red→green.
+ */
+function RouteSystem({ progress, reduced = false, isMobile = false }: Props) {
+ const { chaotic, optimized, chaosNodes, optimizedNodes } = useMemo(
+ () => buildRoutes(),
+ [],
+ );
+
+ const chaosRefs = useRef([]);
+ const optRefs = useRef([]);
+ const chaosNodeGroup = useRef(null);
+ const optNodeGroup = useRef(null);
+
+ const registerChaos = (i: number) => (geo: THREE.TubeGeometry | null) => {
+ if (geo) chaosRefs.current[i] = { ...(chaosRefs.current[i] ?? {}), geo } as TubeHandle;
+ };
+ const registerChaosMat = (i: number) => (mat: THREE.MeshBasicMaterial | null) => {
+ if (mat) chaosRefs.current[i] = { ...(chaosRefs.current[i] ?? {}), mat } as TubeHandle;
+ };
+ const registerOpt = (i: number) => (geo: THREE.TubeGeometry | null) => {
+ if (geo) optRefs.current[i] = { ...(optRefs.current[i] ?? {}), geo } as TubeHandle;
+ };
+ const registerOptMat = (i: number) => (mat: THREE.MeshBasicMaterial | null) => {
+ if (mat) optRefs.current[i] = { ...(optRefs.current[i] ?? {}), mat } as TubeHandle;
+ };
+
+ const setDraw = (geo: THREE.TubeGeometry, frac: number) => {
+ if (!geo.index) return;
+ const total = geo.index.count;
+ const step = RADIAL * 6;
+ const count = Math.max(0, Math.floor((total * frac) / step) * step);
+ geo.setDrawRange(0, count);
+ };
+
+ useFrame((state) => {
+ const p = progress.current ?? 0;
+ const t = state.clock.elapsedTime;
+
+ // Chaotic routes: full → erased during dissolve, with warning flicker.
+ const chaosDraw = 1 - smoothstep(0.4, 0.56, p);
+ const chaosBase = (1 - smoothstep(0.38, 0.55, p)) * 0.85;
+ const flicker = 0.7 + Math.sin(t * 7) * 0.18;
+ for (const h of chaosRefs.current) {
+ if (!h?.geo) continue;
+ setDraw(h.geo, chaosDraw);
+ if (h.mat) h.mat.opacity = chaosBase * flicker;
+ }
+
+ // Optimized routes: draw-in during optimize phase + flowing glow.
+ const optDraw = smoothstep(0.55, 0.74, p);
+ const optBase = smoothstep(0.55, 0.66, p);
+ for (let i = 0; i < optRefs.current.length; i++) {
+ const h = optRefs.current[i];
+ if (!h?.geo) continue;
+ setDraw(h.geo, optDraw);
+ if (h.mat) {
+ // Keep the tube a crisp, saturated energy line rather than a
+ // bloom-blown white slab — the flow energy comes from the pulses + trails.
+ const flow = 0.52 + Math.sin(t * 3 - i * 0.7) * 0.12;
+ h.mat.opacity = optBase * flow;
+ }
+ }
+
+ // Delivery nodes
+ if (chaosNodeGroup.current) {
+ const s = 0.0001 + (1 - smoothstep(0.4, 0.55, p));
+ chaosNodeGroup.current.scale.setScalar(s);
+ }
+ if (optNodeGroup.current) {
+ const appear = smoothstep(0.6, 0.82, p);
+ const pop = appear * (1 + Math.sin(t * 4) * 0.06 * appear);
+ optNodeGroup.current.scale.setScalar(0.0001 + pop);
+ }
+ });
+
+ return (
+
+ {/* Chaotic routes (red) */}
+ {chaotic.map((r, i) => (
+
+
+
+
+ ))}
+
+ {/* Optimized routes (cyan → green) */}
+ {optimized.map((r, i) => (
+
+
+
+
+ ))}
+
+ {/* Chaos delivery nodes (red, delayed) with pulsing warning alerts */}
+
+ {chaosNodes.map((n, i) => (
+
+
+
+
+
+
+
+ ))}
+
+
+ {/* Optimized delivery nodes (green, fulfilled) with pulsing success alerts */}
+
+ {optimizedNodes.map((n, i) => (
+
+
+
+
+
+
+
+ ))}
+
+
+ {/* Flowing delivery particles along routes */}
+ {!isMobile && (
+ <>
+ r.curve)}
+ />
+ r.curve)}
+ />
+ >
+ )}
+
+ );
+}
+
+export default React.memo(RouteSystem);
diff --git a/src/components/optimization/VehicleFleet.tsx b/src/components/optimization/VehicleFleet.tsx
new file mode 100644
index 0000000..20a3333
--- /dev/null
+++ b/src/components/optimization/VehicleFleet.tsx
@@ -0,0 +1,294 @@
+"use client";
+
+import React, { useMemo, useRef } from "react";
+import { useFrame } from "@react-three/fiber";
+import * as THREE from "three";
+import { COLORS } from "./constants";
+import { buildRoutes, RouteDef } from "./routes";
+import { seeded, smoothstep } from "./math";
+
+type Props = {
+ progress: React.RefObject;
+ reduced?: boolean;
+};
+
+type VType = "truck" | "van" | "bike";
+
+type VehicleDef = {
+ curve: THREE.CatmullRomCurve3;
+ type: VType;
+ speed: number;
+ offset: number;
+ color: string;
+ nodes: THREE.Vector3[];
+};
+
+const TMP_POS = new THREE.Vector3();
+const TMP_TAN = new THREE.Vector3();
+const TRAIL_TMP = new THREE.Vector3();
+
+// Glowing energy comet-tail trailing each optimized vehicle (see ref image:
+// a delivery van with flowing green light streaks). Sampled along the vehicle's
+// own route curve behind its current position, so it always hugs the track and
+// never smears on a scroll-jump.
+const TRAIL_N = 16;
+const TRAIL_GAP = 0.011;
+
+/** Flat billboarded truck cutout from the user's artwork (modelled facing +Z;
+ * the fleet loop rotates each one to face the camera). */
+function TruckBillboard({
+ texture,
+ tint,
+ width,
+ aspect,
+}: {
+ texture: THREE.Texture;
+ tint: string;
+ width: number;
+ aspect: number;
+}) {
+ const h = width / aspect;
+ return (
+
+
+
+
+ );
+}
+
+const TruckBillboardMemo = React.memo(TruckBillboard);
+
+const HUB = new THREE.Vector3(0, 0.5, 0);
+
+/**
+ * Moving fleet. 12 erratic vehicles ride the chaotic routes; on the reorganize
+ * phase they fade out and 8 optimally-assigned vehicles take over the clean
+ * routes. Position + heading are driven along the shared curves each frame.
+ */
+function VehicleFleet({ progress, reduced = false }: Props) {
+ const { chaotic, optimized } = useMemo(() => buildRoutes(), []);
+
+ const types: VType[] = ["truck", "van", "bike"];
+
+ const chaosFleet = useMemo(() => {
+ return Array.from({ length: 7 }, (_, i) => ({
+ curve: (chaotic[i % chaotic.length] as RouteDef).curve,
+ type: types[i % 3],
+ speed: 0.018 + seeded(i * 5 + 1) * 0.03,
+ offset: seeded(i * 5 + 2),
+ color: COLORS.red,
+ nodes: (chaotic[i % chaotic.length] as RouteDef).nodes || [],
+ }));
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [chaotic]);
+
+ const optFleet = useMemo(() => {
+ return Array.from({ length: 5 }, (_, i) => ({
+ curve: (optimized[i % optimized.length] as RouteDef).curve,
+ type: types[i % 3],
+ speed: 0.05 + seeded(i * 7 + 1) * 0.025,
+ offset: seeded(i * 7 + 2),
+ color: i % 2 === 0 ? COLORS.cyan : COLORS.green,
+ nodes: (optimized[i % optimized.length] as RouteDef).nodes || [],
+ }));
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [optimized]);
+
+ const chaosRefs = useRef<(THREE.Group | null)[]>([]);
+ const optRefs = useRef<(THREE.Group | null)[]>([]);
+ const optTrailRefs = useRef<(THREE.Mesh | null)[]>([]);
+
+ // The optimized fleet uses the user's isometric truck artwork as a flat
+ // billboarded cutout (loaded imperatively so we don't need a Suspense fence).
+ const truckTex = useMemo(() => {
+ const t = new THREE.TextureLoader().load("/images/truck.png");
+ t.colorSpace = THREE.SRGBColorSpace;
+ t.anisotropy = 8;
+ return t;
+ }, []);
+ const TRUCK_ASPECT = 1080 / 948; // rasterized PNG dimensions
+ const TRUCK_W = 1.75;
+
+ // Persistent progress values
+ const chaosProgress = useRef([]);
+ const optProgress = useRef([]);
+
+ // Re-sync on length change (not just when empty) so a fleet-size edit / HMR
+ // doesn't leave indices reading `undefined` and crashing getPointAt().
+ if (chaosProgress.current.length !== chaosFleet.length) {
+ chaosProgress.current = chaosFleet.map((v) => v.offset);
+ }
+ if (optProgress.current.length !== optFleet.length) {
+ optProgress.current = optFleet.map((v) => v.offset);
+ }
+
+ const place = (
+ group: THREE.Group | null,
+ def: VehicleDef,
+ progressArray: number[],
+ index: number,
+ dt: number,
+ opacity: number,
+ ) => {
+ if (!group || !def?.curve) return;
+ const visible = opacity > 0.02;
+ group.visible = visible;
+ if (!visible) return;
+
+ let u = progressArray[index];
+ if (u === undefined || Number.isNaN(u)) u = progressArray[index] = def.offset ?? 0;
+
+ // Get position to compute distance to nodes
+ def.curve.getPointAt(u, TMP_POS);
+
+ // Compute distance to nearest node
+ let minDist = TMP_POS.distanceTo(HUB);
+ if (def.nodes && def.nodes.length > 0) {
+ def.nodes.forEach((node) => {
+ const dist = TMP_POS.distanceTo(node);
+ if (dist < minDist) minDist = dist;
+ });
+ }
+
+ // Slow at nodes (speed reduction to 20%), accelerate on long corridors (up to 125%)
+ const speedFactor = 0.20 + smoothstep(0.4, 2.5, minDist) * 1.05;
+
+ // Increment progress continuously
+ u = (u + dt * def.speed * speedFactor) % 1;
+ progressArray[index] = u;
+
+ // Get final position and orientation
+ def.curve.getPointAt(u, TMP_POS);
+ def.curve.getTangentAt(u, TMP_TAN);
+
+ group.position.copy(TMP_POS);
+ group.rotation.y = Math.atan2(TMP_TAN.x, TMP_TAN.z);
+
+ group.traverse((o) => {
+ const m = (o as THREE.Mesh).material as THREE.Material | undefined;
+ if (m && "opacity" in m) {
+ (m as THREE.MeshBasicMaterial).opacity = opacity * baseOpacity(o);
+ }
+ });
+ };
+
+ // preserve per-material relative opacity (fill vs wire vs light)
+ const baseOpacity = (o: THREE.Object3D) => {
+ const m = (o as THREE.Mesh).material as THREE.MeshBasicMaterial | undefined;
+ if (!m) return 1;
+ if (m.map) return 1; // textured truck cutout fades at full strength
+ if ((m as THREE.MeshBasicMaterial).wireframe) return 1;
+ if (m.color && m.color.getHex() === 0xffffff) return 1;
+ if (m.color && m.color.getHex() === 0xef4444) return 1; // headlight/taillight colors
+ return 0.2;
+ };
+
+ useFrame((state, dt) => {
+ const p = progress.current ?? 0;
+ const t = state.clock.elapsedTime;
+ const safeDt = Math.min(0.06, dt);
+
+ const chaosFadeIn = smoothstep(0.10, 0.22, p);
+ const chaosFadeOut = 1 - smoothstep(0.70, 0.82, p);
+ const chaosOp = chaosFadeIn * chaosFadeOut * (0.85 + Math.sin(t * 6) * 0.1);
+ const optOp = smoothstep(0.68, 0.82, p);
+
+ const cam = state.camera;
+ for (let i = 0; i < chaosFleet.length; i++) {
+ place(chaosRefs.current[i], chaosFleet[i], chaosProgress.current, i, safeDt, chaosOp);
+ const g = chaosRefs.current[i];
+ if (g && g.visible) {
+ g.rotation.y = Math.atan2(cam.position.x - g.position.x, cam.position.z - g.position.z);
+ }
+ }
+ for (let i = 0; i < optFleet.length; i++) {
+ place(optRefs.current[i], optFleet[i], optProgress.current, i, safeDt, optOp);
+ // The truck is 2D cutout art, so billboard it around Y to always face the
+ // camera (overrides the tangent heading set inside place()).
+ const g = optRefs.current[i];
+ if (g && g.visible) {
+ g.rotation.y = Math.atan2(cam.position.x - g.position.x, cam.position.z - g.position.z);
+ }
+ // Energy comet-tail behind each optimized vehicle.
+ const u = optProgress.current[i];
+ const curve = optFleet[i]?.curve;
+ if (!curve || u === undefined || Number.isNaN(u)) continue;
+ for (let k = 0; k < TRAIL_N; k++) {
+ const mesh = optTrailRefs.current[i * TRAIL_N + k];
+ if (!mesh) continue;
+ if (optOp < 0.02) { mesh.visible = false; continue; }
+ let uk = u - (k + 1) * TRAIL_GAP;
+ if (uk < 0) uk = 0;
+ mesh.visible = true;
+ curve.getPointAt(uk, TRAIL_TMP);
+ mesh.position.copy(TRAIL_TMP);
+ const taper = 1 - k / TRAIL_N;
+ const size = 0.05 + taper * 0.13;
+ mesh.scale.setScalar(size / 0.1);
+ const mat = mesh.material as THREE.MeshBasicMaterial;
+ mat.opacity = optOp * taper * taper * (0.65 + Math.sin(t * 9 - k * 0.7) * 0.35);
+ }
+ }
+ });
+
+ return (
+
+ {/* Chaotic fleet — the user's truck art tinted red (congested/before) */}
+ {chaosFleet.map((_v, i) => (
+ {
+ chaosRefs.current[i] = el;
+ }}
+ >
+
+
+ ))}
+ {/* Optimized fleet — the truck art in clean white */}
+ {optFleet.map((_v, i) => (
+ {
+ optRefs.current[i] = el;
+ }}
+ >
+
+
+ ))}
+
+ {/* Glowing energy comet-tails for the optimized fleet */}
+ {optFleet.map((v, i) =>
+ Array.from({ length: TRAIL_N }, (_, k) => (
+ {
+ optTrailRefs.current[i * TRAIL_N + k] = el;
+ }}
+ >
+
+
+
+ )),
+ )}
+
+ );
+}
+
+export default React.memo(VehicleFleet);
+
+
diff --git a/src/components/optimization/constants.ts b/src/components/optimization/constants.ts
new file mode 100644
index 0000000..4d98615
--- /dev/null
+++ b/src/components/optimization/constants.ts
@@ -0,0 +1,118 @@
+/**
+ * Shared design tokens + KPI data for the AI Logistics Optimization section.
+ * Centralizing here keeps the 3D scene, overlay and metrics panel in sync.
+ */
+
+export const COLORS = {
+ bg: "#020617",
+ cyan: "#00E5FF",
+ green: "#22C55E",
+ red: "#EF4444",
+ amber: "#F59E0B",
+ ink: "#0B1220",
+ slate: "#64748B",
+ textDim: "rgba(226,232,240,0.66)",
+} as const;
+
+/** rgba helper used by inline styles + scoped CSS. */
+export function rgba(hex: string, alpha: number): string {
+ const h = hex.replace("#", "");
+ const r = parseInt(h.substring(0, 2), 16);
+ const g = parseInt(h.substring(2, 4), 16);
+ const b = parseInt(h.substring(4, 6), 16);
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
+}
+
+export type Kpi = {
+ key: string;
+ /** numeric value the counter animates between */
+ before: number;
+ after: number;
+ prefixBefore?: string;
+ prefixAfter?: string;
+ suffix?: string;
+ labelBefore: string;
+ labelAfter: string;
+ /** lower number is the better outcome (drives the up/down arrow) */
+ goodWhenLower: boolean;
+};
+
+export const KPIS: Kpi[] = [
+ {
+ key: "distance",
+ before: 312,
+ after: 182,
+ suffix: " km",
+ labelBefore: "Route Distance",
+ labelAfter: "Route Distance",
+ goodWhenLower: true,
+ },
+ {
+ key: "vehicles",
+ before: 8,
+ after: 5,
+ labelBefore: "Vehicles",
+ labelAfter: "Vehicles",
+ goodWhenLower: true,
+ },
+ {
+ key: "orders",
+ before: 59,
+ after: 59,
+ labelBefore: "Orders",
+ labelAfter: "Orders Fulfilled",
+ goodWhenLower: false,
+ },
+ {
+ key: "delayed",
+ before: 23,
+ after: 0,
+ labelBefore: "Delayed",
+ labelAfter: "Delayed",
+ goodWhenLower: true,
+ },
+ {
+ key: "cost",
+ before: 18,
+ after: 18,
+ prefixBefore: "+",
+ prefixAfter: "−",
+ suffix: "%",
+ labelBefore: "Cost Overrun",
+ labelAfter: "Cost Saved",
+ goodWhenLower: false,
+ },
+];
+
+/**
+ * Scroll-phase thresholds (normalized 0 → 1). The whole narrative is driven by
+ * a single scroll progress value shared between GSAP, R3F and Framer Motion.
+ */
+export const PHASES = {
+ chaos: 0.0, // Stage 1 & 2: Network appears & vehicles active
+ scan: 0.28, // Stage 3: AI scans routes
+ dissolve: 0.42, // Stage 4 & 5: Optimize begins & bad routes dissolve
+ optimize: 0.56, // Stage 6: Optimized routes appear
+ reorganize: 0.70, // Reassigning vehicles
+ metrics: 0.84, // Stage 7: KPIs update & complete
+} as const;
+
+export type PhaseKey = keyof typeof PHASES;
+
+export function phaseFromProgress(p: number): PhaseKey {
+ if (p >= PHASES.metrics) return "metrics";
+ if (p >= PHASES.reorganize) return "reorganize";
+ if (p >= PHASES.optimize) return "optimize";
+ if (p >= PHASES.dissolve) return "dissolve";
+ if (p >= PHASES.scan) return "scan";
+ return "chaos";
+}
+
+export const PHASE_LABELS: Record = {
+ chaos: "Monitoring network",
+ scan: "AI SCANNING NETWORK",
+ dissolve: "AI OPTIMIZING ROUTES",
+ optimize: "AI OPTIMIZING ROUTES",
+ reorganize: "AI REASSIGNING VEHICLES",
+ metrics: "AI COMPLETE",
+};
diff --git a/src/components/optimization/math.ts b/src/components/optimization/math.ts
new file mode 100644
index 0000000..fd0be33
--- /dev/null
+++ b/src/components/optimization/math.ts
@@ -0,0 +1,36 @@
+/** Small framerate-independent math helpers shared by the 3D scene. */
+
+export const clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);
+
+export const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
+
+/** Linear remap of `v` from [inMin,inMax] to [outMin,outMax], clamped. */
+export function remap(
+ v: number,
+ inMin: number,
+ inMax: number,
+ outMin = 0,
+ outMax = 1,
+): number {
+ const t = clamp01((v - inMin) / (inMax - inMin || 1));
+ return outMin + (outMax - outMin) * t;
+}
+
+export function smoothstep(edge0: number, edge1: number, x: number): number {
+ const t = clamp01((x - edge0) / (edge1 - edge0 || 1));
+ return t * t * (3 - 2 * t);
+}
+
+/**
+ * Exponential damp toward a target — frame-rate independent.
+ * lambda ~ responsiveness (higher = snappier).
+ */
+export function damp(current: number, target: number, lambda: number, dt: number): number {
+ return lerp(current, target, 1 - Math.exp(-lambda * dt));
+}
+
+/** Deterministic pseudo-random in [0,1) — avoids Math.random for stable SSR/scene. */
+export function seeded(i: number): number {
+ const x = Math.sin(i * 127.1 + 311.7) * 43758.5453;
+ return x - Math.floor(x);
+}
diff --git a/src/components/optimization/routes.ts b/src/components/optimization/routes.ts
new file mode 100644
index 0000000..570a661
--- /dev/null
+++ b/src/components/optimization/routes.ts
@@ -0,0 +1,91 @@
+import * as THREE from "three";
+import { seeded } from "./math";
+
+/**
+ * Deterministic route + delivery-node generation shared by RouteSystem and
+ * VehicleFleet so glowing paths and moving vehicles stay perfectly aligned.
+ */
+
+export const HUB = new THREE.Vector3(0, 0.5, 0);
+export const ROUTE_Y = 0.45;
+
+export type RouteDef = {
+ curve: THREE.CatmullRomCurve3;
+ nodes: THREE.Vector3[];
+};
+
+export type SceneRoutes = {
+ chaotic: RouteDef[];
+ optimized: RouteDef[];
+ chaosNodes: THREE.Vector3[];
+ optimizedNodes: THREE.Vector3[];
+};
+
+function arc(points: THREE.Vector3[]): THREE.CatmullRomCurve3 {
+ return new THREE.CatmullRomCurve3(points, false, "catmullrom", 0.5);
+}
+
+let cached: SceneRoutes | null = null;
+
+export function buildRoutes(): SceneRoutes {
+ if (cached) return cached;
+
+ // --- Optimized clusters: 5 tidy zones around the hub --------------------
+ const clusterCount = 5;
+ const optimized: RouteDef[] = [];
+ const optimizedNodes: THREE.Vector3[] = [];
+
+ for (let c = 0; c < clusterCount; c++) {
+ const baseAngle = (c / clusterCount) * Math.PI * 2 + 0.3;
+ const radius = 7 + seeded(c * 7 + 1) * 3;
+ const cx = Math.cos(baseAngle) * radius;
+ const cz = Math.sin(baseAngle) * radius;
+
+ // 2-3 stops per cluster (multi-trip planning)
+ const stops = 2 + Math.floor(seeded(c * 7 + 2) * 2);
+ const pts: THREE.Vector3[] = [HUB.clone()];
+ // gentle lift-off control point
+ pts.push(new THREE.Vector3(cx * 0.35, ROUTE_Y + 1.4, cz * 0.35));
+
+ const nodes: THREE.Vector3[] = [];
+ for (let s = 0; s < stops; s++) {
+ const jx = (seeded(c * 31 + s * 5 + 3) - 0.5) * 3.2;
+ const jz = (seeded(c * 31 + s * 5 + 4) - 0.5) * 3.2;
+ const node = new THREE.Vector3(cx + jx, ROUTE_Y, cz + jz);
+ nodes.push(node);
+ optimizedNodes.push(node);
+ pts.push(node.clone());
+ }
+ optimized.push({ curve: arc(pts), nodes });
+ }
+
+ // --- Chaotic routes: tangled, overlapping, wandering --------------------
+ const chaotic: RouteDef[] = [];
+ const chaosNodes: THREE.Vector3[] = [];
+ const chaosCount = 6;
+
+ for (let r = 0; r < chaosCount; r++) {
+ const segs = 4 + Math.floor(seeded(r * 13 + 1) * 3);
+ const pts: THREE.Vector3[] = [HUB.clone()];
+ let angle = seeded(r * 13 + 2) * Math.PI * 2;
+ const nodes: THREE.Vector3[] = [];
+ for (let s = 0; s < segs; s++) {
+ angle += (seeded(r * 13 + s * 3 + 3) - 0.5) * 2.6; // erratic turns
+ const reach = 2.6 + seeded(r * 13 + s * 3 + 4) * 6.4; // tighter, more central
+ const px = Math.cos(angle) * reach + (seeded(r * 17 + s) - 0.5) * 2.6;
+ const pz = Math.sin(angle) * reach + (seeded(r * 19 + s) - 0.5) * 2.6;
+ const py = ROUTE_Y + seeded(r * 23 + s) * 1.1;
+ const p = new THREE.Vector3(px, py, pz);
+ pts.push(p);
+ if (s === segs - 1) {
+ const node = new THREE.Vector3(px, ROUTE_Y, pz);
+ chaosNodes.push(node);
+ nodes.push(node);
+ }
+ }
+ chaotic.push({ curve: arc(pts), nodes });
+ }
+
+ cached = { chaotic, optimized, chaosNodes, optimizedNodes };
+ return cached;
+}
diff --git a/src/components/performance/PerformanceCanvas.tsx b/src/components/performance/PerformanceCanvas.tsx
new file mode 100644
index 0000000..bc496bb
--- /dev/null
+++ b/src/components/performance/PerformanceCanvas.tsx
@@ -0,0 +1,464 @@
+"use client";
+
+import React, { useMemo, useRef } from "react";
+import { Canvas, useFrame } from "@react-three/fiber";
+import { Html } from "@react-three/drei";
+import { EffectComposer, Bloom, Vignette } from "@react-three/postprocessing";
+import { KernelSize } from "postprocessing";
+import * as THREE from "three";
+import { buildPerfRoutes, GATEWAY, LEFT_C, RIGHT_C, ROUTE_Y } from "./perfRoutes";
+import { SplineRider, VType } from "./Vehicles";
+
+type Props = {
+ progress: React.RefObject;
+ reduced?: boolean;
+ isMobile?: boolean;
+ active?: boolean;
+};
+
+// Local math helpers (self-contained; avoids fragile cross-folder const imports).
+const clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);
+const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
+const smoothstep = (e0: number, e1: number, x: number) => {
+ const t = clamp01((x - e0) / (e1 - e0 || 1));
+ return t * t * (3 - 2 * t);
+};
+const damp = (cur: number, target: number, lambda: number, dt: number) =>
+ lerp(cur, target, 1 - Math.exp(-lambda * dt));
+const seeded = (i: number) => {
+ const x = Math.sin(i * 127.1 + 311.7) * 43758.5453;
+ return x - Math.floor(x);
+};
+
+// Grounded "operational" palette — white / gray / green / orange / red. No cyan/blue/purple.
+const C = {
+ bg: "#14171c",
+ ground: "#262b33",
+ road: "#2b3038",
+ bldA: "#4b5563",
+ bldB: "#6b7280",
+ bldC: "#9ca3af",
+ white: "#e5e7eb",
+ red: "#ef4444",
+ orange: "#f97316",
+ amber: "#fbbf24",
+ green: "#22c55e",
+ green2: "#4ade80",
+ steel: "#475569",
+};
+
+/* ───────────── Camera: grounded city flythrough (not orbit) ───────────── */
+type Key = { p: number; pos: [number, number, number]; look: [number, number, number] };
+const KEYS: Key[] = [
+ { p: 0.0, pos: [0, 9, 20], look: [0, 0.8, 0] }, // wide — framed on BOTH districts' content (instant contrast)
+ { p: 0.28, pos: [-13, 3.4, 8.5], look: [-8, 1.0, 0] }, // road-level, chaotic left
+ { p: 0.5, pos: [0, 6.5, 9], look: [0, 2.2, -1] }, // transformation divide
+ { p: 0.72, pos: [13, 3.4, 8.5], look: [8, 1.0, 0] }, // road-level, optimized right
+ { p: 1.0, pos: [0, 15, 12], look: [0, 0.2, 4] }, // top-down logistics overview
+];
+function sampleKeys(e: number) {
+ let a = KEYS[0], b = KEYS[KEYS.length - 1];
+ for (let i = 0; i < KEYS.length - 1; i++) {
+ if (e >= KEYS[i].p && e <= KEYS[i + 1].p) { a = KEYS[i]; b = KEYS[i + 1]; break; }
+ }
+ const k = smoothstep(0, 1, (e - a.p) / ((b.p - a.p) || 1));
+ return {
+ pos: [lerp(a.pos[0], b.pos[0], k), lerp(a.pos[1], b.pos[1], k), lerp(a.pos[2], b.pos[2], k)] as const,
+ look: [lerp(a.look[0], b.look[0], k), lerp(a.look[1], b.look[1], k), lerp(a.look[2], b.look[2], k)] as const,
+ };
+}
+function CameraRig({ progress }: { progress: React.RefObject }) {
+ const eased = useRef(0);
+ const look = useRef(new THREE.Vector3(0, 1.5, 4));
+ useFrame((state, dt) => {
+ eased.current = damp(eased.current, clamp01(progress.current ?? 0), 2.4, dt);
+ const { pos, look: lk } = sampleKeys(eased.current);
+ const t = state.clock.elapsedTime;
+ state.camera.position.set(pos[0] + Math.sin(t * 0.25) * 0.25, pos[1] + Math.sin(t * 0.4) * 0.12, pos[2]);
+ look.current.lerp(new THREE.Vector3(lk[0], lk[1], lk[2]), 0.12);
+ state.camera.lookAt(look.current);
+ });
+ return null;
+}
+
+/* ───────────── Roads (flat asphalt ribbons on the ground) ───────────── */
+const _p = new THREE.Vector3(), _t = new THREE.Vector3();
+function roadRibbon(curve: THREE.CatmullRomCurve3, width: number, segs = 130) {
+ const half = width / 2;
+ const pos: number[] = [];
+ const idx: number[] = [];
+ for (let i = 0; i <= segs; i++) {
+ const u = (i / segs) % 1;
+ curve.getPointAt(u, _p);
+ curve.getTangentAt(u, _t);
+ const nx = -_t.z, nz = _t.x;
+ const len = Math.hypot(nx, nz) || 1;
+ pos.push(_p.x + (nx / len) * half, ROUTE_Y, _p.z + (nz / len) * half);
+ pos.push(_p.x - (nx / len) * half, ROUTE_Y, _p.z - (nz / len) * half);
+ }
+ for (let i = 0; i < segs; i++) {
+ const a = i * 2, b = i * 2 + 1, c = i * 2 + 2, d = i * 2 + 3;
+ idx.push(a, b, c, b, d, c);
+ }
+ const g = new THREE.BufferGeometry();
+ g.setAttribute("position", new THREE.Float32BufferAttribute(pos, 3));
+ g.setIndex(idx);
+ g.computeVertexNormals();
+ return g;
+}
+function Roads({ routes, edge, width }: { routes: { curve: THREE.CatmullRomCurve3 }[]; edge: string; width: number }) {
+ const geos = useMemo(() => routes.map((r) => roadRibbon(r.curve, width)), [routes, width]);
+ return (
+
+ {geos.map((g, i) => (
+
+
+
+ ))}
+
+ );
+}
+
+/* Flowing fleet/data pulses riding the roads. */
+const PULSE_TMP = new THREE.Vector3();
+function RoadPulses({ routes, color, per = 3 }: { routes: { curve: THREE.CatmullRomCurve3 }[]; color: string; per?: number }) {
+ const count = routes.length * per;
+ const refs = useRef<(THREE.Mesh | null)[]>([]);
+ const offs = useMemo(() => Array.from({ length: count }, (_, i) => seeded(i * 7 + 3)), [count]);
+ useFrame((state) => {
+ const t = state.clock.elapsedTime;
+ for (let i = 0; i < count; i++) {
+ const m = refs.current[i];
+ if (!m) continue;
+ const ri = Math.floor(i / per);
+ const u = (offs[i] + t * (0.04 + seeded(i) * 0.03)) % 1;
+ routes[ri].curve.getPointAt(u, PULSE_TMP);
+ m.position.set(PULSE_TMP.x, ROUTE_Y + 0.12, PULSE_TMP.z);
+ (m.material as THREE.MeshBasicMaterial).opacity = 0.6 + Math.sin(t * 6 + i) * 0.35;
+ }
+ });
+ return (
+
+ {Array.from({ length: count }, (_, i) => (
+ { refs.current[i] = el; }}>
+
+
+
+ ))}
+
+ );
+}
+
+/* ───────────── City buildings ───────────── */
+type Bld = { x: number; z: number; w: number; d: number; h: number; side: number; tone: number; accent: boolean };
+function Buildings() {
+ const blds = useMemo(() => {
+ const arr: Bld[] = [];
+ for (let gx = -15; gx <= 15; gx += 2.6) {
+ for (let gz = -12.5; gz <= 12.5; gz += 2.6) {
+ if (Math.abs(gx) < 1.8) continue; // keep gateway band clear
+ const center = gx < 0 ? LEFT_C : RIGHT_C;
+ const dCenter = Math.hypot(gx - center.x, gz - center.z);
+ if (dCenter < 6.4) continue; // keep road area clear
+ if (gz > 7.2 && Math.abs(gx) < 6.5) continue; // keep KPI tower area clear
+ if (seeded(gx * 7.3 + gz * 1.7 + 50) < 0.28) continue; // streets / gaps
+ const jx = (seeded(gx + gz) - 0.5) * 0.6;
+ const jz = (seeded(gx * 2 + gz) - 0.5) * 0.6;
+ arr.push({
+ x: gx + jx, z: gz + jz,
+ w: 1.2 + seeded(gx * 1.1 + gz) * 0.8,
+ d: 1.2 + seeded(gx + gz * 1.3) * 0.8,
+ h: 1.1 + seeded(gx * 3 + gz * 5) * 3.6,
+ side: gx < 0 ? -1 : 1,
+ tone: seeded(gx * 9 + gz * 4),
+ accent: seeded(gx * 4 + gz * 11) > 0.62,
+ });
+ }
+ }
+ return arr;
+ }, []);
+
+ return (
+
+ {blds.map((b, i) => {
+ const bodyColor = b.tone < 0.4 ? C.bldA : b.tone < 0.75 ? C.bldB : (b.side > 0 ? C.white : C.bldC);
+ const accent = b.side < 0 ? (b.tone > 0.5 ? C.orange : C.red) : C.green;
+ return (
+
+
+
+
+
+ {b.accent && (
+
+
+
+
+ )}
+
+ );
+ })}
+
+ );
+}
+
+/* ───────────── Warehouse / dispatch depot ───────────── */
+function Warehouse({ x, z, accent }: { x: number; z: number; accent: string }) {
+ return (
+
+
+
+
+
+ {/* sloped roof slab */}
+
+
+
+
+ {/* roller doors */}
+ {[-1.3, 0, 1.3].map((o, i) => (
+
+
+
+
+ ))}
+
+ );
+}
+
+/* ───────────── Ground delivery zones + congestion heat ───────────── */
+function GroundMarks() {
+ const heat = useRef<(THREE.Mesh | null)[]>([]);
+ useFrame((state) => {
+ const t = state.clock.elapsedTime;
+ heat.current.forEach((m, i) => {
+ if (m) (m.material as THREE.MeshBasicMaterial).opacity = 0.18 + Math.sin(t * 2 + i) * 0.1;
+ });
+ });
+ const leftZones = useMemo(() => Array.from({ length: 4 }, (_, i) => {
+ const a = (i / 4) * Math.PI * 2 + 0.5;
+ return [LEFT_C.x + Math.cos(a) * 4.4, LEFT_C.z + Math.sin(a) * 4.4] as [number, number];
+ }), []);
+ const rightZones = useMemo(() => Array.from({ length: 4 }, (_, i) => {
+ const a = (i / 4) * Math.PI * 2 + 0.5;
+ return [RIGHT_C.x + Math.cos(a) * 4.4, RIGHT_C.z + Math.sin(a) * 4.4] as [number, number];
+ }), []);
+ return (
+
+ {/* left congestion heat patches (red/orange) */}
+ {leftZones.map(([x, z], i) => (
+ { heat.current[i] = el; }} rotation={[-Math.PI / 2, 0, 0]} position={[x, 0.04, z]}>
+
+
+
+ ))}
+ {/* right optimized coverage zones (green rings) */}
+ {rightZones.map(([x, z], i) => (
+
+
+
+
+ ))}
+
+ );
+}
+
+/* ───────────── Central transformation divider (NOT an AI engine) ─────────────
+ A clean glowing seam between the two worlds + a light "optimization wave" that
+ sweeps left→right, literally showing the chaotic side being transformed. */
+function TransformDivider() {
+ const sweep = useRef(null);
+ const seam = useRef(null);
+ useFrame((state) => {
+ const t = state.clock.elapsedTime;
+ if (sweep.current) {
+ const u = (t * 0.16) % 1;
+ sweep.current.position.x = lerp(-6, 6, u);
+ (sweep.current.material as THREE.MeshBasicMaterial).opacity = Math.sin(u * Math.PI) * 0.3;
+ }
+ if (seam.current) seam.current.opacity = 0.4 + Math.sin(t * 2) * 0.12;
+ });
+ return (
+
+ {/* ground seam marking before | after */}
+
+
+
+
+ {/* vertical seam light */}
+
+
+
+
+ {/* sweeping optimization wave crossing the city */}
+
+
+
+
+
+ );
+}
+
+/* Big number that counts up once on mount (drei Html children are normal DOM). */
+function CountUp({ value, decimals = 0, suffix = "" }: { value: number; decimals?: number; suffix?: string }) {
+ const [n, setN] = React.useState(0);
+ React.useEffect(() => {
+ let raf = 0;
+ let start: number | null = null;
+ const dur = 1700;
+ const tick = (now: number) => {
+ if (start === null) start = now;
+ const k = Math.min(1, (now - start) / dur);
+ setN(value * (1 - Math.pow(1 - k, 3)));
+ if (k < 1) raf = requestAnimationFrame(tick);
+ };
+ raf = requestAnimationFrame(tick);
+ return () => cancelAnimationFrame(raf);
+ }, [value]);
+ return <>{n.toFixed(decimals)}{suffix}>;
+}
+
+/* ───────────── KPI performance towers (grow with scroll) + BIG counting numbers ───────────── */
+type Kpi = { name: string; value: number; decimals?: number; suffix: string; h: number; x: number };
+const KPIS: Kpi[] = [
+ { name: "Faster Deliveries", value: 32, suffix: "%", h: 5.4, x: -5.1 },
+ { name: "Lower Op. Cost", value: 18, suffix: "%", h: 4.4, x: -1.7 },
+ { name: "SLA Success", value: 99.2, decimals: 1, suffix: "%", h: 5.7, x: 1.7 },
+ { name: "Less Fuel Used", value: 24, suffix: "%", h: 4.0, x: 5.1 },
+];
+function KpiTowers({ progress }: { progress: React.RefObject }) {
+ const refs = useRef<(THREE.Group | null)[]>([]);
+ const Z = 12.5;
+ useFrame(() => {
+ const p = clamp01(progress.current ?? 0);
+ KPIS.forEach((_, i) => {
+ const g = refs.current[i];
+ if (!g) return;
+ const k = smoothstep(0.12 + i * 0.07, 0.55 + i * 0.07, p);
+ g.scale.y = 0.02 + k * 0.98;
+ });
+ });
+ return (
+
+ {KPIS.map((kpi, i) => (
+
+
+
+
+
+ { refs.current[i] = el; }}>
+
+
+
+
+
+
+
+
+
+ {/* BIG, immediately-readable counting readout integrated on the tower */}
+
+
+
+
+ ))}
+
+ );
+}
+
+/* ───────────── Fleet (spline-locked, drives on the roads) ───────────── */
+const TYPES: VType[] = ["truck", "van", "auto", "bike"];
+function Fleets() {
+ const { chaotic, optimized } = useMemo(() => buildPerfRoutes(), []);
+ const badBodies = [C.steel, "#7f1d1d", "#9a3412"];
+ const goodBodies = [C.white, C.bldC, "#166534"];
+ return (
+
+ {chaotic.map((r, i) => (
+
+ ))}
+ {optimized.map((r, i) => (
+
+ ))}
+
+ );
+}
+
+/* ───────────── Scene ───────────── */
+function Scene({ progress, reduced, isMobile }: { progress: React.RefObject; reduced: boolean; isMobile: boolean }) {
+ const { chaotic, optimized } = useMemo(() => buildPerfRoutes(), []);
+ return (
+ <>
+
+
+
+
+
+
+ {/* ground + faint street grid */}
+
+
+
+
+
+
+ {/* bold per-side atmosphere wash — instant "chaos vs efficiency" read */}
+
+
+
+
+
+
+
+
+
+
+
+ {!isMobile && }
+ {!isMobile && }
+
+
+
+
+
+
+
+
+
+
+ {!reduced && (
+
+ {/* light bloom — only bright emissive (lights, pulses, gateway, tower caps) glow */}
+
+
+
+ )}
+ >
+ );
+}
+
+function PerformanceCanvas({ progress, reduced = false, isMobile = false, active = true }: Props) {
+ return (
+
+
+
+
+ );
+}
+
+export default React.memo(PerformanceCanvas);
diff --git a/src/components/performance/PerformanceSection.tsx b/src/components/performance/PerformanceSection.tsx
new file mode 100644
index 0000000..3584b71
--- /dev/null
+++ b/src/components/performance/PerformanceSection.tsx
@@ -0,0 +1,189 @@
+"use client";
+
+import React, { useEffect, useRef, useState } from "react";
+import dynamic from "next/dynamic";
+import { motion, useMotionValue, useTransform } from "framer-motion";
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+
+const PerformanceCanvas = dynamic(() => import("./PerformanceCanvas"), { ssr: false });
+
+export default function PerformanceSection() {
+ const containerRef = useRef(null);
+ const progressRef = useRef(0);
+ const scroll = useMotionValue(0);
+
+ const [pinState, setPinState] = useState<"before" | "pinned" | "after">("before");
+ const [mountScene, setMountScene] = useState(false);
+ const [sceneActive, setSceneActive] = useState(false);
+ const [isMobile, setIsMobile] = useState(false);
+ const [reduced, setReduced] = useState(false);
+
+ useEffect(() => {
+ const mqMobile = window.matchMedia("(max-width: 767px)");
+ const mqReduce = window.matchMedia("(prefers-reduced-motion: reduce)");
+ const sync = () => { setIsMobile(mqMobile.matches); setReduced(mqReduce.matches); };
+ sync();
+ mqMobile.addEventListener("change", sync);
+ mqReduce.addEventListener("change", sync);
+ return () => { mqMobile.removeEventListener("change", sync); mqReduce.removeEventListener("change", sync); };
+ }, []);
+
+ useEffect(() => {
+ const el = containerRef.current;
+ if (!el) return;
+ const mountIo = new IntersectionObserver(
+ (entries) => {
+ if (entries.some((e) => e.isIntersecting)) {
+ setMountScene(true);
+ setSceneActive(true);
+ mountIo.disconnect();
+ }
+ },
+ { rootMargin: "120% 0px" },
+ );
+ const activeIo = new IntersectionObserver(
+ (entries) => setSceneActive(entries.some((e) => e.isIntersecting)),
+ { rootMargin: "10% 0px" },
+ );
+ mountIo.observe(el);
+ activeIo.observe(el);
+ return () => { mountIo.disconnect(); activeIo.disconnect(); };
+ }, []);
+
+ useEffect(() => {
+ const el = containerRef.current;
+ if (!el) return;
+ gsap.registerPlugin(ScrollTrigger);
+ let lastPin: "before" | "pinned" | "after" = "before";
+ const st = ScrollTrigger.create({
+ trigger: el,
+ start: "top top",
+ end: "bottom bottom",
+ scrub: 0.4,
+ invalidateOnRefresh: true,
+ onUpdate: (self) => {
+ const p = self.progress;
+ progressRef.current = p;
+ scroll.set(p);
+ const ns = p <= 0.0002 ? "before" : p >= 0.9998 ? "after" : "pinned";
+ if (ns !== lastPin) { lastPin = ns; setPinState(ns); }
+ },
+ });
+ const refresh = setTimeout(() => ScrollTrigger.refresh(), 300);
+ return () => { clearTimeout(refresh); st.kill(); };
+ }, [scroll]);
+
+ const beforeOpacity = useTransform(scroll, [0.1, 0.3, 0.46], [0.4, 1, 0.32]);
+ const afterOpacity = useTransform(scroll, [0.6, 0.74, 0.95], [0.32, 1, 0.7]);
+ const stageA = useTransform(scroll, [0, 0.4], [1, 0]);
+ const stageB = useTransform(scroll, [0.4, 0.55, 0.65], [0, 1, 0]);
+ const stageC = useTransform(scroll, [0.65, 0.85], [0, 1]);
+
+ return (
+
+
+
+
+ {mountScene && (
+
+ )}
+
+
+
+
+
+
+ Traditional Dispatch
+ Congestion · long routes · fuel waste · delays
+
+
+ MileTruth Optimized
+ Clean corridors · organized fleet · faster coverage
+
+
+
+
+
+
+ );
+}
+
+const styles = `
+.dm-perf { position: relative; height: 250vh; background: transparent; margin-bottom: 120px; }
+.dm-perf-sticky { position: absolute; top: 0; left: 0; width: 100%; height: 100vh; overflow: hidden; }
+.dm-perf.is-pinned .dm-perf-sticky { position: fixed; top: 0; left: 0; }
+.dm-perf.is-after .dm-perf-sticky { position: absolute; top: auto; bottom: 0; }
+
+.dm-perf-card {
+ position: absolute !important; top: 110px !important; left: 40px !important; right: 40px !important; bottom: 24px !important;
+ border-radius: 60px !important; overflow: hidden !important;
+ background: linear-gradient(168deg, #1b1f26 0%, #15181d 45%, #101216 100%) !important;
+ border: 1.5px solid rgba(255,255,255,0.08) !important;
+ box-shadow: 0 4px 30px -4px rgba(0,0,0,0.7), 0 20px 80px -20px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.05) !important;
+ box-sizing: border-box !important;
+}
+@media (max-width: 1024px) { .dm-perf-card { top: 96px !important; left: 20px !important; right: 20px !important; bottom: 16px !important; border-radius: 42px !important; } }
+@media (max-width: 767px) { .dm-perf-card { top: 86px !important; left: 10px !important; right: 10px !important; bottom: 10px !important; border-radius: 28px !important; } }
+
+.dm-perf-backdrop { position: absolute; inset: 0; z-index: 0;
+ background: radial-gradient(55% 50% at 20% 60%, rgba(239,68,68,0.07) 0%, transparent 60%),
+ radial-gradient(55% 50% at 80% 60%, rgba(34,197,94,0.08) 0%, transparent 60%); }
+.dm-perf-canvas { position: absolute; inset: 0; z-index: 1; }
+.dm-perf-canvas canvas { display: block; }
+.dm-perf-vignette { position: absolute; inset: 0; z-index: 2; pointer-events: none;
+ background: radial-gradient(125% 105% at 50% 46%, transparent 56%, rgba(8,9,12,0.86) 100%),
+ linear-gradient(180deg, rgba(8,9,12,0.5) 0%, transparent 20%, transparent 66%, rgba(8,9,12,0.9) 100%); }
+
+.dm-perf-ui { position: absolute; inset: 0; z-index: 4; pointer-events: none;
+ font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif; }
+
+.dm-perf-head { position: absolute; top: clamp(18px, 3.4vh, 40px); left: 50%; transform: translateX(-50%); width: min(700px, 92vw); text-align: center; }
+.dm-perf-eyebrow { display: inline-flex; align-items: center; gap: 7px; font-size: 11px; letter-spacing: 0.24em; text-transform: uppercase; color: #4ade80;
+ padding: 5px 14px; border-radius: 999px; background: rgba(34,197,94,0.08); border: 1px solid rgba(34,197,94,0.28); backdrop-filter: blur(8px); }
+.dm-perf-dot { width: 6px; height: 6px; border-radius: 50%; background: #22c55e; box-shadow: 0 0 10px #22c55e; }
+.dm-perf .dm-perf-head h2 { margin: 10px 0 6px !important; padding: 0 !important; color: #F8FAFC !important; font-weight: 700 !important; text-transform: none !important;
+ font-size: clamp(22px, 2.6vw, 38px) !important; line-height: 1.08 !important; letter-spacing: -0.015em !important; }
+.dm-perf .dm-perf-head p { margin: 0 auto !important; padding: 0 !important; color: rgba(226,232,240,0.66) !important; max-width: 500px; font-size: clamp(11px, 1vw, 13.5px) !important; line-height: 1.45 !important; }
+
+.dm-perf-status { display: inline-flex; align-items: center; gap: 16px; margin-top: 12px; min-height: 18px; }
+.dm-perf-status__item { position: relative; display: inline-flex; align-items: center; gap: 7px; font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: #E2E8F0; font-weight: 600; }
+.dm-perf-status__item:not(:first-child) { position: absolute; left: 50%; transform: translateX(-50%); white-space: nowrap; }
+.dm-perf-status__dot { width: 6px; height: 6px; border-radius: 50%; }
+.dm-perf-status__dot--red { background: #ef4444; box-shadow: 0 0 10px #ef4444; }
+.dm-perf-status__dot--amber { background: #fbbf24; box-shadow: 0 0 10px #fbbf24; }
+.dm-perf-status__dot--green { background: #22c55e; box-shadow: 0 0 10px #22c55e; }
+
+.dm-perf-label { position: absolute; top: 44%; transform: translateY(-50%); display: flex; flex-direction: column; gap: 4px; }
+.dm-perf-label--before { left: clamp(16px, 4vw, 60px); text-align: left; }
+.dm-perf-label--after { right: clamp(16px, 4vw, 60px); text-align: right; align-items: flex-end; }
+.dm-perf-label__tag { font-size: clamp(17px, 2vw, 28px); font-weight: 800; letter-spacing: -0.02em; color: #f87171; text-shadow: 0 0 22px rgba(239,68,68,0.45); }
+.dm-perf-label__tag--good { color: #4ade80; text-shadow: 0 0 22px rgba(34,197,94,0.5); }
+.dm-perf-label__sub { font-size: 10.5px; letter-spacing: 0.05em; color: rgba(226,232,240,0.6); max-width: 180px; }
+
+@media (max-width: 767px) {
+ .dm-perf { height: 220vh; }
+ .dm-perf-label__sub { display: none; }
+ .dm-perf-label__tag { font-size: 15px; }
+ .dm-perf-head h2 { font-size: 22px; }
+ .dm-perf-status { gap: 10px; }
+}
+`;
diff --git a/src/components/performance/Vehicles.tsx b/src/components/performance/Vehicles.tsx
new file mode 100644
index 0000000..f1a7ba0
--- /dev/null
+++ b/src/components/performance/Vehicles.tsx
@@ -0,0 +1,183 @@
+"use client";
+
+import React, { useRef } from "react";
+import { useFrame } from "@react-three/fiber";
+import * as THREE from "three";
+
+export type VType = "bike" | "auto" | "van" | "truck";
+
+const TMP_POS = new THREE.Vector3();
+const TMP_TAN = new THREE.Vector3();
+
+/* ── Low-poly procedural vehicle bodies (modelled facing +Z) ──────────────
+ Body uses a lit standard material; accent strips + lights are emissive so
+ they pick up bloom. Wheels are a shared group that spins. */
+
+function Wheels({ positions, radius = 0.12, spinRef }: {
+ positions: [number, number, number][];
+ radius?: number;
+ spinRef: React.RefObject;
+}) {
+ return (
+
+ {positions.map((p, i) => (
+
+
+
+
+ ))}
+
+ );
+}
+
+function VehicleBody({ type, body, accent }: { type: VType; body: string; accent: string }) {
+ const wheels = useRef(null);
+ // spin the wheels for a sense of motion
+ useFrame((_, dt) => {
+ if (wheels.current) wheels.current.rotation.x += dt * 9;
+ });
+
+ const std = (color: string, e = 0.15) => (
+
+ );
+ const glow = (color: string) => (
+
+ );
+
+ if (type === "bike") {
+ return (
+
+
+
+ {std(body, 0.2)}
+
+
+
+ {std("#1e293b", 0.1)}
+
+
+
+ {glow(accent)}
+
+
+
+ {glow("#ffffff")}
+
+
+
+ );
+ }
+
+ if (type === "auto") {
+ // three-wheeled auto-rickshaw
+ return (
+
+
+
+ {std(body, 0.18)}
+
+
+
+ {std("#0b1220", 0.05)}
+
+
+
+ {glow(accent)}
+
+ {glow("#ffffff")}
+ {glow("#ffffff")}
+
+
+ );
+ }
+
+ if (type === "van") {
+ return (
+
+
+
+ {std(body, 0.16)}
+
+
+
+ {std("#1e293b", 0.05)}
+
+
+
+ {glow(accent)}
+
+ {glow("#ffffff")}
+ {glow("#ffffff")}
+
+
+ );
+ }
+
+ // truck — cab + long box trailer
+ return (
+
+
+
+ {std(body, 0.18)}
+
+
+
+ {std("#cbd5e1", 0.08)}
+
+
+
+ {glow(accent)}
+
+ {glow("#ffffff")}
+ {glow("#ffffff")}
+
+
+ );
+}
+
+export const VehicleBodyMemo = React.memo(VehicleBody);
+
+/**
+ * Locks a vehicle perfectly to a spline. Position = curve.getPointAt(u),
+ * heading = curve.getTangentAt(u). No drifting, no off-route movement.
+ */
+export function SplineRider({
+ curve,
+ speed,
+ offset,
+ type,
+ body,
+ accent,
+}: {
+ curve: THREE.CatmullRomCurve3;
+ speed: number;
+ offset: number;
+ type: VType;
+ body: string;
+ accent: string;
+}) {
+ const ref = useRef(null);
+ const u = useRef(offset);
+
+ useFrame((_, dt) => {
+ const g = ref.current;
+ if (!g) return;
+ u.current = (u.current + dt * speed) % 1;
+ curve.getPointAt(u.current, TMP_POS);
+ curve.getTangentAt(u.current, TMP_TAN);
+ g.position.copy(TMP_POS);
+ g.rotation.y = Math.atan2(TMP_TAN.x, TMP_TAN.z);
+ // gentle pitch so it follows slope without leaving the path
+ g.rotation.x = -Math.asin(THREE.MathUtils.clamp(TMP_TAN.y, -0.6, 0.6)) * 0.5;
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/performance/perfRoutes.ts b/src/components/performance/perfRoutes.ts
new file mode 100644
index 0000000..b95c0a3
--- /dev/null
+++ b/src/components/performance/perfRoutes.ts
@@ -0,0 +1,69 @@
+import * as THREE from "three";
+import { seeded } from "../optimization/math";
+
+/**
+ * Ground-level ROAD network for the Performance "Results & Impact" city.
+ * - LEFT district (x < 0): traditional dispatch — tangled, overlapping roads.
+ * - RIGHT district (x > 0): MileTruth optimized — clean, organized corridors.
+ * All curves are CLOSED and flat on the ground so fleet vehicles drive on the
+ * roads continuously (spline-locked, no end-of-curve teleport).
+ */
+
+export const ROUTE_Y = 0.05; // roads sit on the ground
+export const LEFT_C = new THREE.Vector3(-8, 0, 0);
+export const RIGHT_C = new THREE.Vector3(8, 0, 0);
+export const GATEWAY = new THREE.Vector3(0, 0, 0); // central transformation gateway
+
+export type PerfRoute = { curve: THREE.CatmullRomCurve3 };
+
+function closedFlat(points: THREE.Vector3[]): THREE.CatmullRomCurve3 {
+ const flat = points.map((p) => new THREE.Vector3(p.x, ROUTE_Y, p.z));
+ return new THREE.CatmullRomCurve3(flat, true, "catmullrom", 0.5);
+}
+
+let cache: { chaotic: PerfRoute[]; optimized: PerfRoute[] } | null = null;
+
+export function buildPerfRoutes() {
+ if (cache) return cache;
+
+ // --- LEFT: tangled, overlapping traffic loops ----------------------------
+ const chaotic: PerfRoute[] = [];
+ for (let r = 0; r < 5; r++) {
+ const pts: THREE.Vector3[] = [];
+ const n = 6 + Math.floor(seeded(r * 13 + 1) * 3);
+ let ang = seeded(r * 13 + 2) * Math.PI * 2;
+ for (let s = 0; s < n; s++) {
+ ang += (seeded(r * 13 + s * 3 + 3) - 0.5) * 2.7; // erratic detours
+ const rad = 1.8 + seeded(r * 7 + s + 4) * 4.2;
+ const x = LEFT_C.x + Math.cos(ang) * rad + (seeded(r * 5 + s) - 0.5) * 2.2;
+ const z = LEFT_C.z + Math.sin(ang) * rad + (seeded(r * 9 + s) - 0.5) * 2.2;
+ pts.push(new THREE.Vector3(x, 0, z));
+ }
+ chaotic.push({ curve: closedFlat(pts) });
+ }
+
+ // --- RIGHT: clean organized delivery corridors, one per zone -------------
+ const optimized: PerfRoute[] = [];
+ const zones: [number, number][] = [
+ [0, 0],
+ [2.8, 2.6],
+ [-2.8, 2.6],
+ [2.8, -2.6],
+ [-2.8, -2.6],
+ ];
+ zones.forEach(([ox, oz], r) => {
+ const pts: THREE.Vector3[] = [];
+ const n = 6;
+ const rad = r === 0 ? 4.8 : 1.5;
+ for (let s = 0; s < n; s++) {
+ const a = (s / n) * Math.PI * 2 + r * 0.3;
+ const x = RIGHT_C.x + ox + Math.cos(a) * rad;
+ const z = RIGHT_C.z + oz + Math.sin(a) * rad;
+ pts.push(new THREE.Vector3(x, 0, z));
+ }
+ optimized.push({ curve: closedFlat(pts) });
+ });
+
+ cache = { chaotic, optimized };
+ return cache;
+}
diff --git a/src/components/sections/BlogGrid.tsx b/src/components/sections/BlogGrid.tsx
index 389977b..a06bf29 100644
--- a/src/components/sections/BlogGrid.tsx
+++ b/src/components/sections/BlogGrid.tsx
@@ -158,10 +158,6 @@ export default function BlogGrid() {
color: #64748b !important;
line-height: 1.6 !important;
margin: 0 0 24px 0 !important;
- display: -webkit-box !important;
- -webkit-line-clamp: 3 !important;
- -webkit-box-orient: vertical !important;
- overflow: hidden !important;
text-transform: none !important;
font-family: var(--font-manrope), sans-serif !important;
}
diff --git a/src/components/sections/BlogsHero.tsx b/src/components/sections/BlogsHero.tsx
index 8c194d4..cfb22b8 100644
--- a/src/components/sections/BlogsHero.tsx
+++ b/src/components/sections/BlogsHero.tsx
@@ -4,6 +4,17 @@ import Link from "next/link";
export default function BlogsHero() {
return (
<>
+
-
+
Our Blogs
diff --git a/src/components/sections/IndexHero.tsx b/src/components/sections/IndexHero.tsx
index 9a6c2b2..68d4121 100644
--- a/src/components/sections/IndexHero.tsx
+++ b/src/components/sections/IndexHero.tsx
@@ -74,7 +74,15 @@ export default function IndexHero() {
zIndex: activeSlide === 0 ? 2 : 1
}}
>
-
+
@@ -107,7 +115,15 @@ export default function IndexHero() {
zIndex: activeSlide === 1 ? 2 : 1
}}
>
-
+
diff --git a/src/components/sections/Workflow2.tsx b/src/components/sections/Workflow2.tsx
index 2585b48..516a87f 100644
--- a/src/components/sections/Workflow2.tsx
+++ b/src/components/sections/Workflow2.tsx
@@ -1,170 +1,261 @@
"use client";
import React, { useState } from "react";
-import Image from "next/image";
+import { motion, AnimatePresence } from "framer-motion";
export default function Workflow2() {
const [activeSlide, setActiveSlide] = useState(0);
const slides = [
{
- title: "Innovation",
+ title: "INNOVATION",
text: "Our Parallel Universe Engine simultaneously evaluates multiple routing strategies to identify the most efficient delivery plan for every dispatch window. By simulating different route combinations in real time, the system ensures faster, smarter, and more cost-effective logistics decisions. This enables businesses to maintain high operational accuracy while adapting dynamically to changing delivery conditions."
},
{
- title: "Innovation",
+ title: "INNOVATION",
text: "The platform solves the EV routing challenge through intelligent battery-aware simulations and advanced optimization logic powered by Google OR-Tools. It balances delivery efficiency, charging constraints, and SLA priorities to maximize fleet performance without compromising reliability. This creates a scalable and future-ready logistics system designed for both traditional and EV fleets."
},
{
- title: "Innovation",
+ title: "INNOVATION",
text: "With sub-45ms inference latency and real-time ETA validation, the engine delivers instant routing decisions with exceptional precision. Multiple strategy universes are benchmarked in parallel to consistently select the best-performing route configuration. The result is highly reliable, SLA-first delivery operations with improved customer experience and operational consistency."
}
];
return (
- <>
-
+
+
+ {/* Left Column: Overlapping Chevron Graphic */}
+
+
+ {/* Top-Left Chevron Outline (Low Opacity) */}
+
+ {/* Bottom-Right Chevron Outline (High Opacity) */}
+
+
+
-
-
-
- {/* Left Column: Image with overlay */}
-
-
-
-
-
-
-
-
-
-
-
-
- Our Competitive Edge
-
-
-
-
-
-
-
-
-
-
+ {/* Right Column: Quotes & Text Content */}
+
+ {/* Slanted red quotes block */}
+
+
+
+
+
+ {/* Heading */}
+
+ {slides[activeSlide].title}
+
+
+ {/* Slide Paragraph with premium fade-in transition */}
+
+
+
+ {slides[activeSlide].text}
+
+
- {/* Right Column: Testimonial/Text Slider */}
-
-
-
-
- {/* Secondary Image inside right col */}
-
-
- {/* Slider */}
-
-
-
-
-
-
-
- {slides.map((slide, index) => (
-
-
-
{slide.title}
-
{slide.text}
-
-
- ))}
-
-
- {/* Slider Navigation Footer */}
-
-
-
-
- 0{activeSlide + 1}
- /
- 03
-
-
- {slides.map((_, index) => (
- setActiveSlide(index)}
- style={{ width: "10px", height: "10px", borderRadius: "50%", padding: 0 }}
- >
-
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
-
+ {/* Right-aligned Navigation (Counter + Indication lines) */}
+
+
+ 0{activeSlide + 1}/03
+
+
+ {slides.map((_, index) => (
+ setActiveSlide(index)}
+ />
+ ))}
-
- >
+
+
+
);
}
+
+const styles = `
+.dm-workflow-section {
+ max-width: 1700px;
+ margin: 20px auto;
+ padding: 0 40px;
+ box-sizing: border-box;
+}
+
+.dm-workflow-card {
+ position: relative;
+ background: #181818;
+ border-radius: 50px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ box-shadow:
+ 0 10px 40px -10px rgba(0, 0, 0, 0.5),
+ inset 0 1px 0 rgba(255, 255, 255, 0.05);
+ padding: 40px 60px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 40px;
+ overflow: hidden;
+ box-sizing: border-box;
+}
+
+/* Left Column - Graphic */
+.dm-workflow-left {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ max-width: 440px;
+}
+
+.dm-workflow-svg {
+ width: 100%;
+ height: auto;
+ filter: drop-shadow(0 8px 24px rgba(0,0,0,0.3));
+}
+
+/* Right Column - Text & Slide Content */
+.dm-workflow-right {
+ flex: 1.2;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 20px;
+}
+
+.dm-workflow-quote {
+ margin-bottom: 5px;
+}
+
+.dm-workflow-title {
+ font-family: var(--font-space-grotesk), var(--font-manrope), system-ui, sans-serif;
+ font-size: 38px;
+ font-weight: 700;
+ color: #F8FAFC !important;
+ letter-spacing: -0.015em;
+ margin: 0 !important;
+ padding: 0 !important;
+ text-transform: uppercase;
+}
+
+.dm-workflow-text-container {
+ min-height: 110px; /* Prevent layout shift when swapping slide texts */
+ width: 100%;
+}
+
+.dm-workflow-text {
+ font-family: var(--font-manrope), system-ui, sans-serif;
+ font-size: 16px;
+ line-height: 1.65;
+ color: #A3A3A3;
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+/* Navigation footer */
+.dm-workflow-nav {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 10px;
+ align-self: flex-end;
+ margin-top: 10px;
+}
+
+.dm-workflow-counter {
+ font-family: var(--font-space-grotesk), sans-serif;
+ font-size: 13px;
+ font-weight: 700;
+ color: #737373;
+ letter-spacing: 0.08em;
+}
+
+.dm-workflow-bars {
+ display: flex;
+ gap: 8px;
+}
+
+.dm-workflow-bar {
+ width: 40px;
+ height: 3px;
+ border: none;
+ padding: 0;
+ background: rgba(255, 255, 255, 0.15);
+ border-radius: 999px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.dm-workflow-bar.is-active {
+ background: #C01227;
+}
+
+.dm-workflow-bar:hover {
+ background: rgba(255, 255, 255, 0.35);
+}
+
+.dm-workflow-bar.is-active:hover {
+ background: #C01227;
+}
+
+/* Responsive design */
+@media (max-width: 1024px) {
+ .dm-workflow-card {
+ padding: 50px 50px;
+ gap: 50px;
+ border-radius: 40px;
+ }
+ .dm-workflow-title {
+ font-size: 32px;
+ }
+}
+
+@media (max-width: 768px) {
+ .dm-workflow-card {
+ flex-direction: column;
+ padding: 40px 30px;
+ gap: 40px;
+ border-radius: 30px;
+ }
+ .dm-workflow-left {
+ max-width: 280px;
+ }
+ .dm-workflow-right {
+ width: 100%;
+ }
+ .dm-workflow-title {
+ font-size: 28px;
+ }
+ .dm-workflow-text-container {
+ min-height: auto;
+ }
+}
+`;