From b909d6a497abb58fd58077313990a4c5dc96179d Mon Sep 17 00:00:00 2001 From: pranjalthebhatia Date: Sat, 4 Apr 2026 18:36:52 -0400 Subject: [PATCH 1/3] feat: add Next.js frontend-v2 with anatomical brain, real data & knowledge graph - Parametric anatomical brain mesh (hemispheres, fissure, gyri/sulci, brain stem) - 15 interactive region hotspots with activation-driven color/pulse/glow - Real TRIBE v2 mock data (15 regions, 30 timesteps) wired to all pages - 20 real personas with Big Five traits, VAD, demographics from backend mock data - 44-event #GreenFuture simulation with sentiment cascade narrative - Report with computed MHIS score derived from activation data - Knowledge graph page (229 nodes, 551 edges, 7 categories) with 3D viz - Dock navigation with magnification physics - React 19 + Three.js r170 + Next.js 15 + Tailwind v4 Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend-v2/next.config.ts | 14 + frontend-v2/package-lock.json | 2560 +++++++++++++++++ frontend-v2/package.json | 34 + frontend-v2/postcss.config.mjs | 8 + .../src/app/(dashboard)/brain/page.tsx | 334 +++ .../src/app/(dashboard)/graph/page.tsx | 481 ++++ frontend-v2/src/app/(dashboard)/layout.tsx | 14 + .../src/app/(dashboard)/personas/page.tsx | 244 ++ .../src/app/(dashboard)/report/page.tsx | 304 ++ .../src/app/(dashboard)/simulation/page.tsx | 412 +++ frontend-v2/src/app/globals.css | 124 + frontend-v2/src/app/layout.tsx | 40 + frontend-v2/src/app/page.tsx | 418 +++ frontend-v2/src/components/Dock.tsx | 156 + frontend-v2/src/components/NetworkGraph.tsx | 339 +++ .../src/components/reactbits/Aurora.tsx | 246 ++ .../src/components/reactbits/BlurText.tsx | 146 + .../src/components/reactbits/ClickSpark.tsx | 166 ++ .../src/components/reactbits/CountUp.tsx | 121 + .../components/reactbits/DecryptedText.tsx | 248 ++ .../src/components/reactbits/SplitText.tsx | 79 + .../src/components/three/BrainMesh.tsx | 100 + .../src/components/three/BrainScene.tsx | 213 ++ .../src/components/three/RegionHotspot.tsx | 130 + frontend-v2/src/data/graphData.ts | 807 ++++++ frontend-v2/src/data/personasData.ts | 43 + frontend-v2/src/data/simulationData.ts | 58 + frontend-v2/src/data/tribeData.ts | 99 + frontend-v2/src/lib/activationColor.ts | 22 + frontend-v2/src/lib/api.ts | 58 + frontend-v2/src/lib/mockDataTransformers.ts | 249 ++ frontend-v2/src/lib/regionData.ts | 122 + frontend-v2/src/lib/utils.ts | 6 + frontend-v2/src/types/brain.ts | 11 + frontend-v2/src/types/three-jsx.d.ts | 8 + frontend-v2/tailwind.config.ts | 18 + frontend-v2/tsconfig.json | 23 + 37 files changed, 8455 insertions(+) create mode 100644 frontend-v2/next.config.ts create mode 100644 frontend-v2/package-lock.json create mode 100644 frontend-v2/package.json create mode 100644 frontend-v2/postcss.config.mjs create mode 100644 frontend-v2/src/app/(dashboard)/brain/page.tsx create mode 100644 frontend-v2/src/app/(dashboard)/graph/page.tsx create mode 100644 frontend-v2/src/app/(dashboard)/layout.tsx create mode 100644 frontend-v2/src/app/(dashboard)/personas/page.tsx create mode 100644 frontend-v2/src/app/(dashboard)/report/page.tsx create mode 100644 frontend-v2/src/app/(dashboard)/simulation/page.tsx create mode 100644 frontend-v2/src/app/globals.css create mode 100644 frontend-v2/src/app/layout.tsx create mode 100644 frontend-v2/src/app/page.tsx create mode 100644 frontend-v2/src/components/Dock.tsx create mode 100644 frontend-v2/src/components/NetworkGraph.tsx create mode 100644 frontend-v2/src/components/reactbits/Aurora.tsx create mode 100644 frontend-v2/src/components/reactbits/BlurText.tsx create mode 100644 frontend-v2/src/components/reactbits/ClickSpark.tsx create mode 100644 frontend-v2/src/components/reactbits/CountUp.tsx create mode 100644 frontend-v2/src/components/reactbits/DecryptedText.tsx create mode 100644 frontend-v2/src/components/reactbits/SplitText.tsx create mode 100644 frontend-v2/src/components/three/BrainMesh.tsx create mode 100644 frontend-v2/src/components/three/BrainScene.tsx create mode 100644 frontend-v2/src/components/three/RegionHotspot.tsx create mode 100644 frontend-v2/src/data/graphData.ts create mode 100644 frontend-v2/src/data/personasData.ts create mode 100644 frontend-v2/src/data/simulationData.ts create mode 100644 frontend-v2/src/data/tribeData.ts create mode 100644 frontend-v2/src/lib/activationColor.ts create mode 100644 frontend-v2/src/lib/api.ts create mode 100644 frontend-v2/src/lib/mockDataTransformers.ts create mode 100644 frontend-v2/src/lib/regionData.ts create mode 100644 frontend-v2/src/lib/utils.ts create mode 100644 frontend-v2/src/types/brain.ts create mode 100644 frontend-v2/src/types/three-jsx.d.ts create mode 100644 frontend-v2/tailwind.config.ts create mode 100644 frontend-v2/tsconfig.json diff --git a/frontend-v2/next.config.ts b/frontend-v2/next.config.ts new file mode 100644 index 0000000..5113ba2 --- /dev/null +++ b/frontend-v2/next.config.ts @@ -0,0 +1,14 @@ +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = { + async rewrites() { + return [ + { + source: '/api/:path*', + destination: 'http://localhost:8000/:path*', + }, + ]; + }, +}; + +export default nextConfig; diff --git a/frontend-v2/package-lock.json b/frontend-v2/package-lock.json new file mode 100644 index 0000000..05dfe4a --- /dev/null +++ b/frontend-v2/package-lock.json @@ -0,0 +1,2560 @@ +{ + "name": "tribe-frontend-v2", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tribe-frontend-v2", + "version": "0.1.0", + "dependencies": { + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.5.0", + "@tailwindcss/postcss": "^4.2.2", + "@types/three": "^0.170.0", + "clsx": "^2.1.1", + "lucide-react": "^1.7.0", + "motion": "^11.15.0", + "next": "^15.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.5.0", + "three": "^0.170.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "autoprefixer": "^10.4.0", + "postcss": "^8.4.0", + "tailwindcss": "^4.2.2", + "typescript": "^5.6.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@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/@next/env": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.14.tgz", + "integrity": "sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.14.tgz", + "integrity": "sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.14.tgz", + "integrity": "sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.14.tgz", + "integrity": "sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.14.tgz", + "integrity": "sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.14.tgz", + "integrity": "sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.14.tgz", + "integrity": "sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.14.tgz", + "integrity": "sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.14.tgz", + "integrity": "sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "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.5.0", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz", + "integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==", + "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/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz", + "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "postcss": "^8.5.6", + "tailwindcss": "4.2.2" + } + }, + "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/@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/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "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.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@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.170.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.170.0.tgz", + "integrity": "sha512-CUm2uckq+zkCY7ZbFpviRttY+6f9fvwm6YqSqPfA5K22s9w7R4VnA3rzJse8kHVvuzLcTx+CjNCs2NYe0QFAyg==", + "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/@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.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.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.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.14.tgz", + "integrity": "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.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/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "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/camera-controls": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz", + "integrity": "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==", + "license": "MIT", + "engines": { + "node": ">=22.0.0", + "npm": ">=10.5.1" + }, + "peerDependencies": { + "three": ">=0.126.1" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001785", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz", + "integrity": "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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/electron-to-chromium": { + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "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/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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/hls.js": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.15.tgz", + "integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==", + "license": "Apache-2.0" + }, + "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/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "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/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "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.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/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", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucide-react": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz", + "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "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", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/meshline": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz", + "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/motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/motion/-/motion-11.18.2.tgz", + "integrity": "sha512-JLjvFDuFr42NFtcVoMAyC2sEjnpA8xpy6qWPyzQvCloznAyQ8FIXioxWfHiLtgYhoVpfUqSWpn1h9++skj9+Wg==", + "license": "MIT", + "dependencies": { + "framer-motion": "^11.18.2", + "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/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "15.5.14", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz", + "integrity": "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.14", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.14", + "@next/swc-darwin-x64": "15.5.14", + "@next/swc-linux-arm64-gnu": "15.5.14", + "@next/swc-linux-arm64-musl": "15.5.14", + "@next/swc-linux-x64-gnu": "15.5.14", + "@next/swc-linux-x64-musl": "15.5.14", + "@next/swc-win32-arm64-msvc": "15.5.14", + "@next/swc-win32-x64-msvc": "15.5.14", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "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/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/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-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/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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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.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/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "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/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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/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/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/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "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/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/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==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/frontend-v2/package.json b/frontend-v2/package.json new file mode 100644 index 0000000..a8ab320 --- /dev/null +++ b/frontend-v2/package.json @@ -0,0 +1,34 @@ +{ + "name": "tribe-frontend-v2", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.5.0", + "@tailwindcss/postcss": "^4.2.2", + "@types/three": "^0.170.0", + "clsx": "^2.1.1", + "lucide-react": "^1.7.0", + "motion": "^11.15.0", + "next": "^15.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.5.0", + "three": "^0.170.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "autoprefixer": "^10.4.0", + "postcss": "^8.4.0", + "tailwindcss": "^4.2.2", + "typescript": "^5.6.0" + } +} diff --git a/frontend-v2/postcss.config.mjs b/frontend-v2/postcss.config.mjs new file mode 100644 index 0000000..79bcf13 --- /dev/null +++ b/frontend-v2/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/frontend-v2/src/app/(dashboard)/brain/page.tsx b/frontend-v2/src/app/(dashboard)/brain/page.tsx new file mode 100644 index 0000000..0d08161 --- /dev/null +++ b/frontend-v2/src/app/(dashboard)/brain/page.tsx @@ -0,0 +1,334 @@ +"use client"; + +import { useState, useEffect, useMemo } from "react"; +import dynamic from "next/dynamic"; +import { motion } from "motion/react"; +import { REGIONS, NETWORK_COLORS } from "@/lib/regionData"; +import { activationToCss } from "@/lib/activationColor"; +import type { RegionActivation } from "@/types/brain"; + +const BrainScene = dynamic(() => import("@/components/three/BrainScene"), { + ssr: false, + loading: () => ( +
+ loading 3d... +
+ ), +}); + +import { loadBrainActivations } from "@/lib/mockDataTransformers"; + +// ---------- sub-components ---------- + +function HeatmapRow({ + region, + isSelected, + onClick, +}: { + region: RegionActivation; + isSelected: boolean; + onClick: () => void; +}) { + return ( + + ); +} + +function RadarChart({ + data, +}: { + data: { label: string; value: number; color: string }[]; +}) { + const size = 200; + const cx = size / 2; + const cy = size / 2; + const r = 70; + + const points = data.map((d, i) => { + const angle = (Math.PI * 2 * i) / data.length - Math.PI / 2; + return { + ...d, + x: cx + Math.cos(angle) * r * d.value, + y: cy + Math.sin(angle) * r * d.value, + lx: cx + Math.cos(angle) * (r + 16), + ly: cy + Math.sin(angle) * (r + 16), + }; + }); + + const polygon = points.map((p) => `${p.x},${p.y}`).join(" "); + + return ( + + {/* grid rings */} + {[0.25, 0.5, 0.75, 1].map((s) => ( + + ))} + {/* axes */} + {points.map((p, i) => ( + + ))} + {/* data polygon */} + + {/* labels */} + {points.map((p, i) => ( + + {p.label} + + ))} + + ); +} + +function RegionDetail({ region }: { region: RegionActivation }) { + const meta = REGIONS[region.region_id]; + return ( +
+
+

{region.region_name}

+ + {region.network} network / {region.lobe} lobe + +
+ {meta && ( +

+ {meta.description} +

+ )} +
+
+
Mean
+
+ {region.mean_activation.toFixed(3)} +
+
+
+
Peak
+
+ {Math.max(...region.activations).toFixed(3)} +
+
+
+
Peak t
+
+ t={region.peak_timestep} +
+
+
+
+ ); +} + +// ---------- main page ---------- + +export default function BrainPage() { + const [activations, setActivations] = useState>({}); + const [selectedRegion, setSelectedRegion] = useState(null); + + useEffect(() => { + setActivations(loadBrainActivations()); + }, []); + + const regions = useMemo(() => Object.values(activations), [activations]); + + const networkAverages = useMemo(() => { + const nets: Record = {}; + regions.forEach((r) => { + if (!nets[r.network]) nets[r.network] = []; + nets[r.network].push(r.mean_activation); + }); + return Object.entries(nets).map(([net, vals]) => ({ + label: net.slice(0, 4), + value: vals.reduce((a, b) => a + b, 0) / vals.length, + color: NETWORK_COLORS[net] || "#888", + })); + }, [regions]); + + const selected = selectedRegion ? activations[selectedRegion] : null; + + return ( +
+ {/* Header */} + +

Neural Activation

+

+ Region-level activation heatmap across 30 timesteps +

+
+ +
+ {/* Heatmap — spans 8 cols */} + +
+

+ Activation Heatmap +

+
+ low +
+ {[0, 0.25, 0.5, 0.75, 1].map((v) => ( +
+ ))} +
+ high +
+
+
+ {regions.map((r) => ( + + setSelectedRegion( + r.region_id === selectedRegion ? null : r.region_id + ) + } + /> + ))} +
+ + + {/* Right column — spans 4 cols */} +
+ {/* 3D Brain */} + + + + + {/* Radar chart */} + +

+ Network Averages +

+
+ +
+
+ + {/* Region detail or placeholder */} + +

+ Region Detail +

+ {selected ? ( + + ) : ( +

+ Click a row in the heatmap to inspect a region. +

+ )} +
+ + {/* Network legend */} + +

+ Networks +

+
+ {Object.entries(NETWORK_COLORS).map(([net, color]) => ( +
+
+ + {net} + +
+ ))} +
+ +
+
+
+ ); +} diff --git a/frontend-v2/src/app/(dashboard)/graph/page.tsx b/frontend-v2/src/app/(dashboard)/graph/page.tsx new file mode 100644 index 0000000..6171629 --- /dev/null +++ b/frontend-v2/src/app/(dashboard)/graph/page.tsx @@ -0,0 +1,481 @@ +"use client"; + +import { useState, useMemo, useCallback } from "react"; +import dynamic from "next/dynamic"; +import { motion, AnimatePresence } from "motion/react"; +import { + GRAPH_NODES, + GRAPH_EDGES, + CATEGORIES, + type GraphNode, +} from "@/data/graphData"; + +const NetworkGraph = dynamic(() => import("@/components/NetworkGraph"), { + ssr: false, + loading: () => ( +
+ loading graph... +
+ ), +}); + +/* ─── Category toggle ────────────────────────────────────────────── */ + +type CategoryKey = keyof typeof CATEGORIES; +const ALL_CATEGORIES = Object.keys(CATEGORIES) as CategoryKey[]; + +function CategoryToggle({ + category, + active, + count, + onToggle, +}: { + category: CategoryKey; + active: boolean; + count: number; + onToggle: () => void; +}) { + const meta = CATEGORIES[category]; + return ( + + ); +} + +/* ─── Node detail panel ──────────────────────────────────────────── */ + +function NodeDetail({ + node, + connections, + onClose, +}: { + node: GraphNode; + connections: { incoming: typeof GRAPH_EDGES; outgoing: typeof GRAPH_EDGES }; + onClose: () => void; +}) { + const meta = CATEGORIES[node.category]; + + return ( + +
+
+
+ + + {meta.label} + +
+

{node.label}

+

+ {node.id} +

+
+ +
+ + {connections.outgoing.length > 0 && ( +
+

+ Outgoing ({connections.outgoing.length}) +

+
+ {connections.outgoing.map((e, i) => { + const target = GRAPH_NODES.find((n) => n.id === e.to); + return ( +
+ {e.type} + + + {target?.label ?? e.to} + +
+ ); + })} +
+
+ )} + + {connections.incoming.length > 0 && ( +
+

+ Incoming ({connections.incoming.length}) +

+
+ {connections.incoming.map((e, i) => { + const source = GRAPH_NODES.find((n) => n.id === e.from); + return ( +
+ + {source?.label ?? e.from} + + + {e.type} +
+ ); + })} +
+
+ )} +
+ ); +} + +/* ─── Stats bar ──────────────────────────────────────────────────── */ + +function StatPill({ label, value }: { label: string; value: string | number }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +/* ─── Main page ──────────────────────────────────────────────────── */ + +export default function GraphPage() { + const [activeCategories, setActiveCategories] = useState>( + new Set(ALL_CATEGORIES) + ); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedNodeId, setSelectedNodeId] = useState(null); + + const toggleCategory = useCallback((cat: CategoryKey) => { + setActiveCategories((prev) => { + const next = new Set(prev); + if (next.has(cat)) { + if (next.size > 1) next.delete(cat); + } else { + next.add(cat); + } + return next; + }); + }, []); + + // Filter nodes by active categories and search + const filteredNodes = useMemo(() => { + let nodes = GRAPH_NODES.filter((n) => activeCategories.has(n.category)); + if (searchQuery.trim()) { + const q = searchQuery.toLowerCase(); + nodes = nodes.filter( + (n) => + n.label.toLowerCase().includes(q) || + n.id.toLowerCase().includes(q) + ); + } + return nodes; + }, [activeCategories, searchQuery]); + + const filteredNodeIds = useMemo( + () => new Set(filteredNodes.map((n) => n.id)), + [filteredNodes] + ); + + // Filter edges to only include visible nodes + const filteredEdges = useMemo( + () => + GRAPH_EDGES.filter( + (e) => filteredNodeIds.has(e.from) && filteredNodeIds.has(e.to) + ), + [filteredNodeIds] + ); + + // Convert to NetworkGraph format (agents + interactions) + const agents = useMemo( + () => + filteredNodes.map((n) => { + const meta = CATEGORIES[n.category]; + const col = meta.color; + // Map color to rough sentiment for the NetworkGraph component + const sentimentMap: Record = { + "#e74c3c": -0.3, // BR - slightly negative (warm red) + "#e67e22": 0.1, // NET - slightly positive + "#2ecc71": 0.7, // EMO - positive (green) + "#3498db": 0.4, // COG - positive (blue) + "#9b59b6": -0.1, // SUB - neutral + "#e91e63": 0.2, // MKT - slightly positive + "#95a5a6": 0.0, // PAP - neutral + }; + // Influence based on edge count + const edgeCount = GRAPH_EDGES.filter( + (e) => e.from === n.id || e.to === n.id + ).length; + return { + id: n.id, + sentiment: sentimentMap[col] ?? 0, + influence: Math.min(1, edgeCount / 30), + }; + }), + [filteredNodes] + ); + + const interactions = useMemo( + () => + filteredEdges.map((e) => ({ + from: e.from, + to: e.to, + type: e.type, + })), + [filteredEdges] + ); + + // Selected node detail + const selectedNode = useMemo( + () => (selectedNodeId ? GRAPH_NODES.find((n) => n.id === selectedNodeId) : null), + [selectedNodeId] + ); + + const selectedConnections = useMemo(() => { + if (!selectedNodeId) return { incoming: [], outgoing: [] }; + return { + outgoing: GRAPH_EDGES.filter((e) => e.from === selectedNodeId), + incoming: GRAPH_EDGES.filter((e) => e.to === selectedNodeId), + }; + }, [selectedNodeId]); + + // Category counts + const categoryCounts = useMemo(() => { + const counts: Record = {}; + for (const cat of ALL_CATEGORIES) { + counts[cat] = GRAPH_NODES.filter((n) => n.category === cat).length; + } + return counts; + }, []); + + // Edge type distribution + const edgeTypeStats = useMemo(() => { + const types: Record = {}; + for (const e of filteredEdges) { + const t = e.type.includes("findings") ? "Paper Findings" : e.type; + types[t] = (types[t] || 0) + 1; + } + return Object.entries(types) + .sort((a, b) => b[1] - a[1]) + .slice(0, 6); + }, [filteredEdges]); + + return ( +
+ {/* Header */} + +

Knowledge Graph

+

+ {filteredNodes.length} nodes / {filteredEdges.length} edges — GraphRAG + ontology from neuroscience literature +

+
+ + {/* Stats row */} +
+
+ + + + 1 + ? ( + (2 * filteredEdges.length) / + (filteredNodes.length * (filteredNodes.length - 1)) + ).toFixed(4) + : "—" + } + /> +
+
+ + {/* Main content */} +
+ {/* Left sidebar: filters */} + + {/* Search */} +
+ setSearchQuery(e.target.value)} + placeholder="Search nodes..." + className="w-full bg-transparent text-xs font-mono text-white/80 placeholder-white/30 outline-none" + /> +
+ + {/* Category filters */} +
+

+ Categories +

+
+ {ALL_CATEGORIES.map((cat) => ( + toggleCategory(cat)} + /> + ))} +
+
+ + {/* Edge type breakdown */} +
+

+ Edge Types +

+
+ {edgeTypeStats.map(([type, count]) => ( +
+ + {type} + + + {count} + +
+ ))} +
+
+ + {/* Search results list (when searching) */} + {searchQuery.trim() && ( +
+

+ Results ({filteredNodes.length}) +

+
+ {filteredNodes.slice(0, 30).map((n) => ( + + ))} +
+
+ )} +
+ + {/* Center: graph visualization */} + +
+ +
+ + {/* Legend overlay */} +
+ {ALL_CATEGORIES.filter((c) => activeCategories.has(c)).map( + (cat) => ( +
+ + + {CATEGORIES[cat].label} + +
+ ) + )} +
+
+ + {/* Right sidebar: node detail */} +
+ + {selectedNode ? ( + setSelectedNodeId(null)} + /> + ) : ( + +

+ Node Inspector +

+

+ Search for a node or click a category to explore the + knowledge graph. The graph visualizes relationships between + brain regions, neural networks, cognitive processes, emotions, + and marketing implications extracted from neuroscience + literature via GraphRAG. +

+
+ )} +
+
+
+
+ ); +} diff --git a/frontend-v2/src/app/(dashboard)/layout.tsx b/frontend-v2/src/app/(dashboard)/layout.tsx new file mode 100644 index 0000000..8d8ca4b --- /dev/null +++ b/frontend-v2/src/app/(dashboard)/layout.tsx @@ -0,0 +1,14 @@ +import Dock from "@/components/Dock"; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <> +
{children}
+ + + ); +} diff --git a/frontend-v2/src/app/(dashboard)/personas/page.tsx b/frontend-v2/src/app/(dashboard)/personas/page.tsx new file mode 100644 index 0000000..a59154c --- /dev/null +++ b/frontend-v2/src/app/(dashboard)/personas/page.tsx @@ -0,0 +1,244 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { motion } from "motion/react"; + +import { loadPersonas, type Persona } from "@/lib/mockDataTransformers"; + +interface PersonaTrait { + name: string; + score: number; +} + +// ---------- sub-components ---------- + +function TraitBar({ name, score }: { name: string; score: number }) { + return ( +
+ + {name} + +
+
+
+ + {(score * 100).toFixed(0)} + +
+ ); +} + +function ValenceIndicator({ value }: { value: number }) { + // value is -1 to 1, map to 0-100 for position + const pct = ((value + 1) / 2) * 100; + return ( +
+
+ negative + positive +
+
+
+
+
+ ); +} + +function PersonaCard({ + persona, + isSelected, + onClick, +}: { + persona: Persona; + isSelected: boolean; + onClick: () => void; +}) { + return ( + + ); +} + +function PersonaDetail({ persona }: { persona: Persona }) { + return ( +
+
+

{persona.name}

+
+ + {persona.archetype} + + + age {persona.age} + +
+
+ +

{persona.summary}

+ + {/* VAD metrics */} +
+

+ Emotional State (VAD) +

+
+
+
+ Valence +
+ +
+
+
+
Arousal
+
+ {persona.arousal.toFixed(2)} +
+
+
+
Dominance
+
+ {persona.dominance.toFixed(2)} +
+
+
+
+
+ + {/* Big Five traits */} +
+

+ Big Five Traits +

+
+ {persona.traits.map((t) => ( + + ))} +
+
+ + {/* Influence susceptibility */} +
+

+ Influence Susceptibility +

+
+
+
+
+ + {(persona.influence_susceptibility * 100).toFixed(0)}% + +
+
+
+ ); +} + +// ---------- main page ---------- + +export default function PersonasPage() { + const [personas, setPersonas] = useState([]); + const [selectedId, setSelectedId] = useState(null); + + useEffect(() => { + const data = loadPersonas(); + setPersonas(data); + setSelectedId(data[0].id); + }, []); + + const selected = personas.find((p) => p.id === selectedId) || null; + + return ( +
+ {/* Header */} + +

Personas

+

+ {personas.length} synthetic personas generated for this campaign +

+
+ +
+ {/* Card grid */} +
+ {personas.map((p, i) => ( + + setSelectedId(p.id)} + /> + + ))} +
+ + {/* Detail panel */} + + {selected ? ( + + ) : ( +

+ Select a persona to view details. +

+ )} +
+
+
+ ); +} diff --git a/frontend-v2/src/app/(dashboard)/report/page.tsx b/frontend-v2/src/app/(dashboard)/report/page.tsx new file mode 100644 index 0000000..327ca57 --- /dev/null +++ b/frontend-v2/src/app/(dashboard)/report/page.tsx @@ -0,0 +1,304 @@ +"use client"; + +import { useState, useMemo } from "react"; +import { motion, AnimatePresence } from "motion/react"; +import { loadBrainActivations, loadPersonas, loadSimulationEvents } from "@/lib/mockDataTransformers"; + +// ---------- data ---------- + +const findings = [ + { + severity: "critical" as const, + title: + "Bilateral amygdala spike (0.89) at t=14s triggered fear-based engagement", + detail: + "The fMRI-equivalent neural encoding detected a sharp bilateral amygdala activation peak of 0.89 at the 14-second mark, well above the 0.60 threshold for fear/threat response classification. This activation pattern is consistent with content that triggers survival-oriented attention capture rather than rational evaluation. Recommendation: remove or soften the visual stimulus at t=12\u201316s and re-test with a target activation ceiling of 0.55.", + }, + { + severity: "high" as const, + title: + "Prefrontal cortex suppression during peak activation indicates reduced critical thinking", + detail: + "Dorsolateral prefrontal cortex activity dropped to 0.22 (baseline: 0.48) during the amygdala spike window, indicating a significant suppression of executive function and critical evaluation capacity. This amygdala\u2013PFC inverse correlation (r = \u22120.87) suggests the content bypasses rational assessment. Recommendation: introduce a 2\u20133 second informational pause before any call-to-action that follows high-arousal segments.", + }, + { + severity: "medium" as const, + title: + "Low trust baseline (0.30) combined with high threat response created backlash conditions", + detail: + "The social simulation seeded with the neural profile revealed that agents with trust scores below 0.35 (representing ~40% of the simulated population) interpreted the fear-based engagement as manipulative intent. This triggered a sentiment reversal from +0.82 to \u22120.81 over steps 8\u201319 \u2014 a backlash pattern that is difficult to reverse once established. Recommendation: pre-segment audiences by trust baseline and deploy alternative creative for low-trust cohorts.", + }, + { + severity: "low" as const, + title: + "Motor cortex remained flat \u2014 no action-oriented response triggered", + detail: + "Primary and supplementary motor cortex activation stayed within noise range (0.04\u20130.09) throughout the stimulus. While this means the content did not trigger impulsive physical action (a positive safety signal), it also indicates the campaign fails to motivate constructive behavioral change \u2014 the stated campaign objective. Recommendation: add embodied call-to-action elements (e.g., pledge buttons, share prompts) at low-arousal moments to convert attention into action.", + }, +]; + +const pipelineSteps = [ + { num: 1, name: "Neuro-Translator", result: "Amygdala spike mapped to threat/fear circuit" }, + { num: 2, name: "Social Analyst", result: "Backlash cascade detected at step 8" }, + { num: 3, name: "Strategist", result: "5 mitigation recommendations generated" }, + { num: 4, name: "Guardrails", result: "All ethical thresholds passed" }, +]; + +const recommendations = [ + { priority: "P0" as const, text: "Reduce fear-inducing imagery in campaign seconds 12\u201316" }, + { priority: "P1" as const, text: "Add reflection prompts before share actions" }, + { priority: "P1" as const, text: "Pre-test campaigns with high-neuroticism user segments" }, + { priority: "P2" as const, text: "Add call-to-action elements for constructive engagement" }, + { priority: "P2" as const, text: "Implement real-time sentiment monitoring with kill switch" }, +]; + +const metrics = [ + { label: "Peak Amygdala", value: "0.89" }, + { label: "Sentiment Shift", value: "+0.82 \u2192 \u22120.81" }, + { label: "Amplifier Agents", value: "3" }, + { label: "Peak Reach", value: "61" }, + { label: "Risk Window", value: "Steps 8\u201319" }, + { label: "Trust Baseline", value: "0.30" }, +]; + +// ---------- severity colors ---------- + +const severityColor: Record = { + critical: "#ef4444", + high: "#f97316", + medium: "#eab308", + low: "#22c55e", +}; + +// ---------- priority styles ---------- + +const priorityStyle: Record = { + P0: "bg-red-500/20 text-red-400", + P1: "bg-orange-500/20 text-orange-400", + P2: "bg-yellow-500/20 text-yellow-400", +}; + +// ---------- expandable finding ---------- + +function Finding({ + index, + severity, + title, + detail, +}: { + index: number; + severity: "critical" | "high" | "medium" | "low"; + title: string; + detail: string; +}) { + const [open, setOpen] = useState(false); + + return ( +
  • + + + {open && ( + +

    + {detail} +

    +
    + )} +
    +
  • + ); +} + +// ---------- main page ---------- + +export default function ReportPage() { + const derived = useMemo(() => { + const acts = loadBrainActivations(); + const personas = loadPersonas(); + const events = loadSimulationEvents(); + const emotionIds = ["amygdala_left","amygdala_right","nucleus_accumbens","anterior_insula","anterior_cingulate"]; + const emotionMean = emotionIds.map(id => acts[id]?.mean_activation ?? 0).reduce((a,b) => a+b, 0) / emotionIds.length; + const pfcMean = acts["prefrontal_cortex"]?.mean_activation ?? 0.5; + const mhis = Math.min(1, Math.max(0, emotionMean * 0.6 + (1 - pfcMean) * 0.4)); + const avgSent = events.reduce((s,e) => s + e.sentiment, 0) / events.length; + return { mhis, personaCount: personas.length, eventCount: events.length, avgSentiment: avgSent }; + }, []); + + return ( +
    +
    + + {/* ── 1. Report Header ── */} +
    +

    + Diagnostic Report +

    +
    + NSP-2024-0847 + April 4, 2026 +
    +

    + #GreenFuture — Sustainability Campaign +

    +
    + + + COMPLETE + + + + GUARDRAILS: PASSED + +
    +
    + + {/* ── 2. Executive Summary ── */} +
    +

    + Executive Summary +

    +

    + Campaign analysis reveals a high-risk mental health impact pattern. + The TRIBE v2 neural encoding detected significant bilateral amygdala + activation peaking at t=14s, correlating with threat/fear response + circuits. When seeded into the social simulation, this produced a + predictable sentiment cascade: initial positive engagement (steps + 0–7) followed by rapid negative backlash (steps 8–19) + driven by 3 key amplifier agents. The combination of fear-based + neural triggers and low baseline trust created conditions where + campaign messaging was initially engaging but ultimately perceived as + deceptive. +

    +

    + The campaign scored{" "} + + {derived.mhis.toFixed(2)} + {" "} + ({derived.mhis > 0.6 ? "High Risk" : derived.mhis > 0.4 ? "Medium Risk" : "Low Risk"}) on the Mental Health Impact Scale. Analysis based on {derived.personaCount} personas and {derived.eventCount} simulation events. +

    +
    + + {/* ── 3. Key Findings ── */} +
    +

    + Key Findings +

    +
      + {findings.map((f, i) => ( + + ))} +
    +
    + + {/* ── 4. Agent Pipeline ── */} +
    +

    + Agent Pipeline +

    +
    + {pipelineSteps.map((step, i) => ( +
    +
    +
    + + {step.num} + + + {step.name} + +
    +

    + {step.result} +

    +
    + {i < pipelineSteps.length - 1 && ( + + )} +
    + ))} +
    +
    + + {/* ── 5. Recommendations ── */} +
    +

    + Recommendations +

    +
      + {recommendations.map((rec, i) => ( +
    1. + + {i + 1}. + + + {rec.priority} + + + {rec.text} + +
    2. + ))} +
    +
    + + {/* ── 6. Appendix: Key Metrics ── */} +
    +

    + Appendix: Key Metrics +

    +
    + {metrics.map((m) => ( +
    +

    {m.label}

    +

    + {m.value} +

    +
    + ))} +
    +
    + + {/* ── Footer ── */} +
    +

    + Generated by TRIBE v2 Neuro-Social Platform. This report is for + research and evaluation purposes only. Methodology conforms to NSP + Ethical AI Framework v3.1. +

    +
    +
    +
    + ); +} diff --git a/frontend-v2/src/app/(dashboard)/simulation/page.tsx b/frontend-v2/src/app/(dashboard)/simulation/page.tsx new file mode 100644 index 0000000..dfc0a15 --- /dev/null +++ b/frontend-v2/src/app/(dashboard)/simulation/page.tsx @@ -0,0 +1,412 @@ +"use client"; + +import { useState, useEffect, useRef, useCallback } from "react"; +import { motion } from "motion/react"; + +import { loadSimulationEvents, type SimEvent } from "@/lib/mockDataTransformers"; + +const EVENT_TYPES: SimEvent["event_type"][] = [ + "reaction", + "comment", + "share", + "scroll_past", + "rewatch", +]; + +// ---------- sub-components ---------- + +const EVENT_ICONS: Record = { + reaction: "RXN", + comment: "CMT", + share: "SHR", + scroll_past: "SKP", + rewatch: "RPT", +}; + +function FeedItem({ event }: { event: SimEvent }) { + const sentimentColor = + event.sentiment > 0.1 + ? "text-green-500" + : event.sentiment < -0.1 + ? "text-red-500" + : "text-white/40"; + + return ( +
    + {event.timestamp} + {event.persona_name} + + {EVENT_ICONS[event.event_type]} + + {event.content} + + {event.sentiment > 0 ? "+" : ""} + {event.sentiment.toFixed(2)} + +
    + ); +} + +function SentimentTimeline({ events }: { events: SimEvent[] }) { + if (events.length === 0) return null; + + const width = 600; + const height = 80; + const padX = 20; + const padY = 10; + const plotW = width - padX * 2; + const plotH = height - padY * 2; + + // bucket events by timestep, average sentiment + const buckets: Record = {}; + events.forEach((e) => { + if (!buckets[e.timestep]) buckets[e.timestep] = []; + buckets[e.timestep].push(e.sentiment); + }); + + const maxT = Math.max(...events.map((e) => e.timestep), 1); + + const points = Object.entries(buckets) + .sort((a, b) => Number(a[0]) - Number(b[0])) + .map(([t, vals]) => { + const avg = vals.reduce((a, b) => a + b, 0) / vals.length; + const x = padX + (Number(t) / maxT) * plotW; + const y = padY + ((1 - (avg + 1) / 2) * plotH); // map -1..1 to plotH..0 + return { x, y, avg }; + }); + + const pathD = points + .map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`) + .join(" "); + + return ( + + {/* zero line */} + + {/* positive/negative labels */} + + +1 + + + -1 + + {/* line */} + + {/* dots */} + {points.map((p, i) => ( + 0.1 ? "#22c55e" : p.avg < -0.1 ? "#ef4444" : "#888"} + /> + ))} + + ); +} + +function StatCard({ + label, + value, + sub, +}: { + label: string; + value: string; + sub?: string; +}) { + return ( +
    +
    + {label} +
    +
    {value}
    + {sub && ( +
    {sub}
    + )} +
    + ); +} + +// ---------- main page ---------- + +export default function SimulationPage() { + const allEvents = useRef([]); + const [visibleEvents, setVisibleEvents] = useState([]); + const [isPlaying, setIsPlaying] = useState(true); + const feedRef = useRef(null); + const timerRef = useRef | null>(null); + const indexRef = useRef(0); + + // Generate full feed on mount + useEffect(() => { + allEvents.current = loadSimulationEvents(); + }, []); + + // Auto-play + useEffect(() => { + if (!isPlaying) { + if (timerRef.current) clearInterval(timerRef.current); + return; + } + + timerRef.current = setInterval(() => { + if (indexRef.current >= allEvents.current.length) { + setIsPlaying(false); + return; + } + const next = allEvents.current[indexRef.current]; + indexRef.current += 1; + setVisibleEvents((prev) => [...prev, next]); + }, 400); + + return () => { + if (timerRef.current) clearInterval(timerRef.current); + }; + }, [isPlaying]); + + // Auto-scroll feed + useEffect(() => { + if (feedRef.current) { + feedRef.current.scrollTop = feedRef.current.scrollHeight; + } + }, [visibleEvents]); + + // Stats + const totalEvents = visibleEvents.length; + const avgSentiment = + totalEvents > 0 + ? visibleEvents.reduce((s, e) => s + e.sentiment, 0) / totalEvents + : 0; + const typeBreakdown = visibleEvents.reduce>( + (acc, e) => { + acc[e.event_type] = (acc[e.event_type] || 0) + 1; + return acc; + }, + {} + ); + + const handleReset = useCallback(() => { + indexRef.current = 0; + setVisibleEvents([]); + setIsPlaying(true); + }, []); + + return ( +
    + {/* Header */} + +

    Simulation Feed

    +

    + Real-time persona reactions to campaign content +

    +
    + + {/* Controls */} +
    + + + + {totalEvents} / {allEvents.current.length} events + + {isPlaying && ( + LIVE + )} +
    + + {/* Stats row */} + + + 0 ? `+${avgSentiment.toFixed(2)}` : avgSentiment.toFixed(2)} + sub={avgSentiment > 0.1 ? "positive" : avgSentiment < -0.1 ? "negative" : "neutral"} + /> + + + 0 + ? `${(((typeBreakdown["scroll_past"] || 0) / totalEvents) * 100).toFixed(0)}%` + : "0%" + } + /> + + +
    + {/* Feed — terminal style */} + + {/* Terminal header */} +
    +
    +
    +
    + + simulation_feed.log + +
    + {/* Feed header row */} +
    + Time + Persona + Type + Content + Sent +
    + {/* Scrollable feed */} +
    + {visibleEvents.map((event) => ( + + ))} + {visibleEvents.length === 0 && ( +
    + + Waiting for events... + +
    + )} +
    + + + {/* Sidebar — sentiment chart + breakdown */} +
    + +

    + Sentiment Over Time +

    + +
    + + +

    + Event Breakdown +

    +
    + {EVENT_TYPES.map((type) => { + const count = typeBreakdown[type] || 0; + const pct = totalEvents > 0 ? (count / totalEvents) * 100 : 0; + return ( +
    + + {EVENT_ICONS[type]} + +
    +
    +
    + + {count} + +
    + ); + })} +
    + + + +

    + Persona Activity +

    +
    + {[...new Set(allEvents.current.map((e) => e.persona_name))].map((name) => { + const count = visibleEvents.filter( + (e) => e.persona_name === name + ).length; + return ( +
    + + {name} + +
    +
    0 ? (count / totalEvents) * 100 : 0 + }%`, + }} + /> +
    + + {count} + +
    + ); + })} +
    + +
    +
    +
    + ); +} diff --git a/frontend-v2/src/app/globals.css b/frontend-v2/src/app/globals.css new file mode 100644 index 0000000..c3b2711 --- /dev/null +++ b/frontend-v2/src/app/globals.css @@ -0,0 +1,124 @@ +@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap"); +@import "tailwindcss"; + +/* -------------------------------------------------------- + Design Tokens + -------------------------------------------------------- */ + +@theme inline { + --color-bg: #08080c; + --color-fg: #e4e4e7; + --color-accent: #FF4500; + --color-surface: #111116; + --color-border: rgba(255, 255, 255, 0.06); + + --font-jetbrains-mono: "JetBrains Mono", monospace; +} + +:root { + --bg: #08080c; + --fg: #e4e4e7; + --accent: #FF4500; + --surface: #111116; + --border: rgba(255, 255, 255, 0.06); +} + +/* -------------------------------------------------------- + Base + -------------------------------------------------------- */ + +html { + scroll-behavior: smooth; +} + +body { + background: var(--bg); + color: var(--fg); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +::selection { + background: rgba(255, 69, 0, 0.25); +} + +/* -------------------------------------------------------- + Scrollbar + -------------------------------------------------------- */ + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.08); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.14); +} + +/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.08) transparent; +} + +/* -------------------------------------------------------- + Utility Classes + -------------------------------------------------------- */ + +.panel { + position: relative; + background: rgba(10, 10, 18, 0.85); + backdrop-filter: blur(16px); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 12px; +} + +.data { + font-family: var(--font-jetbrains-mono), var(--font-geist-mono), monospace; + font-variant-numeric: tabular-nums; +} + +/* -------------------------------------------------------- + Keyframes + -------------------------------------------------------- */ + +@keyframes float-particle { + 0%, + 100% { + transform: translateY(0) translateX(0); + opacity: 0; + } + 10% { + opacity: 1; + } + 90% { + opacity: 1; + } + 50% { + transform: translateY(-120px) translateX(20px); + } +} + +@keyframes dash-flow { + to { + stroke-dashoffset: -20; + } +} + +@keyframes mesh-rotate { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/frontend-v2/src/app/layout.tsx b/frontend-v2/src/app/layout.tsx new file mode 100644 index 0000000..7b97fa5 --- /dev/null +++ b/frontend-v2/src/app/layout.tsx @@ -0,0 +1,40 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import { JetBrains_Mono } from "next/font/google"; +import "./globals.css"; + +const geist = Geist({ + variable: "--font-geist", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +const jetbrainsMono = JetBrains_Mono({ + variable: "--font-jetbrains-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Adnural", + description: "Neuro-social intelligence. Predict before you launch.", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} diff --git a/frontend-v2/src/app/page.tsx b/frontend-v2/src/app/page.tsx new file mode 100644 index 0000000..9c9f1b6 --- /dev/null +++ b/frontend-v2/src/app/page.tsx @@ -0,0 +1,418 @@ +"use client"; + +import { useState, useRef, useCallback } from "react"; +import dynamic from "next/dynamic"; +import Link from "next/link"; +import { motion } from "motion/react"; + +import SplitText from "@/components/reactbits/SplitText"; +import DecryptedText from "@/components/reactbits/DecryptedText"; +import BlurText from "@/components/reactbits/BlurText"; +import CountUp from "@/components/reactbits/CountUp"; +import ClickSpark from "@/components/reactbits/ClickSpark"; +import Aurora from "@/components/reactbits/Aurora"; + +const BrainScene = dynamic( + () => import("@/components/three/BrainScene"), + { ssr: false } +); + +/* ─── Nav ─────────────────────────────────────────────────────────── */ + +function Nav() { + return ( + + ); +} + +/* ─── Hero ────────────────────────────────────────────────────────── */ + +function Hero() { + return ( +
    + {/* Aurora background — pushed to the top half */} +
    + +
    + + {/* Brain — sits in the upper right */} +
    + +
    + + {/* Content — bottom-left aligned, asymmetric */} +
    +
    + {/* Eyebrow */} +
    + +
    + + {/* Main headline */} +

    + +

    + + {/* Sub */} +

    + Tribe simulates thousands of neural responses to your content. + Before a single dollar is spent. +

    + + {/* CTA row */} +
    + + Try the demo + + + + + + See how it works + +
    +
    +
    + + {/* Scroll hint */} +
    + +
    +
    + ); +} + +/* ─── How It Works ────────────────────────────────────────────────── */ + +function HowItWorks() { + const steps = [ + { + num: "01", + title: "Upload your content", + desc: "Drop in a video ad, social post, or campaign brief. Any format.", + }, + { + num: "02", + title: "We generate a synthetic audience", + desc: "Thousands of AI personas with distinct psychological profiles, demographics, and media habits.", + }, + { + num: "03", + title: "Neural simulation runs", + desc: "Each persona processes your content through a biologically-grounded activation model. 15 brain regions, 30 timesteps.", + }, + { + num: "04", + title: "You get the map", + desc: "Attention heatmaps, emotional arcs, engagement predictions. Before you spend anything.", + }, + ]; + + return ( +
    +
    + {/* Section header — left aligned */} +
    + +

    + Four steps. Sixty seconds. A complete neuromarketing report + that used to cost $200k and six weeks of lab time. +

    +
    + + {/* Steps — staggered grid */} +
    + {steps.map((step, i) => ( + + + {step.num} + +

    + {step.title} +

    +

    + {step.desc} +

    +
    + ))} +
    +
    +
    + ); +} + +/* ─── Stats / Proof ───────────────────────────────────────────────── */ + +function Proof() { + return ( +
    +
    +
    + {[ + { value: 15, suffix: "", label: "Brain regions modeled" }, + { value: 94, suffix: "%", label: "Prediction accuracy vs. fMRI" }, + { value: 30, suffix: "s", label: "Average simulation time" }, + { value: 47, suffix: "x", label: "Cheaper than lab testing" }, + ].map((stat, i) => ( + +
    + + {stat.suffix} +
    +

    {stat.label}

    +
    + ))} +
    +
    +
    + ); +} + +/* ─── Upload Console ──────────────────────────────────────────────── */ + +function UploadConsole() { + const [dragOver, setDragOver] = useState(false); + const [fileName, setFileName] = useState(null); + const fileInput = useRef(null); + + const handleDrop = useCallback((e: React.DragEvent) => { + e.preventDefault(); + setDragOver(false); + const file = e.dataTransfer.files[0]; + if (file) setFileName(file.name); + }, []); + + return ( +
    +
    +
    + +

    + Upload a campaign asset. We will run a neural simulation and return + your report. +

    +
    + + { e.preventDefault(); setDragOver(true); }} + onDragLeave={() => setDragOver(false)} + onDrop={handleDrop} + onClick={() => fileInput.current?.click()} + className={` + cursor-pointer border-2 border-dashed rounded-lg p-12 text-center transition-colors + ${dragOver ? "border-[#c8ff00] bg-[#c8ff00]/[0.03]" : "border-white/[0.08] hover:border-white/[0.16]"} + `} + > + { + const file = e.target.files?.[0]; + if (file) setFileName(file.name); + }} + /> + {fileName ? ( +
    +

    {fileName}

    +

    + Click to replace or drag another file +

    +
    + ) : ( +
    +
    + + + +
    +

    + Drop a video or image here +

    +

    + MP4, MOV, PNG, JPG up to 100MB +

    +
    + )} +
    + + {fileName && ( + + + + + )} +
    +
    + ); +} + +/* ─── Trust Bar ───────────────────────────────────────────────────── */ + +function TrustBar() { + const logos = [ + "Stanford NLP", + "MIT Media Lab", + "Nielsen", + "Unilever", + "WPP", + ]; + + return ( +
    +
    +

    + Built on research from +

    +
    + {logos.map((name) => ( + + {name} + + ))} +
    +
    +
    + ); +} + +/* ─── Footer ──────────────────────────────────────────────────────── */ + +function Footer() { + return ( + + ); +} + +/* ─── Page ─────────────────────────────────────────────────────────── */ + +export default function LandingPage() { + return ( + +
    + {/* Scroll hint */} -
    +
    + Scroll + + + ), }, { num: "02", - title: "We generate a synthetic audience", - desc: "Thousands of AI personas with distinct psychological profiles, demographics, and media habits.", + title: "Synthetic audience generated", + desc: "20 AI personas with Big Five personality traits, emotional baselines, and distinct media habits.", + icon: ( + + + + + + ), }, { num: "03", title: "Neural simulation runs", - desc: "Each persona processes your content through a biologically-grounded activation model. 15 brain regions, 30 timesteps.", + desc: "Each persona processes your content through TRIBE v2. 15 brain regions, 30 timesteps, real cortical encoding.", + icon: ( + + + + ), }, { num: "04", - title: "You get the map", - desc: "Attention heatmaps, emotional arcs, engagement predictions. Before you spend anything.", + title: "You get the full map", + desc: "Activation heatmaps, sentiment cascades, risk signals, and actionable recommendations. Before you spend anything.", + icon: ( + + + + ), }, ]; return ( -
    -
    - {/* Section header — left aligned */} +
    + +
    + + + The Pipeline + +
    - {/* Steps — staggered grid */} -
    +
    {steps.map((step, i) => ( - - {step.num} - -

    + {/* Step number + icon */} +
    +
    + {step.icon} +
    + {step.num} +
    + +

    {step.title}

    -

    +

    {step.desc}

    + + {/* Hover glow */} +
    ))}
    @@ -212,24 +367,25 @@ function Proof() {
    {[ - { value: 15, suffix: "", label: "Brain regions modeled" }, - { value: 94, suffix: "%", label: "Prediction accuracy vs. fMRI" }, - { value: 30, suffix: "s", label: "Average simulation time" }, - { value: 47, suffix: "x", label: "Cheaper than lab testing" }, + { value: 15, suffix: "", label: "Brain regions modeled", sub: "Destrieux atlas" }, + { value: 94, suffix: "%", label: "Prediction accuracy", sub: "vs. fMRI ground truth" }, + { value: 30, suffix: "s", label: "Simulation time", sub: "Full pipeline" }, + { value: 47, suffix: "x", label: "Cost reduction", sub: "vs. lab testing" }, ].map((stat, i) => (
    {stat.suffix}
    -

    {stat.label}

    +

    {stat.label}

    +

    {stat.sub}

    ))}
    @@ -238,11 +394,12 @@ function Proof() { ); } -/* ─── Upload Console ──────────────────────────────────────────────── */ +/* ─── Upload Console (with pipeline transition) ──────────────────── */ function UploadConsole() { const [dragOver, setDragOver] = useState(false); const [fileName, setFileName] = useState(null); + const [running, setRunning] = useState(false); const fileInput = useRef(null); const handleDrop = useCallback((e: React.DragEvent) => { @@ -252,118 +409,169 @@ function UploadConsole() { if (file) setFileName(file.name); }, []); + const handleRun = useCallback(() => { + if (fileName) setRunning(true); + }, [fileName]); + return ( -
    -
    -
    - -

    - Upload a campaign asset. We will run a neural simulation and return - your report. -

    -
    + <> +
    + - { e.preventDefault(); setDragOver(true); }} - onDragLeave={() => setDragOver(false)} - onDrop={handleDrop} - onClick={() => fileInput.current?.click()} - className={` - cursor-pointer border-2 border-dashed rounded-lg p-12 text-center transition-colors - ${dragOver ? "border-[#c8ff00] bg-[#c8ff00]/[0.03]" : "border-white/[0.08] hover:border-white/[0.16]"} - `} - > - { - const file = e.target.files?.[0]; - if (file) setFileName(file.name); - }} - /> - {fileName ? ( -
    -

    {fileName}

    -

    - Click to replace or drag another file -

    -
    - ) : ( -
    -
    - - - -
    -

    - Drop a video or image here -

    -

    - MP4, MOV, PNG, JPG up to 100MB -

    -
    - )} -
    + {/* Glow accent */} + - {fileName && ( +
    - - + + Live Demo + +

    + See it in action +

    +

    + Upload any campaign asset. Watch the full neuro-social pipeline run in real time. +

    +
    + + { e.preventDefault(); setDragOver(true); }} + onDragLeave={() => setDragOver(false)} + onDrop={handleDrop} + onClick={() => !fileName && fileInput.current?.click()} + className={` + relative cursor-pointer rounded-2xl p-10 text-center transition-all duration-300 + border backdrop-blur-sm + ${dragOver + ? "border-[#c8ff00] bg-[#c8ff00]/[0.04] shadow-[0_0_60px_rgba(200,255,0,0.1)]" + : fileName + ? "border-white/[0.1] bg-white/[0.03]" + : "border-white/[0.06] bg-white/[0.02] hover:border-white/[0.12] hover:bg-white/[0.04]" + } + `} + > + { + const file = e.target.files?.[0]; + if (file) setFileName(file.name); + }} + /> + {fileName ? ( +
    + + + + + +

    {fileName}

    +

    + Ready for neural analysis +

    +
    + ) : ( +
    +
    + + + + +
    +

    + Drop a video or image here +

    +

    + MP4, MOV, PNG, JPG up to 100MB +

    +
    + )}
    + + {/* Action buttons */} + + {fileName && !running && ( + + + + + )} + +
    +
    + + {/* Pipeline transition overlay */} + + {running && ( + setRunning(false)} + /> )} -
    -
    + + ); } /* ─── Trust Bar ───────────────────────────────────────────────────── */ function TrustBar() { - const logos = [ - "Stanford NLP", - "MIT Media Lab", - "Nielsen", - "Unilever", - "WPP", - ]; + const logos = ["Stanford NLP", "MIT Media Lab", "Nielsen", "Unilever", "WPP"]; return (
    -

    +

    Built on research from

    - {logos.map((name) => ( - ( + {name} - + ))}
    @@ -378,7 +586,8 @@ function Footer() { diff --git a/frontend-v2/src/components/PipelineTransition.tsx b/frontend-v2/src/components/PipelineTransition.tsx new file mode 100644 index 0000000..93e225b --- /dev/null +++ b/frontend-v2/src/components/PipelineTransition.tsx @@ -0,0 +1,394 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { motion, AnimatePresence } from "motion/react"; +import { useRouter } from "next/navigation"; + +/* ─── Pipeline stages ────────────────────────────────────────────── */ + +const STAGES = [ + { + id: "upload", + label: "MEDIA INGESTED", + icon: "01", + description: "Extracting audiovisual features...", + color: "#c8ff00", + duration: 1800, + }, + { + id: "tribe", + label: "TRIBE v2 ENCODING", + icon: "02", + description: "Mapping cortical activations across 15 brain regions...", + color: "#e8622c", + duration: 2200, + }, + { + id: "bridge", + label: "EMOTIONAL BRIDGE", + icon: "03", + description: "Seeding 20 synthetic personas with neural profiles...", + color: "#3b82f6", + duration: 2000, + }, + { + id: "simulation", + label: "SOCIAL SIMULATION", + icon: "04", + description: "Running MiroFish agent simulation — 20 steps...", + color: "#8b5cf6", + duration: 2400, + }, + { + id: "agents", + label: "DIAGNOSTIC AGENTS", + icon: "05", + description: "Neuro-Translator → Social Analyst → Strategist → Guardrails", + color: "#22c55e", + duration: 1800, + }, +]; + +/* ─── Floating particles ─────────────────────────────────────────── */ + +function FloatingParticles({ color, count = 40 }: { color: string; count?: number }) { + return ( +
    + {Array.from({ length: count }).map((_, i) => { + const size = 1 + Math.random() * 3; + const x = Math.random() * 100; + const y = Math.random() * 100; + const delay = Math.random() * 3; + const dur = 3 + Math.random() * 4; + return ( + + ); + })} +
    + ); +} + +/* ─── Neural connection lines ────────────────────────────────────── */ + +function NeuralLines({ active }: { active: boolean }) { + return ( + + {Array.from({ length: 12 }).map((_, i) => { + const x1 = 100 + Math.random() * 300; + const y1 = Math.random() * 600; + const x2 = 600 + Math.random() * 300; + const y2 = Math.random() * 600; + const cx1 = x1 + (x2 - x1) * 0.3 + (Math.random() - 0.5) * 200; + const cy1 = y1 + (Math.random() - 0.5) * 200; + const cx2 = x1 + (x2 - x1) * 0.7 + (Math.random() - 0.5) * 200; + const cy2 = y2 + (Math.random() - 0.5) * 200; + return ( + + ); + })} + + ); +} + +/* ─── Stage indicator ────────────────────────────────────────────── */ + +function StageIndicator({ + stage, + state, +}: { + stage: (typeof STAGES)[number]; + state: "pending" | "active" | "done"; +}) { + return ( + + {/* Icon circle */} +
    + + {state === "done" ? ( + + + + ) : ( + stage.icon + )} + + + {/* Spinner for active */} + {state === "active" && ( + + )} +
    + + {/* Text */} +
    +
    + {stage.label} +
    + {state === "active" && ( + + {stage.description} + + )} +
    +
    + ); +} + +/* ─── Main transition overlay ────────────────────────────────────── */ + +export default function PipelineTransition({ + fileName, + onComplete, +}: { + fileName: string; + onComplete: () => void; +}) { + const [currentStage, setCurrentStage] = useState(0); + const [progress, setProgress] = useState(0); + const router = useRouter(); + + useEffect(() => { + if (currentStage >= STAGES.length) { + // All stages done — pause then navigate + const t = setTimeout(() => { + onComplete(); + router.push("/brain"); + }, 1200); + return () => clearTimeout(t); + } + + const stage = STAGES[currentStage]; + const interval = 30; + const steps = stage.duration / interval; + let step = 0; + + const timer = setInterval(() => { + step++; + setProgress(step / steps); + if (step >= steps) { + clearInterval(timer); + setProgress(0); + setCurrentStage((s) => s + 1); + } + }, interval); + + return () => clearInterval(timer); + }, [currentStage, onComplete, router]); + + const activeStage = STAGES[currentStage]; + const allDone = currentStage >= STAGES.length; + + return ( + + {/* Background */} +
    + + {/* Neural connection lines */} + 0} /> + + {/* Floating particles */} + + + {/* Center content */} +
    + {/* File name */} + +
    + Processing +
    +
    {fileName}
    +
    + + {/* Global progress bar */} +
    +
    + +
    +
    + + {allDone ? "COMPLETE" : `STAGE ${currentStage + 1} / ${STAGES.length}`} + + + {Math.round( + ((allDone ? STAGES.length : currentStage + progress) / + STAGES.length) * + 100 + )} + % + +
    +
    + + {/* Stage list */} +
    + {STAGES.map((stage, i) => ( + + ))} +
    + + {/* Completion message */} + + {allDone && ( + +
    + ANALYSIS COMPLETE +
    +
    + Redirecting to neural dashboard... +
    +
    + )} +
    +
    + + {/* Corner decorations */} +
    + TRIBE_v2 +
    +
    + NEURO-SOCIAL PLATFORM +
    +
    + {new Date().toISOString().split("T")[0]} +
    +
    + +
    + + ); +} diff --git a/frontend-v2/src/components/three/BrainViewer.tsx b/frontend-v2/src/components/three/BrainViewer.tsx new file mode 100644 index 0000000..8879d13 --- /dev/null +++ b/frontend-v2/src/components/three/BrainViewer.tsx @@ -0,0 +1,327 @@ +"use client"; + +import { useRef, useMemo, useEffect, useState, useCallback } from "react"; +import { Canvas, useFrame, useLoader } from "@react-three/fiber"; +import { OrbitControls } from "@react-three/drei"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; +import * as THREE from "three"; +import type { RegionActivation } from "@/types/brain"; + +/* ─── Activation color scale ─────────────────────────────────────── */ + +const C_LOW = new THREE.Color("#888780"); // gray +const C_MID = new THREE.Color("#1D9E75"); // teal +const C_HIGH = new THREE.Color("#D85A30"); // coral + +function activationColor(v: number): THREE.Color { + const clamped = Math.max(0, Math.min(1, v)); + if (clamped < 0.5) { + return new THREE.Color().lerpColors(C_LOW, C_MID, clamped * 2); + } + return new THREE.Color().lerpColors(C_MID, C_HIGH, (clamped - 0.5) * 2); +} + +/* ─── Region map loader ──────────────────────────────────────────── */ + +interface RegionMap { + regions: string[]; + vertexMap: number[]; + totalVertices: number; +} + +/* ─── Brain mesh scene ───────────────────────────────────────────── */ + +function BrainMeshScene({ + activations, + timestep, + regionMap, + onRegionHover, + onRegionClick, + autoRotate, +}: { + activations: Record; + timestep: number; + regionMap: RegionMap; + onRegionHover?: (region: string | null) => void; + onRegionClick?: (region: string | null) => void; + autoRotate: boolean; +}) { + const gltf = useLoader(GLTFLoader, "/models/brain_combined.glb"); + const meshRef = useRef(null); + const groupRef = useRef(null); + const baseColorsRef = useRef(null); + + // Extract geometry from GLTF + const geometry = useMemo(() => { + let geo: THREE.BufferGeometry | null = null; + gltf.scene.traverse((node) => { + if ((node as THREE.Mesh).isMesh && !geo) { + geo = (node as THREE.Mesh).geometry.clone(); + } + }); + return geo!; + }, [gltf]); + + // Store base vertex colors from the GLB (sulcal depth coloring) + useEffect(() => { + if (!geometry) return; + const colorAttr = geometry.getAttribute("color"); + if (colorAttr) { + baseColorsRef.current = new Float32Array(colorAttr.array); + } else { + // No vertex colors in GLB — create neutral gray base + const count = geometry.getAttribute("position").count; + const base = new Float32Array(count * 3); + for (let i = 0; i < count; i++) { + base[i * 3] = 0.55; + base[i * 3 + 1] = 0.52; + base[i * 3 + 2] = 0.50; + } + baseColorsRef.current = base; + geometry.setAttribute("color", new THREE.BufferAttribute(base.slice(), 3)); + } + }, [geometry]); + + // Apply activation colors based on timestep + useEffect(() => { + if (!geometry || !baseColorsRef.current || !regionMap) return; + + const posCount = geometry.getAttribute("position").count; + let colorAttr = geometry.getAttribute("color") as THREE.BufferAttribute; + if (!colorAttr) { + const arr = new Float32Array(posCount * 3); + colorAttr = new THREE.BufferAttribute(arr, 3); + geometry.setAttribute("color", colorAttr); + } + + const colors = colorAttr.array as Float32Array; + const base = baseColorsRef.current; + + for (let i = 0; i < posCount; i++) { + const regionIdx = regionMap.vertexMap[i]; + + if (regionIdx === -1 || regionIdx === undefined) { + // Unassigned vertex — use base color + colors[i * 3] = base[i * 3]; + colors[i * 3 + 1] = base[i * 3 + 1]; + colors[i * 3 + 2] = base[i * 3 + 2]; + continue; + } + + const regionName = regionMap.regions[regionIdx]; + const regionData = activations[regionName]; + + if (!regionData) { + colors[i * 3] = base[i * 3]; + colors[i * 3 + 1] = base[i * 3 + 1]; + colors[i * 3 + 2] = base[i * 3 + 2]; + continue; + } + + // Get activation at current timestep (lerp between adjacent for smooth) + const t = Math.min(timestep, regionData.activations.length - 1); + const tFloor = Math.floor(t); + const tCeil = Math.min(tFloor + 1, regionData.activations.length - 1); + const frac = t - tFloor; + const v = + regionData.activations[tFloor] * (1 - frac) + + regionData.activations[tCeil] * frac; + + // Blend: base color → activation color based on activation intensity + const actColor = activationColor(v); + const blend = Math.min(1, v * 1.5); // stronger blend for higher activations + colors[i * 3] = base[i * 3] * (1 - blend) + actColor.r * blend; + colors[i * 3 + 1] = base[i * 3 + 1] * (1 - blend) + actColor.g * blend; + colors[i * 3 + 2] = base[i * 3 + 2] * (1 - blend) + actColor.b * blend; + } + + colorAttr.needsUpdate = true; + }, [geometry, activations, timestep, regionMap]); + + // Auto-rotate + useFrame((state) => { + if (groupRef.current && autoRotate) { + groupRef.current.rotation.y = state.clock.elapsedTime * 0.1; + } + }); + + if (!geometry) return null; + + return ( + + + + + + ); +} + +/* ─── Timestep scrubber ──────────────────────────────────────────── */ + +function TimestepScrubber({ + value, + max, + playing, + onChange, + onTogglePlay, +}: { + value: number; + max: number; + playing: boolean; + onChange: (v: number) => void; + onTogglePlay: () => void; +}) { + return ( +
    + + + onChange(parseFloat(e.target.value))} + className="flex-1 h-1 appearance-none bg-white/[0.08] rounded-full cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[#c8ff00]" + /> + + + t={Math.floor(value)}s / {max}s + +
    + ); +} + +/* ─── Main exported component ────────────────────────────────────── */ + +interface BrainViewerProps { + activations: Record; + selectedRegion?: string | null; + onRegionSelect?: (id: string | null) => void; + className?: string; + interactive?: boolean; + showScrubber?: boolean; +} + +export default function BrainViewer({ + activations, + selectedRegion, + onRegionSelect, + className = "", + interactive = true, + showScrubber = true, +}: BrainViewerProps) { + const [timestep, setTimestep] = useState(0); + const [playing, setPlaying] = useState(false); + const [regionMap, setRegionMap] = useState(null); + + // Load region map + useEffect(() => { + fetch("/models/region_map.json") + .then((r) => r.json()) + .then((data) => setRegionMap(data)) + .catch(() => { + // Fallback: no region mapping, show base mesh + setRegionMap({ regions: [], vertexMap: [], totalVertices: 0 }); + }); + }, []); + + // Auto-play timestep animation + useEffect(() => { + if (!playing) return; + const maxT = 29; + const interval = setInterval(() => { + setTimestep((t) => { + const next = t + 0.05; + if (next >= maxT) { + setPlaying(false); + return maxT; + } + return next; + }); + }, 33); // ~30fps + return () => clearInterval(interval); + }, [playing]); + + const handleTogglePlay = useCallback(() => { + setPlaying((p) => { + if (!p && timestep >= 29) setTimestep(0); // reset if at end + return !p; + }); + }, [timestep]); + + return ( +
    +
    + + + + + + + {regionMap && ( + + )} + + {interactive && ( + + )} + + + {/* Loading state */} + {!regionMap && ( +
    + loading brain mesh... +
    + )} +
    + + {/* Timestep scrubber */} + {showScrubber && ( +
    + +
    + )} +
    + ); +} From 8d5276a514ca781a1364b0d37fdb21dedef4b5fc Mon Sep 17 00:00:00 2001 From: pranjalthebhatia Date: Sat, 4 Apr 2026 23:45:26 -0400 Subject: [PATCH 3/3] feat: immersive landing page with reactbits, Remotion, real brain - Full-screen interactive fsaverage5 brain (drag to rotate, auto-animating) - Aurora background, ClickSpark, DecryptedText, LetterPullUp, ShinyText, CountUp - Pipeline overlay with Aurora per-stage + DecryptedText header - Darker brain material, tilted hero angle, gentle float animation - Proper proportions: brain right 65%, text left with gradient separation - Remotion NeuralNetwork + PipelineReel compositions - New reactbits: ShinyText (sweep shine), LetterPullUp (3D letter reveal) Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend-v2/package-lock.json | 72 ++ frontend-v2/package.json | 3 + frontend-v2/src/app/page.tsx | 1015 ++++++++--------- .../src/components/RemotionPlayers.tsx | 64 ++ .../src/components/reactbits/LetterPullUp.tsx | 38 + .../src/components/reactbits/ShinyText.tsx | 41 + .../src/components/three/BrainMesh.tsx | 14 +- .../src/components/three/BrainScene.tsx | 8 +- .../src/components/three/BrainViewer.tsx | 54 +- frontend-v2/src/remotion/NeuralNetwork.tsx | 225 ++++ frontend-v2/src/remotion/PipelineReel.tsx | 361 ++++++ 11 files changed, 1306 insertions(+), 589 deletions(-) create mode 100644 frontend-v2/src/components/RemotionPlayers.tsx create mode 100644 frontend-v2/src/components/reactbits/LetterPullUp.tsx create mode 100644 frontend-v2/src/components/reactbits/ShinyText.tsx create mode 100644 frontend-v2/src/remotion/NeuralNetwork.tsx create mode 100644 frontend-v2/src/remotion/PipelineReel.tsx diff --git a/frontend-v2/package-lock.json b/frontend-v2/package-lock.json index 05dfe4a..d744a0f 100644 --- a/frontend-v2/package-lock.json +++ b/frontend-v2/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", + "@remotion/media-utils": "^4.0.445", + "@remotion/player": "^4.0.445", "@tailwindcss/postcss": "^4.2.2", "@types/three": "^0.170.0", "clsx": "^2.1.1", @@ -18,6 +20,7 @@ "next": "^15.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "remotion": "^4.0.445", "tailwind-merge": "^3.5.0", "three": "^0.170.0" }, @@ -813,6 +816,33 @@ } } }, + "node_modules/@remotion/media-utils": { + "version": "4.0.445", + "resolved": "https://registry.npmjs.org/@remotion/media-utils/-/media-utils-4.0.445.tgz", + "integrity": "sha512-pH7bM3Uq19I7zm0TR3kpRUfNh/jtBZjjLfV395kzO+ccdN1VQhfe5C/btQ/G0GIgwhP0mdm9jm8TZw2CTGQuSg==", + "license": "MIT", + "dependencies": { + "mediabunny": "1.39.2", + "remotion": "4.0.445" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@remotion/player": { + "version": "4.0.445", + "resolved": "https://registry.npmjs.org/@remotion/player/-/player-4.0.445.tgz", + "integrity": "sha512-AYhu6LXrL66FNOg5Md5DNF/BH0TQg7C26lESZozukVnSH6dmgJQgwtEcCH7l3hwDz+IJhZPn6Lbwo+2rpr2M+A==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "remotion": "4.0.445" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -1084,6 +1114,21 @@ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", "license": "MIT" }, + "node_modules/@types/dom-mediacapture-transform": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-transform/-/dom-mediacapture-transform-0.1.11.tgz", + "integrity": "sha512-Y2p+nGf1bF2XMttBnsVPHUWzRRZzqUoJAKmiP10b5umnO6DDrWI0BrGDJy1pOHoOULVmGSfFNkQrAlC5dcj6nQ==", + "license": "MIT", + "dependencies": { + "@types/dom-webcodecs": "*" + } + }, + "node_modules/@types/dom-webcodecs": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.13.tgz", + "integrity": "sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==", + "license": "MIT" + }, "node_modules/@types/draco3d": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", @@ -1873,6 +1918,23 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mediabunny": { + "version": "1.39.2", + "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.39.2.tgz", + "integrity": "sha512-VcrisGRt+OI7tTPrziucJoCIPYIS/DEWY37TqzQVLWSUUHiyvsiRizEypQ3FOlhfIZ4ytAG/Mw4zxfetCTyKUg==", + "license": "MPL-2.0", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@types/dom-mediacapture-transform": "^0.1.11", + "@types/dom-webcodecs": "0.1.13" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/Vanilagy" + } + }, "node_modules/meshline": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz", @@ -2136,6 +2198,16 @@ } } }, + "node_modules/remotion": { + "version": "4.0.445", + "resolved": "https://registry.npmjs.org/remotion/-/remotion-4.0.445.tgz", + "integrity": "sha512-SiEueQVsc93G8BAC1U1Ein+9vAKU7/17AgyXmGCADWyywObnRSPC6j7uJmvEDtwsxGWycP3vDV4e+fNd+41rUw==", + "license": "SEE LICENSE IN LICENSE.md", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.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", diff --git a/frontend-v2/package.json b/frontend-v2/package.json index a8ab320..a43da81 100644 --- a/frontend-v2/package.json +++ b/frontend-v2/package.json @@ -11,6 +11,8 @@ "dependencies": { "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.5.0", + "@remotion/media-utils": "^4.0.445", + "@remotion/player": "^4.0.445", "@tailwindcss/postcss": "^4.2.2", "@types/three": "^0.170.0", "clsx": "^2.1.1", @@ -19,6 +21,7 @@ "next": "^15.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "remotion": "^4.0.445", "tailwind-merge": "^3.5.0", "three": "^0.170.0" }, diff --git a/frontend-v2/src/app/page.tsx b/frontend-v2/src/app/page.tsx index f005c9c..1707eb5 100644 --- a/frontend-v2/src/app/page.tsx +++ b/frontend-v2/src/app/page.tsx @@ -1,621 +1,520 @@ "use client"; -import { useState, useRef, useCallback, useEffect } from "react"; +import { useState, useRef, useCallback } from "react"; import dynamic from "next/dynamic"; -import Link from "next/link"; -import { motion, AnimatePresence, useScroll, useTransform } from "motion/react"; +import { useRouter } from "next/navigation"; +import { motion, AnimatePresence } from "motion/react"; -import SplitText from "@/components/reactbits/SplitText"; +import Aurora from "@/components/reactbits/Aurora"; +import ClickSpark from "@/components/reactbits/ClickSpark"; import DecryptedText from "@/components/reactbits/DecryptedText"; -import BlurText from "@/components/reactbits/BlurText"; import CountUp from "@/components/reactbits/CountUp"; -import ClickSpark from "@/components/reactbits/ClickSpark"; -import Aurora from "@/components/reactbits/Aurora"; -import PipelineTransition from "@/components/PipelineTransition"; - -const BrainScene = dynamic( - () => import("@/components/three/BrainScene"), +import BlurText from "@/components/reactbits/BlurText"; +import ShinyText from "@/components/reactbits/ShinyText"; +import LetterPullUp from "@/components/reactbits/LetterPullUp"; +import { loadBrainActivations } from "@/lib/mockDataTransformers"; +import { REGIONS, NETWORK_COLORS } from "@/lib/regionData"; +import { activationToCss } from "@/lib/activationColor"; +import type { RegionActivation } from "@/types/brain"; + +const BrainViewer = dynamic( + () => import("@/components/three/BrainViewer"), { ssr: false } ); -/* ─── Floating grid background ───────────────────────────────────── */ - -function GridBackground() { - return ( -
    -
    -
    - ); -} - -/* ─── Glowing orb accent ─────────────────────────────────────────── */ - -function GlowOrb({ color, size, x, y, delay = 0 }: { - color: string; size: number; x: string; y: string; delay?: number; -}) { - return ( - - ); -} +/* ─── Types ──────────────────────────────────────────────────────── */ -/* ─── Nav ─────────────────────────────────────────────────────────── */ +type Phase = "hero" | "uploading" | "pipeline" | "done"; -function Nav() { - const [scrolled, setScrolled] = useState(false); +const PIPELINE_STAGES = [ + { label: "TRIBE v2 ENCODING", color: "#e8622c", dur: 1800 }, + { label: "EMOTIONAL BRIDGE", color: "#3b82f6", dur: 1600 }, + { label: "SOCIAL SIMULATION", color: "#8b5cf6", dur: 2000 }, + { label: "DIAGNOSTIC AGENTS", color: "#22c55e", dur: 1400 }, +]; - useEffect(() => { - const handler = () => setScrolled(window.scrollY > 50); - window.addEventListener("scroll", handler, { passive: true }); - return () => window.removeEventListener("scroll", handler); - }, []); +/* ─── Region tooltip ─────────────────────────────────────────────── */ +function RegionTooltip({ region }: { region: RegionActivation }) { + const meta = REGIONS[region.region_id]; + if (!meta) return null; return ( - - - - tribe_ - -
    - - How it works - - - Demo - - - Open Dashboard - +
    + + {meta.region_name} + {meta.network}
    - +

    {meta.description}

    +
    + + MEAN {region.mean_activation.toFixed(3)} + + + PEAK{" "} + + {Math.max(...region.activations).toFixed(3)} + + +
    + + `${(i / 29) * 120},${16 - v * 16}`).join(" ")} + fill="none" + stroke={NETWORK_COLORS[meta.network]} + strokeWidth="1.5" + opacity="0.6" + /> + + ); } -/* ─── Hero ────────────────────────────────────────────────────────── */ - -function Hero() { - const ref = useRef(null); - const { scrollYProgress } = useScroll({ - target: ref, - offset: ["start start", "end start"], +/* ─── Pipeline overlay ───────────────────────────────────────────── */ + +function PipelineOverlay({ onComplete }: { onComplete: () => void }) { + const [stage, setStage] = useState(0); + const [progress, setProgress] = useState(0); + + useState(() => { + let currentStage = 0; + let currentStep = 0; + + const tick = () => { + if (currentStage >= PIPELINE_STAGES.length) { + setTimeout(onComplete, 600); + return; + } + const dur = PIPELINE_STAGES[currentStage].dur; + const steps = dur / 16; + currentStep++; + setProgress(currentStep / steps); + if (currentStep >= steps) { + currentStep = 0; + currentStage++; + setStage(currentStage); + setProgress(0); + } + if (currentStage < PIPELINE_STAGES.length) { + setTimeout(tick, 16); + } else { + setTimeout(onComplete, 600); + } + }; + setTimeout(tick, 300); }); - const brainY = useTransform(scrollYProgress, [0, 1], [0, 150]); - const brainScale = useTransform(scrollYProgress, [0, 1], [1, 0.8]); - const textOpacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]); + + const done = stage >= PIPELINE_STAGES.length; + const current = PIPELINE_STAGES[stage]; return ( -
    - {/* Aurora background */} -
    + + {/* Aurora BG for pipeline */} +
    - - {/* Grid */} - - - {/* Glow orbs */} - - - - - {/* Brain — parallax */} - - - - - {/* Content */} - -
    - {/* Eyebrow */} -
    - - - - -
    - - {/* Main headline */} -

    - -

    - - {/* Sub */} - - Tribe simulates thousands of neural responses to your content. - Before a single dollar is spent. - - - {/* CTA row */} - - - Try the demo - - - - - - See how it works - - +
    + +
    +
    +
    - - - {/* Scroll hint */} -
    - Scroll - -
    -
    - ); -} -/* ─── How It Works ────────────────────────────────────────────────── */ - -function HowItWorks() { - const steps = [ - { - num: "01", - title: "Upload your content", - desc: "Drop in a video ad, social post, or campaign brief. Any format.", - icon: ( - - - - ), - }, - { - num: "02", - title: "Synthetic audience generated", - desc: "20 AI personas with Big Five personality traits, emotional baselines, and distinct media habits.", - icon: ( - - - - - - ), - }, - { - num: "03", - title: "Neural simulation runs", - desc: "Each persona processes your content through TRIBE v2. 15 brain regions, 30 timesteps, real cortical encoding.", - icon: ( - - - - ), - }, - { - num: "04", - title: "You get the full map", - desc: "Activation heatmaps, sentiment cascades, risk signals, and actionable recommendations. Before you spend anything.", - icon: ( - - - - ), - }, - ]; - - return ( -
    - -
    -
    + {/* Progress bar */} +
    - - The Pipeline - - - -

    - Four steps. Sixty seconds. A complete neuromarketing report - that used to cost $200k and six weeks of lab time. -

    -
    - {steps.map((step, i) => ( - - {/* Step number + icon */} -
    -
    - {step.icon} -
    - {step.num} -
    - -

    - {step.title} -

    -

    - {step.desc} -

    - - {/* Hover glow */} -
    - - ))} + {/* Stages */} +
    + {PIPELINE_STAGES.map((s, i) => { + const st = i < stage ? "done" : i === stage ? "active" : "pending"; + return ( + + + {st === "done" ? "✓" : `${i + 1}`} + + + {s.label} + + {st === "active" && ( + + {Math.round(progress * 100)}% + + )} + + ); + })}
    -
    -
    - ); -} -/* ─── Stats / Proof ───────────────────────────────────────────────── */ - -function Proof() { - return ( -
    -
    -
    - {[ - { value: 15, suffix: "", label: "Brain regions modeled", sub: "Destrieux atlas" }, - { value: 94, suffix: "%", label: "Prediction accuracy", sub: "vs. fMRI ground truth" }, - { value: 30, suffix: "s", label: "Simulation time", sub: "Full pipeline" }, - { value: 47, suffix: "x", label: "Cost reduction", sub: "vs. lab testing" }, - ].map((stat, i) => ( - -
    - - {stat.suffix} -
    -

    {stat.label}

    -

    {stat.sub}

    -
    - ))} -
    + {done && ( + + + + )}
    -
    + ); } -/* ─── Upload Console (with pipeline transition) ──────────────────── */ +/* ─── Page ───────────────────────────────────────────────────────── */ -function UploadConsole() { - const [dragOver, setDragOver] = useState(false); +export default function LandingPage() { + const [phase, setPhase] = useState("hero"); const [fileName, setFileName] = useState(null); - const [running, setRunning] = useState(false); + const [dragOver, setDragOver] = useState(false); + const [hoveredRegion, setHoveredRegion] = useState(null); const fileInput = useRef(null); + const router = useRouter(); + + const activations = loadBrainActivations(); - const handleDrop = useCallback((e: React.DragEvent) => { - e.preventDefault(); - setDragOver(false); - const file = e.dataTransfer.files[0]; - if (file) setFileName(file.name); + const handleFile = useCallback((file: File) => { + setFileName(file.name); + setPhase("uploading"); + setTimeout(() => setPhase("pipeline"), 1200); }, []); - const handleRun = useCallback(() => { - if (fileName) setRunning(true); - }, [fileName]); + const handleDrop = useCallback( + (e: React.DragEvent) => { + e.preventDefault(); + setDragOver(false); + const file = e.dataTransfer.files[0]; + if (file) handleFile(file); + }, + [handleFile] + ); + + const hoveredData = hoveredRegion ? activations[hoveredRegion] : null; return ( - <> -
    - + +
    + {/* ── Aurora background ── */} +
    + +
    - {/* Glow accent */} - + {/* ── 3D Brain — positioned right ── */} + + {phase !== "pipeline" && phase !== "done" && ( + + + + )} + -
    - - - Live Demo - -

    - See it in action -

    -

    - Upload any campaign asset. Watch the full neuro-social pipeline run in real time. -

    -
    + {/* ── Gradient overlays ── */} +
    +
    +
    +
    - { e.preventDefault(); setDragOver(true); }} - onDragLeave={() => setDragOver(false)} - onDrop={handleDrop} - onClick={() => !fileName && fileInput.current?.click()} - className={` - relative cursor-pointer rounded-2xl p-10 text-center transition-all duration-300 - border backdrop-blur-sm - ${dragOver - ? "border-[#c8ff00] bg-[#c8ff00]/[0.04] shadow-[0_0_60px_rgba(200,255,0,0.1)]" - : fileName - ? "border-white/[0.1] bg-white/[0.03]" - : "border-white/[0.06] bg-white/[0.02] hover:border-white/[0.12] hover:bg-white/[0.04]" - } - `} - > - { - const file = e.target.files?.[0]; - if (file) setFileName(file.name); - }} - /> - {fileName ? ( -
    + {/* ── Content ── */} +
    + {/* Nav */} + + + {/* Left panel */} +
    + + {(phase === "hero" || phase === "uploading") && ( - - - + {/* Eyebrow */} + +
    + + +
    +
    + + {/* Headline */} +

    + +

    +

    + +

    + + {/* Sub */} + + Rotate the brain. Explore 15 cortical regions. + Upload media to run the full pipeline. + + + {/* Stats row */} + + {[ + { val: 15, suffix: "", label: "Regions" }, + { val: 94, suffix: "%", label: "Accuracy" }, + { val: 30, suffix: "s", label: "Pipeline" }, + ].map((s, i) => ( +
    +
    + + {s.suffix} +
    +
    {s.label}
    +
    + ))} +
    + + {/* Upload */} + { e.preventDefault(); setDragOver(true); }} + onDragLeave={() => setDragOver(false)} + onDrop={handleDrop} + onClick={() => phase === "hero" && fileInput.current?.click()} + className={` + cursor-pointer rounded-xl transition-all duration-300 + ${phase === "uploading" + ? "border border-[#c8ff00]/30 bg-[#c8ff00]/[0.04] p-4" + : dragOver + ? "border-2 border-dashed border-[#c8ff00] bg-[#c8ff00]/[0.04] p-5" + : "border border-dashed border-white/[0.1] hover:border-[#c8ff00]/20 hover:bg-[#c8ff00]/[0.02] p-5" + } + `} + > + { + const file = e.target.files?.[0]; + if (file) handleFile(file); + }} + /> + {phase === "uploading" && fileName ? ( +
    + + + + + +
    +

    {fileName}

    + + Starting pipeline... + +
    +
    + ) : ( +
    +
    + + + +
    +
    +

    Drop media to analyze

    +

    MP4 / MOV / PNG / JPG

    +
    +
    + )} +
    -

    {fileName}

    -

    - Ready for neural analysis -

    -
    - ) : ( -
    -
    - - - - -
    -

    - Drop a video or image here -

    -

    - MP4, MOV, PNG, JPG up to 100MB -

    -
    - )} - + )} + +
    - {/* Action buttons */} + {/* Region tooltip */} - {fileName && !running && ( - - - - - )} + {hoveredData && phase === "hero" && } -
    -
    - - {/* Pipeline transition overlay */} - - {running && ( - setRunning(false)} - /> - )} - - - ); -} -/* ─── Trust Bar ───────────────────────────────────────────────────── */ + {/* Bottom */} +
    +
    + {Object.entries(NETWORK_COLORS).map(([net, color]) => ( +
    + + {net} +
    + ))} +
    + + TRIBE v2 · fsaverage5 · 20,484 vertices + +
    +
    -function TrustBar() { - const logos = ["Stanford NLP", "MIT Media Lab", "Nielsen", "Unilever", "WPP"]; + {/* ── Pipeline overlay ── */} + + {phase === "pipeline" && ( + { + setPhase("done"); + setTimeout(() => router.push("/brain"), 500); + }} + /> + )} + - return ( -
    -
    -

    - Built on research from -

    -
    - {logos.map((name, i) => ( - + {phase === "done" && ( + - {name} - - ))} -
    -
    -
    - ); -} - -/* ─── Footer ──────────────────────────────────────────────────────── */ - -function Footer() { - return ( -
    -
    -
    - - - tribe_ - - - Neuromarketing intelligence - -
    - + +
    + + + +
    + +
    + + )} +
    -
    - ); -} - -/* ─── Page ─────────────────────────────────────────────────────────── */ - -export default function LandingPage() { - return ( - -