diff --git a/package.json b/package.json index 933df6b..cbad907 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,8 @@ "preview": "vite preview" }, "dependencies": { + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.5.0", "@vercel/analytics": "^1.6.1", "autoprefixer": "^10.4.23", "class-variance-authority": "^0.7.1", @@ -30,7 +32,8 @@ "recharts": "^3.7.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", - "tailwindcss": "^3.4.1" + "tailwindcss": "^3.4.1", + "three": "^0.182.0" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd4e224..60060a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@react-three/drei': + specifier: ^10.7.7 + version: 10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.10)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.182.0))(@types/react@19.2.10)(@types/three@0.182.0)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.182.0) + '@react-three/fiber': + specifier: ^9.5.0 + version: 9.5.0(@types/react@19.2.10)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.182.0) '@vercel/analytics': specifier: ^1.6.1 version: 1.6.1(react@19.2.4) @@ -68,6 +74,9 @@ importers: tailwindcss: specifier: ^3.4.1 version: 3.4.19 + three: + specifier: ^0.182.0 + version: 0.182.0 devDependencies: '@eslint/js': specifier: ^9.39.1 @@ -174,6 +183,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -186,6 +199,9 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@dimforge/rapier3d-compat@0.12.0': + resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} + '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} @@ -412,6 +428,14 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mediapipe/tasks-vision@0.10.17': + resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} + + '@monogrid/gainmap-js@3.4.0': + resolution: {integrity: sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==} + peerDependencies: + three: '>= 0.159.0' + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -431,6 +455,42 @@ packages: react: ^19.0.0 react-dom: ^19.0.0 + '@react-three/drei@10.7.7': + resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==} + peerDependencies: + '@react-three/fiber': ^9.0.0 + react: ^19 + react-dom: ^19 + three: '>=0.159' + peerDependenciesMeta: + react-dom: + optional: true + + '@react-three/fiber@9.5.0': + resolution: {integrity: sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==} + 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 + '@reduxjs/toolkit@2.11.2': resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} peerDependencies: @@ -576,6 +636,9 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@tweenjs/tween.js@23.1.3': + resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -618,6 +681,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/draco3d@1.4.10': + resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -642,14 +708,28 @@ packages: '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/offscreencanvas@2019.7.3': + resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 + '@types/react-reconciler@0.28.9': + resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} + peerDependencies: + '@types/react': '*' + '@types/react@19.2.10': resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==} + '@types/stats.js@0.17.4': + resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} + + '@types/three@0.182.0': + resolution: {integrity: sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -659,9 +739,20 @@ packages: '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/webxr@0.5.24': + resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@use-gesture/core@10.3.1': + resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} + + '@use-gesture/react@10.3.1': + resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==} + peerDependencies: + react: '>= 16.8.0' + '@vercel/analytics@1.6.1': resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} peerDependencies: @@ -694,6 +785,9 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@webgpu/types@0.1.69': + resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -748,10 +842,16 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.9.19: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -768,6 +868,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -780,6 +883,12 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camera-controls@3.1.2: + resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==} + engines: {node: '>=22.0.0', npm: '>=10.5.1'} + peerDependencies: + three: '>=0.126.1' + caniuse-lite@1.0.30001766: resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} @@ -841,6 +950,11 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -923,6 +1037,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + detect-gpu@5.0.70: + resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -932,6 +1049,9 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + draco3d@1.5.7: + resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1062,6 +1182,12 @@ packages: picomatch: optional: true + fflate@0.6.10: + resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==} + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1145,6 +1271,9 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} + glsl-noise@0.0.0: + resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1183,16 +1312,25 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hls.js@1.6.15: + resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immer@10.2.0: resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} @@ -1250,9 +1388,17 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + its-fine@2.0.0: + resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==} + peerDependencies: + react: ^19.0.0 + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -1307,6 +1453,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1332,6 +1481,12 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + maath@0.10.8: + resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==} + peerDependencies: + '@types/three': '>=0.134.0' + three: '>=0.134.0' + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1364,6 +1519,14 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + meshline@3.3.1: + resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==} + peerDependencies: + three: '>=0.137' + + meshoptimizer@0.22.0: + resolution: {integrity: sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -1587,10 +1750,16 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + potpack@1.0.2: + resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + promise-worker-transferable@1.0.4: + resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} + property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} @@ -1655,6 +1824,15 @@ packages: react-dom: optional: true + react-use-measure@2.1.7: + resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} + peerDependencies: + react: '>=16.13' + react-dom: '>=16.13' + peerDependenciesMeta: + react-dom: + optional: true + react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -1688,6 +1866,10 @@ packages: remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -1743,6 +1925,15 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + stats-gl@2.4.2: + resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==} + peerDependencies: + '@types/three': '*' + three: '*' + + stats.js@0.17.0: + resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -1769,6 +1960,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + suspend-react@0.1.3: + resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==} + peerDependencies: + react: '>=17.0' + tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} @@ -1784,6 +1980,19 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + three-mesh-bvh@0.8.3: + resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==} + peerDependencies: + three: '>= 0.159.0' + + three-stdlib@2.36.1: + resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==} + peerDependencies: + three: '>=0.128.0' + + three@0.182.0: + resolution: {integrity: sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==} + tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -1801,6 +2010,19 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + troika-three-text@0.52.4: + resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==} + peerDependencies: + three: '>=0.125.0' + + troika-three-utils@0.52.4: + resolution: {integrity: sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==} + peerDependencies: + three: '>=0.125.0' + + troika-worker-utils@0.52.0: + resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==} + trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -1810,6 +2032,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-rat@0.1.2: + resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1852,6 +2077,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -1905,6 +2134,12 @@ packages: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} + webgl-constants@1.1.1: + resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==} + + webgl-sdf-generator@1.1.1: + resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -1936,6 +2171,39 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + 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 + + zustand@5.0.11: + resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + 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 + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -2032,6 +2300,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.28.6': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -2055,6 +2325,8 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@dimforge/rapier3d-compat@0.12.0': {} + '@esbuild/aix-ppc64@0.27.2': optional: true @@ -2209,6 +2481,13 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mediapipe/tasks-vision@0.10.17': {} + + '@monogrid/gainmap-js@3.4.0(three@0.182.0)': + dependencies: + promise-worker-transferable: 1.0.4 + three: 0.182.0 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2227,6 +2506,59 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + '@react-three/drei@10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.10)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.182.0))(@types/react@19.2.10)(@types/three@0.182.0)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.182.0)': + dependencies: + '@babel/runtime': 7.28.6 + '@mediapipe/tasks-vision': 0.10.17 + '@monogrid/gainmap-js': 3.4.0(three@0.182.0) + '@react-three/fiber': 9.5.0(@types/react@19.2.10)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.182.0) + '@use-gesture/react': 10.3.1(react@19.2.4) + camera-controls: 3.1.2(three@0.182.0) + cross-env: 7.0.3 + detect-gpu: 5.0.70 + glsl-noise: 0.0.0 + hls.js: 1.6.15 + maath: 0.10.8(@types/three@0.182.0)(three@0.182.0) + meshline: 3.3.1(three@0.182.0) + react: 19.2.4 + stats-gl: 2.4.2(@types/three@0.182.0)(three@0.182.0) + stats.js: 0.17.0 + suspend-react: 0.1.3(react@19.2.4) + three: 0.182.0 + three-mesh-bvh: 0.8.3(three@0.182.0) + three-stdlib: 2.36.1(three@0.182.0) + troika-three-text: 0.52.4(three@0.182.0) + tunnel-rat: 0.1.2(@types/react@19.2.10)(immer@11.1.3)(react@19.2.4) + use-sync-external-store: 1.6.0(react@19.2.4) + utility-types: 3.11.0 + zustand: 5.0.11(@types/react@19.2.10)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - '@types/three' + - immer + + '@react-three/fiber@9.5.0(@types/react@19.2.10)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.182.0)': + dependencies: + '@babel/runtime': 7.28.6 + '@types/webxr': 0.5.24 + base64-js: 1.5.1 + buffer: 6.0.3 + its-fine: 2.0.0(@types/react@19.2.10)(react@19.2.4) + react: 19.2.4 + react-use-measure: 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + scheduler: 0.27.0 + suspend-react: 0.1.3(react@19.2.4) + three: 0.182.0 + use-sync-external-store: 1.6.0(react@19.2.4) + zustand: 5.0.11(@types/react@19.2.10)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.10)(react@19.2.4)(redux@5.0.1))(react@19.2.4)': dependencies: '@standard-schema/spec': 1.1.0 @@ -2320,6 +2652,8 @@ snapshots: '@standard-schema/utils@0.3.0': {} + '@tweenjs/tween.js@23.1.3': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.0 @@ -2369,6 +2703,8 @@ snapshots: dependencies: '@types/ms': 2.1.0 + '@types/draco3d@1.4.10': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 @@ -2396,22 +2732,49 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/offscreencanvas@2019.7.3': {} + '@types/react-dom@19.2.3(@types/react@19.2.10)': dependencies: '@types/react': 19.2.10 + '@types/react-reconciler@0.28.9(@types/react@19.2.10)': + dependencies: + '@types/react': 19.2.10 + '@types/react@19.2.10': dependencies: csstype: 3.2.3 + '@types/stats.js@0.17.4': {} + + '@types/three@0.182.0': + dependencies: + '@dimforge/rapier3d-compat': 0.12.0 + '@tweenjs/tween.js': 23.1.3 + '@types/stats.js': 0.17.4 + '@types/webxr': 0.5.24 + '@webgpu/types': 0.1.69 + fflate: 0.8.2 + meshoptimizer: 0.22.0 + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} '@types/use-sync-external-store@0.0.6': {} + '@types/webxr@0.5.24': {} + '@ungap/structured-clone@1.3.0': {} + '@use-gesture/core@10.3.1': {} + + '@use-gesture/react@10.3.1(react@19.2.4)': + dependencies: + '@use-gesture/core': 10.3.1 + react: 19.2.4 + '@vercel/analytics@1.6.1(react@19.2.4)': optionalDependencies: react: 19.2.4 @@ -2428,6 +2791,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@webgpu/types@0.1.69': {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -2479,8 +2844,14 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + baseline-browser-mapping@2.9.19: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + binary-extensions@2.3.0: {} brace-expansion@1.1.12: @@ -2500,6 +2871,11 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -2509,6 +2885,10 @@ snapshots: camelcase-css@2.0.1: {} + camera-controls@3.1.2(three@0.182.0): + dependencies: + three: 0.182.0 + caniuse-lite@1.0.30001766: {} ccount@2.0.1: {} @@ -2564,6 +2944,10 @@ snapshots: cookie@1.1.1: {} + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2628,6 +3012,10 @@ snapshots: dequal@2.0.3: {} + detect-gpu@5.0.70: + dependencies: + webgl-constants: 1.1.1 + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -2636,6 +3024,8 @@ snapshots: dlv@1.1.3: {} + draco3d@1.5.7: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2807,6 +3197,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.6.10: {} + + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -2890,6 +3284,8 @@ snapshots: globals@16.5.0: {} + glsl-noise@0.0.0: {} + gopd@1.2.0: {} groq-sdk@0.37.0: @@ -2948,14 +3344,20 @@ snapshots: dependencies: hermes-estree: 0.25.1 + hls.js@1.6.15: {} + html-url-attributes@3.0.1: {} humanize-ms@1.2.1: dependencies: ms: 2.1.3 + ieee754@1.2.1: {} + ignore@5.3.2: {} + immediate@3.0.6: {} + immer@10.2.0: {} immer@11.1.3: {} @@ -3000,8 +3402,17 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@2.2.2: {} + isexe@2.0.0: {} + its-fine@2.0.0(@types/react@19.2.10)(react@19.2.4): + dependencies: + '@types/react-reconciler': 0.28.9(@types/react@19.2.10) + react: 19.2.4 + transitivePeerDependencies: + - '@types/react' + jiti@1.21.7: {} js-tokens@4.0.0: {} @@ -3035,6 +3446,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -3055,6 +3470,11 @@ snapshots: dependencies: react: 19.2.4 + maath@0.10.8(@types/three@0.182.0)(three@0.182.0): + dependencies: + '@types/three': 0.182.0 + three: 0.182.0 + math-intrinsics@1.1.0: {} mdast-util-from-markdown@2.0.2: @@ -3148,6 +3568,12 @@ snapshots: merge2@1.4.1: {} + meshline@3.3.1(three@0.182.0): + dependencies: + three: 0.182.0 + + meshoptimizer@0.22.0: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -3412,8 +3838,15 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + potpack@1.0.2: {} + prelude-ls@1.2.1: {} + promise-worker-transferable@1.0.4: + dependencies: + is-promise: 2.2.2 + lie: 3.3.0 + property-information@7.1.0: {} punycode@2.3.1: {} @@ -3477,6 +3910,12 @@ snapshots: optionalDependencies: react-dom: 19.2.4(react@19.2.4) + react-use-measure@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + optionalDependencies: + react-dom: 19.2.4(react@19.2.4) + react@19.2.4: {} read-cache@1.0.0: @@ -3530,6 +3969,8 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + require-from-string@2.0.2: {} + reselect@5.1.1: {} resolve-from@4.0.0: {} @@ -3598,6 +4039,13 @@ snapshots: space-separated-tokens@2.0.2: {} + stats-gl@2.4.2(@types/three@0.182.0)(three@0.182.0): + dependencies: + '@types/three': 0.182.0 + three: 0.182.0 + + stats.js@0.17.0: {} + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -3629,6 +4077,10 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + suspend-react@0.1.3(react@19.2.4): + dependencies: + react: 19.2.4 + tailwind-merge@3.4.0: {} tailwindcss@3.4.19: @@ -3667,6 +4119,22 @@ snapshots: dependencies: any-promise: 1.3.0 + three-mesh-bvh@0.8.3(three@0.182.0): + dependencies: + three: 0.182.0 + + three-stdlib@2.36.1(three@0.182.0): + dependencies: + '@types/draco3d': 1.4.10 + '@types/offscreencanvas': 2019.7.3 + '@types/webxr': 0.5.24 + draco3d: 1.5.7 + fflate: 0.6.10 + potpack: 1.0.2 + three: 0.182.0 + + three@0.182.0: {} + tiny-invariant@1.3.3: {} tinyglobby@0.2.15: @@ -3682,12 +4150,34 @@ snapshots: trim-lines@3.0.1: {} + troika-three-text@0.52.4(three@0.182.0): + dependencies: + bidi-js: 1.0.3 + three: 0.182.0 + troika-three-utils: 0.52.4(three@0.182.0) + troika-worker-utils: 0.52.0 + webgl-sdf-generator: 1.1.1 + + troika-three-utils@0.52.4(three@0.182.0): + dependencies: + three: 0.182.0 + + troika-worker-utils@0.52.0: {} + trough@2.2.0: {} ts-interface-checker@0.1.13: {} tslib@2.8.1: {} + tunnel-rat@0.1.2(@types/react@19.2.10)(immer@11.1.3)(react@19.2.4): + dependencies: + zustand: 4.5.7(@types/react@19.2.10)(immer@11.1.3)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + - react + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -3743,6 +4233,8 @@ snapshots: util-deprecate@1.0.2: {} + utility-types@3.11.0: {} + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -3784,6 +4276,10 @@ snapshots: web-streams-polyfill@4.0.0-beta.3: {} + webgl-constants@1.1.1: {} + + webgl-sdf-generator@1.1.1: {} + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -3807,4 +4303,19 @@ snapshots: zod@4.3.6: {} + zustand@4.5.7(@types/react@19.2.10)(immer@11.1.3)(react@19.2.4): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.10 + immer: 11.1.3 + react: 19.2.4 + + zustand@5.0.11(@types/react@19.2.10)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): + optionalDependencies: + '@types/react': 19.2.10 + immer: 11.1.3 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + zwitch@2.0.4: {} diff --git a/src/assets/images/hero_aerial_scan.png b/src/assets/images/hero_aerial_scan.png new file mode 100644 index 0000000..7226fb3 Binary files /dev/null and b/src/assets/images/hero_aerial_scan.png differ diff --git a/src/assets/images/intel_map_dfw_twilight.png b/src/assets/images/intel_map_dfw_twilight.png new file mode 100644 index 0000000..19706a3 Binary files /dev/null and b/src/assets/images/intel_map_dfw_twilight.png differ diff --git a/src/assets/images/new_images/Cool_Intelligence_Section.png b/src/assets/images/new_images/Cool_Intelligence_Section.png new file mode 100644 index 0000000..19706a3 Binary files /dev/null and b/src/assets/images/new_images/Cool_Intelligence_Section.png differ diff --git a/src/assets/images/new_images/Hero_Section.png b/src/assets/images/new_images/Hero_Section.png new file mode 100644 index 0000000..7226fb3 Binary files /dev/null and b/src/assets/images/new_images/Hero_Section.png differ diff --git a/src/assets/images/new_images/Warm_Intelligence_Section.png b/src/assets/images/new_images/Warm_Intelligence_Section.png new file mode 100644 index 0000000..3010bf9 Binary files /dev/null and b/src/assets/images/new_images/Warm_Intelligence_Section.png differ diff --git a/src/components/IntelligenceMap.jsx b/src/components/IntelligenceMap.jsx index 1168ece..5d28440 100644 --- a/src/components/IntelligenceMap.jsx +++ b/src/components/IntelligenceMap.jsx @@ -1,197 +1,453 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; +import ReactDOM from 'react-dom'; import { useNavigate } from 'react-router-dom'; -import { Icons } from './landing/constants'; -import MapBg from '../assets/images/hyper_intelligence_bg_image.jpg'; +import MapBg from '../assets/images/intel_map_dfw_twilight.png'; import IntelligenceSidePanel from './dashboard/IntelligenceSidePanel'; +import gsap from 'gsap'; +import { ScrollTrigger } from 'gsap/ScrollTrigger'; -// Updated with Residential & Commercial mix +gsap.registerPlugin(ScrollTrigger); + +// ── 10 Hotspots — 5 Commercial + 5 Residential, DFW Metro ── const hotspots = [ + // Commercial { - id: 1, - top: '45%', - left: '65%', - address: 'Preston Royal Village', + id: 1, top: '22%', left: '58%', + address: '8333 Douglas Ave, Dallas', type: 'Commercial', problem: 'Roof Membrane Uplift', - gain: '+$8.5M Value', + gain: '+$8.5M', weather: 'Hail Expected', status: 'Urgent', - specs: { built: '1998', maintenance: '5 years since last coating' }, + specs: { built: '1998', maintenance: '5 yrs since last coating' }, management: { name: 'Sterling Properties', contact: 'Sign in for contact details' }, photos: [ - { id: 1, url: '/assets/images/properties/Warehouse_Roof_Aerial.jpg', caption: 'Warehouse Roof Aerial' }, - { id: 2, url: '/assets/images/properties/Office_Roof_Top_View.jpg', caption: 'Office Roof Top' } + { id: 'c1-1', url: '/assets/images/properties/Apartment_Complex.jpg', caption: 'Exterior View' }, + { id: 'c1-2', url: '/assets/images/properties/Office_Roof_Top_View.jpg', caption: 'Roof Aerial' }, + { id: 'c1-3', url: '/assets/images/properties/Broken_Roof_Surface.jpg', caption: 'Membrane Damage' }, ] }, { - id: 2, - top: '28%', - left: '42%', - address: 'Highland Park Place', + id: 2, top: '35%', left: '38%', + address: '5600 Tennyson Pkwy, Plano', type: 'Commercial', problem: 'Critical Membrane Failure', - gain: '$22.5 Million', + gain: '+$22.5M', weather: 'Clear', status: 'Monitoring', - specs: { built: '1982', maintenance: '20 years since last roof replacement permit' }, + specs: { built: '1982', maintenance: '20 yrs since roof replacement' }, management: { name: 'Goldman Group', contact: 'Sign in for contact details' }, photos: [ - { id: 3, url: '/assets/images/properties/Mall_Rooftop_HVAC_Units.jpg', caption: 'Rooftop HVAC Units' } + { id: 'c2-1', url: '/assets/images/properties/Warehouse_Roof_Aerial.jpg', caption: 'Aerial Survey' }, + { id: 'c2-2', url: '/assets/images/properties/Mall_Rooftop_HVAC_Units.jpg', caption: 'HVAC Systems' }, + { id: 'c2-3', url: '/assets/images/properties/Storm_Worn_Roof.jpg', caption: 'Surface Wear' }, + { id: 'c2-4', url: '/assets/images/properties/Cracked_Asphalt_Shingles.jpg', caption: 'Substrate Cracking' }, ] }, { - id: 3, - top: '55%', - left: '25%', - address: '4200 Beverly Dr', + id: 3, top: '48%', left: '72%', + address: '1900 Preston Rd, Frisco', + type: 'Commercial', + problem: 'Ponding Water Accumulation', + gain: '+$4.2M', + weather: 'Storm Watch', + status: 'Action Required', + specs: { built: '2005', maintenance: '3 yrs since drainage check' }, + management: { name: 'Hillcrest Capital', contact: 'Sign in for contact details' }, + photos: [ + { id: 'c3-1', url: '/assets/images/properties/Apartment_Complex.jpg', caption: 'Building Exterior' }, + { id: 'c3-2', url: '/assets/images/properties/Mall_Rooftop_HVAC_Units.jpg', caption: 'Rooftop Overview' }, + { id: 'c3-3', url: '/assets/images/properties/Broken_Roof_Surface.jpg', caption: 'Ponding Damage' }, + ] + }, + { + id: 4, top: '62%', left: '28%', + address: '2100 N Hwy 360, Grand Prairie', + type: 'Commercial', + problem: 'HVAC Penetration Leak', + gain: '+$6.8M', + weather: 'Windy', + status: 'Monitoring', + specs: { built: '1995', maintenance: '8 yrs since waterproofing' }, + management: { name: 'Apex Industrial Partners', contact: 'Sign in for contact details' }, + photos: [ + { id: 'c4-1', url: '/assets/images/properties/Warehouse_Roof_Aerial.jpg', caption: 'Facility Aerial' }, + { id: 'c4-2', url: '/assets/images/properties/Mall_Rooftop_HVAC_Units.jpg', caption: 'HVAC Penetration' }, + { id: 'c4-3', url: '/assets/images/properties/Cracked_Storm_Shingles.jpg', caption: 'Seal Failure' }, + ] + }, + { + id: 5, top: '15%', left: '20%', + address: '401 Commerce St, Fort Worth', + type: 'Commercial', + problem: 'Flashing Deterioration', + gain: '+$11.3M', + weather: 'Hail Expected', + status: 'Urgent', + specs: { built: '1990', maintenance: '12 yrs since seal replacement' }, + management: { name: 'Tarrant Realty Holdings', contact: 'Sign in for contact details' }, + photos: [ + { id: 'c5-1', url: '/assets/images/properties/Office_Roof_Top_View.jpg', caption: 'Roof Survey' }, + { id: 'c5-2', url: '/assets/images/properties/Apartment_Complex.jpg', caption: 'Building Facade' }, + { id: 'c5-3', url: '/assets/images/properties/Curled_Aging_Shingles.jpg', caption: 'Flashing Wear' }, + { id: 'c5-4', url: '/assets/images/properties/Storm_Worn_Roof.jpg', caption: 'Wind Exposure' }, + ] + }, + // Residential + { + id: 6, top: '55%', left: '50%', + address: '4200 Beverly Dr, Highland Park', type: 'Residential', problem: 'Severe Hail Impact', - gain: '$1.1M', + gain: '+$1.1M', weather: 'Rain Soon', status: 'Action Required', - specs: { roofSize: '62 sq Roof', eventHistory: '8 years since last known hail event' }, + specs: { roofSize: '62 sq', eventHistory: '8 yrs since last hail event' }, owner: { fico: '715', profile: 'Early Adopter / Long-term value' }, photos: [ - { id: 4, url: '/assets/images/properties/Hail_Damaged_Shingles.jpg', caption: 'Hail Damage Close-up' }, - { id: 5, url: '/assets/images/properties/Broken_Roof_Surface.jpg', caption: 'Broken Surface' } + { id: 'r6-1', url: '/assets/images/properties/Luxury_Driveway_Residence.jpg', caption: 'Front Exterior' }, + { id: 'r6-2', url: '/assets/images/properties/Hail_Damaged_Shingles.jpg', caption: 'Hail Damage' }, + { id: 'r6-3', url: '/assets/images/properties/Cracked_Storm_Shingles.jpg', caption: 'Storm Damage' }, ] }, { - id: 4, - top: '70%', - left: '48%', - address: '3505 Lexington Ave', + id: 7, top: '72%', left: '65%', + address: '3505 Lexington Ave, University Park', type: 'Residential', problem: 'Asphalt Shingle Loss', - gain: '+$950k Home', + gain: '+$950K', weather: 'Windy', status: 'Low Priority', - specs: { roofSize: '45 sq Roof', eventHistory: 'High Wind Zone' }, + specs: { roofSize: '45 sq', eventHistory: 'High Wind Zone' }, owner: { fico: '680', profile: 'First-time Buyer' }, photos: [ - { id: 6, url: '/assets/images/properties/Cracked_Asphalt_Shingles.jpg', caption: 'Cracked Shingles' }, - { id: 7, url: '/assets/images/properties/Curled_Aging_Shingles.jpg', caption: 'Aging Shingles' } + { id: 'r7-1', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', caption: 'Front View' }, + { id: 'r7-2', url: '/assets/images/properties/Cracked_Asphalt_Shingles.jpg', caption: 'Shingle Loss' }, + { id: 'r7-3', url: '/assets/images/properties/Roof_Inspection_Angle.jpg', caption: 'Drone Inspection' }, + ] + }, + { + id: 8, top: '40%', left: '15%', + address: '7212 Covewood Dr, Garland', + type: 'Residential', + problem: 'Aging Composite Shingles', + gain: '+$420K', + weather: 'Clear', + status: 'Low Priority', + specs: { roofSize: '38 sq', eventHistory: 'No recent events' }, + owner: { fico: '740', profile: 'Long-term Owner' }, + photos: [ + { id: 'r8-1', url: '/assets/images/properties/Beige_Two_Story_House.jpg', caption: 'Street View' }, + { id: 'r8-2', url: '/assets/images/properties/Curled_Aging_Shingles.jpg', caption: 'Aging Shingles' }, + ] + }, + { + id: 9, top: '75%', left: '42%', + address: '1520 Creekside Dr, Allen', + type: 'Residential', + problem: 'Ridge Cap Separation', + gain: '+$780K', + weather: 'Hail Expected', + status: 'Urgent', + specs: { roofSize: '55 sq', eventHistory: '2 hail events in 3 years' }, + owner: { fico: '695', profile: 'Insurance-motivated' }, + photos: [ + { id: 'r9-1', url: '/assets/images/properties/Brick_Front_Porch_Home.jpg', caption: 'Entryway' }, + { id: 'r9-2', url: '/assets/images/properties/Overlapping_Roof_Shingles.jpg', caption: 'Ridge Detail' }, + { id: 'r9-3', url: '/assets/images/properties/Hail_Damaged_Shingles.jpg', caption: 'Hail Damage' }, + { id: 'r9-4', url: '/assets/images/properties/Storm_Worn_Roof.jpg', caption: 'Worn Surface' }, + ] + }, + { + id: 10, top: '30%', left: '82%', + address: '905 W Main St, Richardson', + type: 'Residential', + problem: 'Granule Loss Pattern', + gain: '+$560K', + weather: 'Storm Watch', + status: 'Action Required', + specs: { roofSize: '42 sq', eventHistory: '5 yrs since installation' }, + owner: { fico: '720', profile: 'Recently Purchased' }, + photos: [ + { id: 'r10-1', url: '/assets/images/properties/Red_Shingle_Cottage.jpg', caption: 'Cottage Exterior' }, + { id: 'r10-2', url: '/assets/images/properties/Residential_Roof_Texture.jpg', caption: 'Roof Texture' }, + { id: 'r10-3', url: '/assets/images/properties/Cracked_Asphalt_Shingles.jpg', caption: 'Granule Loss' }, ] }, ]; +const FILTERS = ['All', 'Commercial', 'Residential', 'Storm Path']; +const STORM_WEATHER = ['Hail Expected', 'Storm Watch', 'Rain Soon']; + +const metrics = [ + { label: 'Total Pipeline', value: 57.1, prefix: '$', suffix: 'M', color: 'text-blue-500', stamp: '\u25CF Active', stampClass: 'permit-stamp--accent' }, + { label: 'Active Alerts', value: 7, prefix: '', suffix: '', color: 'text-amber-500', stamp: '\u25B2 Monitoring', stampClass: 'permit-stamp--accent' }, + { label: 'Urgent', value: 3, prefix: '', suffix: '', color: 'text-red-500', stamp: '\u2715 Critical', stampClass: 'permit-stamp--danger' }, + { label: 'Avg Roof Age', value: 18, prefix: '', suffix: ' yrs', color: 'text-emerald-500', stamp: '\u2713 Assessed', stampClass: 'permit-stamp--verified' }, +]; + +const isSpotVisible = (spot, filter) => { + if (filter === 'All') return true; + if (filter === 'Commercial') return spot.type === 'Commercial'; + if (filter === 'Residential') return spot.type === 'Residential'; + if (filter === 'Storm Path') return STORM_WEATHER.includes(spot.weather); + return true; +}; + const IntelligenceMap = () => { const navigate = useNavigate(); const [activeSpot, setActiveSpot] = useState(null); + const [activeFilter, setActiveFilter] = useState('All'); + const [hoveredSpot, setHoveredSpot] = useState(null); const [showRescheduleDialog, setShowRescheduleDialog] = useState(false); + const sectionRef = useRef(null); + const pinRefs = useRef([]); + const metricRefs = useRef([]); + const mapGlowRef = useRef(null); + + // Reschedule dialog timer useEffect(() => { - // Simulate incoming weather alert - const timer = setTimeout(() => { - setShowRescheduleDialog(true); - }, 2500); + const timer = setTimeout(() => setShowRescheduleDialog(true), 2500); return () => clearTimeout(timer); }, []); + // GSAP: Staggered pin entrance + metric counters + useEffect(() => { + const ctx = gsap.context(() => { + // Pin entrance + const pins = pinRefs.current.filter(Boolean); + pins.forEach(pin => gsap.set(pin, { opacity: 0, scale: 0, y: 20 })); + + gsap.to(pins, { + opacity: 1, scale: 1, y: 0, + duration: 0.6, stagger: 0.08, + ease: 'back.out(1.7)', + scrollTrigger: { + trigger: sectionRef.current, + start: 'top 70%', + toggleActions: 'play none none none', + }, + onComplete: () => { + // Clear GSAP inline styles so CSS transitions take over + pins.forEach(pin => { + if (pin) gsap.set(pin, { clearProps: 'opacity,transform' }); + }); + }, + }); + + // Metric counters — proxy object approach + metricRefs.current.forEach((el) => { + if (!el) return; + const target = parseFloat(el.dataset.target); + const suffix = el.dataset.suffix || ''; + const prefix = el.dataset.prefix || ''; + const counter = { value: 0 }; + el.innerText = prefix + '0' + suffix; + + gsap.to(counter, { + value: target, + duration: 2, + ease: 'power1.out', + scrollTrigger: { + trigger: el, + start: 'top 85%', + toggleActions: 'play none none none', + }, + onUpdate: () => { + const display = target % 1 !== 0 + ? counter.value.toFixed(1) + : Math.ceil(counter.value).toString(); + el.innerText = prefix + display + suffix; + }, + }); + }); + }, sectionRef); + + return () => ctx.revert(); + }, []); + + // Mouse-follow glow on map + const handleMapMouseMove = (e) => { + if (!mapGlowRef.current) return; + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + mapGlowRef.current.style.background = + `radial-gradient(350px circle at ${x}px ${y}px, rgba(6,182,212,0.08), transparent 60%)`; + }; + + const handleMapMouseLeave = () => { + if (mapGlowRef.current) mapGlowRef.current.style.background = 'transparent'; + }; + return ( -
-
-
-
-
- Live CRM Data Feed +
+ {/* Blueprint grid bg */} +
+ +
+ + {/* ── Header ── */} +
+
+
+ Live CRM Data Feed
-

Hyper-Local Sales Intelligence

-

+

+ Hyper-Local Sales Intelligence +

+

Our systems overlay live NOAA weather data with property value insights. Maximize revenue by targeting neighborhoods directly in the storm path.

- {/* Map Container Frame */} -
+ {/* ── Filter Bar ── */} +
+ {FILTERS.map((filter) => ( + + ))} +
- {/* Map Background - DFW Satellite Style */} + {/* ══════════════════════════════════════════════ */} + {/* MAP CONTAINER — always dark (satellite image) */} + {/* ══════════════════════════════════════════════ */} +
+ {/* Map Background */}
- {/* Aerial/Satellite View of a Grid City */} DFW Metroplex Map Overlay - - {/* Map Grid Overlay */}
-
+
- {/* Region Indicator - HUD Style */} -
-
-
-
Active Region
-
DFW Zone 2
-
-
+ {/* Mouse-follow glow */} +
+ + {/* Region Indicator — HUD */} +
+
+
Active Region
+
DFW Zone 2
+
- {/* Weather Overlay Widget */} -
-
-
-
- -
-
-
Severe Hail Watch
-
Approaching Tarrant County
-
ETA: 15 MINS
-
+ {/* Weather Widget */} +
+
+
+
+ {'\u26C8'} +
+
+
Severe Hail Watch
+
Approaching Tarrant County
+
ETA: 15 MINS
- {/* Interactive Pins */} - {hotspots.map((spot) => ( -
setActiveSpot(spot)} - > - {/* Radar Ripple Effect */} -
+ {/* ── Interactive Pins ── */} + {hotspots.map((spot, index) => { + const visible = isSpotVisible(spot, activeFilter); + const isDimmed = activeSpot && activeSpot.id !== spot.id; + const isCommercial = spot.type === 'Commercial'; - {/* Price Tag Pin */} -
-
- {spot.gain} + return ( +
{ pinRefs.current[index] = el; }} + className={`absolute group/pin z-20 cursor-pointer transition-all duration-300 ease-in-out + ${!visible ? 'opacity-0 scale-0 pointer-events-none' : ''} + ${visible && isDimmed ? 'opacity-30' : ''} + `} + style={{ top: spot.top, left: spot.left }} + onClick={() => setActiveSpot(activeSpot?.id === spot.id ? null : spot)} + onMouseEnter={() => setHoveredSpot(spot.id)} + onMouseLeave={() => setHoveredSpot(null)} + > + {/* Larger invisible touch target */} +
+ + {/* Radar Ripple */} + {visible && ( +
+ )} + + {/* Selected glow ring */} + {activeSpot?.id === spot.id && ( +
+ )} + + {/* Pin cluster */} +
+ {/* Pin Head */} +
+ + {/* Pin Line */} +
+ + {/* Ground Shadow */} +
- {/* Pin Head */} -
- - {/* Pin Line */} -
- - {/* Ground Shadow */} -
+ {/* Hover Tooltip */} +
+
{spot.address}
+
+ {spot.gain} +
+
+
-
- ))} + ); + })} - {/* Reschedule Dialog Overlay */} + {/* Reschedule Dialog */} {showRescheduleDialog && ( -
-
+
+
!
-
-

Schedule Conflict

-
+

Schedule Conflict

- +
-

- Heavy rainfall expected in Plano at 2:00 PM.
+ Heavy rainfall expected in Plano at 2:00 PM.
This conflicts with the inspection at Legacy West.

-
)} - {/* Right Sidebar Panel (Glass Detail View) */} + {/* Desktop Side Panel */} {activeSpot && ( - setActiveSpot(null)} - /> +
+ setActiveSpot(null)} + /> +
)} +
+ {/* ── Mobile Bottom Sheet ── */} + {activeSpot && ReactDOM.createPortal( +
+
setActiveSpot(null)} /> +
+ {/* Sheet header */} +
+
+
+
+
+

{activeSpot.address}

+
+ +
+
+ setActiveSpot(null)} + variant="sheet" + /> +
+
, + document.body + )} + + {/* ── Aggregate Metrics ── */} +
+ {metrics.map((m, i) => ( +
+
+
+ {m.label} +
+
{ metricRefs.current[i] = el; }} + data-target={m.value} + data-suffix={m.suffix} + data-prefix={m.prefix} + className={`text-3xl font-mono font-bold ${m.color} tracking-tight`} + > + {m.prefix}0{m.suffix} +
+
+ {m.stamp} +
+
+
+ ))}
+ @keyframes slideUp { + from { opacity: 0; transform: translate(-50%, 20px); } + to { opacity: 1; transform: translate(-50%, 0); } + } + @media (min-width: 768px) { + @keyframes slideUp { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } + } + } + `}
); }; diff --git a/src/components/Services.jsx b/src/components/Services.jsx index 76eb3c1..375b784 100644 --- a/src/components/Services.jsx +++ b/src/components/Services.jsx @@ -4,26 +4,34 @@ import { Icons } from './landing/constants'; const services = [ { - title: "Commercial and Residential Repair and Diagnostics", - desc: "Leak detection & shingle replacement in record time. We identify micro-fractures invisible to the naked eye.", + title: "Commercial & Residential Repair and Diagnostics", + desc: "Leak detection & shingle replacement in record time. We identify micro-fractures invisible to the naked eye using drone-captured LiDAR scans and AI-powered thermal analysis.", icon: Icons.Home, color: "from-blue-400 to-blue-600", - glow: "shadow-blue-500/20" + glow: "shadow-blue-500/20", + badge: "Licensed", + badgeClass: "permit-stamp--verified", + badgeIcon: "\u2713", + stat: "2,847", + statLabel: "Properties Inspected", }, - // Commercial Development Removed as per request { title: "Lead & Data Solutions", - desc: "Advanced lead scoring and real time storm tracking alerts. Know where the damage is before the phone rings. Powered by intelligent data models and predictive algorithms, we direct your reps and marketing into high-impact zones; where probability, urgency and opportunity align.", + desc: "Advanced lead scoring and real-time storm tracking alerts. Know where the damage is before the phone rings. We direct your reps and marketing into high-impact zones where probability, urgency and opportunity align.", icon: Icons.Chart, color: "from-purple-400 to-indigo-600", - glow: "shadow-purple-500/20" + glow: "shadow-purple-500/20", + badge: "Certified", + badgeClass: "permit-stamp--accent", + badgeIcon: "\u25B9", + stat: "96%", + statLabel: "Lead Accuracy", } ]; const Services = () => { const navigate = useNavigate(); - // Simple Tilt Logic const handleMouseMove = (e) => { const card = e.currentTarget; const rect = card.getBoundingClientRect(); @@ -31,11 +39,8 @@ const Services = () => { const y = e.clientY - rect.top; const centerX = rect.width / 2; const centerY = rect.height / 2; - - // Rotation calc (Max 5deg) - const rotateX = ((y - centerY) / centerY) * -5; - const rotateY = ((x - centerX) / centerX) * 5; - + const rotateX = ((y - centerY) / centerY) * -4; + const rotateY = ((x - centerX) / centerX) * 4; card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(1.02)`; }; @@ -44,52 +49,88 @@ const Services = () => { }; return ( -
-
+
+
-
+
-

Our Core Services

-

Leveraging military-grade technology for civilian roofing applications.

+ ▷ Core Operations +

What We Do Best

+

Military-grade technology applied to civilian roofing. Two pillars, one platform.

-
+
{services.map((service, idx) => (
- {/* Gradient Glow on Hover */} -
+ {/* Measurement ticks — top */} +
+ + {/* Gradient glow on hover */} +
-
-
- + {/* Top row: Icon + Service number */} +
+
+
+ +
+ 0{idx + 1}
-

+

{service.title}

-

+

{service.desc}

-
{ e.stopPropagation(); navigate('/login'); }} - className="pt-6 border-t border-gray-100 dark:border-white/10 flex items-center justify-between mt-auto group-hover:border-cyan/20 transition-colors cursor-pointer" - > - Explore Solution -
- ↗ + {/* Inline stat */} +
+ {service.stat} + {service.statLabel} +
+ + {/* Footer row: Permit stamp + CTA */} +
+ {service.badgeIcon} {service.badge} +
{ e.stopPropagation(); navigate('/login'); }} + className="flex items-center gap-2 text-xs font-bold text-zinc-500 uppercase tracking-wider group-hover:text-blue-400 transition-colors" + > + Explore +
+ ↗ +
+ + {/* Measurement ticks — bottom */} +
+
+ ))} +
+ + {/* Trust signals strip */} +
+ {[ + { label: "AI-Powered Analysis", icon: "◉" }, + { label: "15-Min Turnaround", icon: "◉" }, + { label: "Drone-Powered Inspections", icon: "◉" }, + { label: "Insurance-Ready Reports", icon: "◉" }, + ].map((item, i) => ( +
+ {item.icon} + {item.label}
))}
diff --git a/src/components/SpotlightCard.jsx b/src/components/SpotlightCard.jsx index 2d78cbb..564757e 100644 --- a/src/components/SpotlightCard.jsx +++ b/src/components/SpotlightCard.jsx @@ -44,7 +44,7 @@ export const SpotlightCard = ({ children, className = "", spotlightColor = "rgba onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} // Light: bg-white, Dark: bg-zinc-800 - className={`group relative ${rounded} bg-white dark:bg-zinc-800 overflow-hidden shadow-sm dark:shadow-none ${className}`} + className={`group relative ${rounded} bg-white dark:bg-zinc-800 overflow-hidden shadow-xl dark:shadow-[0_20px_60px_rgba(0,0,0,0.4)] ${className}`} > {/* Rainbow Glow Layer diff --git a/src/components/dashboard/IntelligenceSidePanel.jsx b/src/components/dashboard/IntelligenceSidePanel.jsx index 36792e4..7a9f9b4 100644 --- a/src/components/dashboard/IntelligenceSidePanel.jsx +++ b/src/components/dashboard/IntelligenceSidePanel.jsx @@ -4,7 +4,8 @@ import { useNavigate } from 'react-router-dom'; import { Icons } from '../landing/constants'; import { ChevronLeft, ChevronRight, X } from 'lucide-react'; -const IntelligenceSidePanel = ({ data, onClose }) => { +const IntelligenceSidePanel = ({ data, onClose, variant = 'default' }) => { + const isSheet = variant === 'sheet'; const navigate = useNavigate(); const [activeTab, setActiveTab] = useState('details'); // Initialize photos from data or use empty array if not present @@ -65,31 +66,41 @@ const IntelligenceSidePanel = ({ data, onClose }) => { return (
- {/* Header */} -
-
-
-

INTEL_FEED

+ {/* Header — only on desktop panel, mobile sheet provides its own */} + {!isSheet && ( +
+
+
+

INTEL_FEED

+
+
- -
+ )} {/* Tabs */} -
+
- {/* Content Area */} -
+ {/* Status / Weather Quick Bar */} +
+ {data.status && ( + + + {data.status} + + )} + {data.weather && data.weather !== 'Clear' && ( + + {'\u26C8'} {data.weather} + + )} + {data.weather === 'Clear' && ( + + {'\u2600'} Clear + + )} +
+ + {/* Content Area — data-lenis-prevent stops Lenis from hijacking wheel/touch scroll */} +
{/* DETAILS TAB */} {activeTab === 'details' && (
-
+
- -
{data.gain}
+ +
{data.gain}
- -
{data.address}
-
Dallas-Fort Worth Metroplex • {data.type} Zone
+ +
{data.address}
+
Dallas-Fort Worth Metroplex • {data.type} Zone
{/* Divider */} -
+
- +
{data.problem}
@@ -145,17 +194,17 @@ const IntelligenceSidePanel = ({ data, onClose }) => {
- -
{data.specs.built}
+ +
{data.specs.built}
- -
{data.specs.maintenance}
+ +
{data.specs.maintenance}
-
+
-
{data.management.name}
+
{data.management.name}
navigate('/login')} className="text-xs text-blue-400 cursor-pointer hover:underline mt-1 focus:outline-none focus:ring-1 focus:ring-blue-400 rounded" @@ -171,30 +220,30 @@ const IntelligenceSidePanel = ({ data, onClose }) => {
- -
{data.specs.roofSize}
+ +
{data.specs.roofSize}
- -
{data.specs.eventHistory}
+ +
{data.specs.eventHistory}
-
+
-
FICO Score
-
{data.owner.fico}
+
FICO Score
+
{data.owner.fico}
-
Profile
-
{data.owner.profile}
+
Profile
+
{data.owner.profile}
) : ( -
+
> ANALYSIS: {data.type === 'Commercial' ? 'High-value commercial asset at risk. Immediate inspection recommended.' : 'High probability of insurance approval due to recent storm activity.'}
)} @@ -214,11 +263,11 @@ const IntelligenceSidePanel = ({ data, onClose }) => {
{/* Photo Grid */} -
{/* pb-20 for bottom fixed button space if needed, though button is inline here */} +
{photos.map((photo, index) => (
setLightboxIndex(index)} > { alt={photo.caption || 'Property view'} className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" /> -
- View +
+ View + {photo.caption && ( + {photo.caption} + )}
- -
))} {/* Upload Placeholders if empty */} {photos.length === 0 && ( -
-

No photos available

+
+

No photos available

)}
diff --git a/src/components/landing/HeroParticles.jsx b/src/components/landing/HeroParticles.jsx new file mode 100644 index 0000000..c5e76d2 --- /dev/null +++ b/src/components/landing/HeroParticles.jsx @@ -0,0 +1,114 @@ +import React, { useRef, useMemo } from 'react'; +import { Canvas, useFrame, useThree } from '@react-three/fiber'; +import * as THREE from 'three'; + +/** + * ParticleField — Internal R3F component + * Renders ~800 small white dots with gentle organic drift and mouse parallax. + * Uses Points geometry with BufferAttribute for maximum performance. + * Aesthetic: "aerospace night sky" — calm, precise, not chaotic. + */ +function ParticleField({ count = 800 }) { + const points = useRef(); + const { pointer } = useThree(); + + // Generate initial positions + per-particle phase seeds + const { geometry, basePositions, seeds } = useMemo(() => { + const positions = new Float32Array(count * 3); + const seedArr = new Float32Array(count * 3); + + for (let i = 0; i < count; i++) { + const i3 = i * 3; + // Wide rectangular spread — matches hero viewport proportions + positions[i3] = (Math.random() - 0.5) * 26; // x: wide + positions[i3 + 1] = (Math.random() - 0.5) * 16; // y: tall + positions[i3 + 2] = (Math.random() - 0.5) * 10; // z: depth layers + // Random phase offsets for organic non-uniform motion + seedArr[i3] = Math.random() * Math.PI * 2; + seedArr[i3 + 1] = Math.random() * Math.PI * 2; + seedArr[i3 + 2] = Math.random() * Math.PI * 2; + } + + const geo = new THREE.BufferGeometry(); + const posAttr = new THREE.BufferAttribute(positions, 3); + posAttr.setUsage(THREE.DynamicDrawUsage); // Optimized for frequent updates + geo.setAttribute('position', posAttr); + + return { + geometry: geo, + basePositions: new Float32Array(positions), // Immutable copy + seeds: seedArr, + }; + }, [count]); + + useFrame((state) => { + if (!points.current) return; + + const time = state.clock.elapsedTime * 0.25; // Very slow time factor + const posArray = points.current.geometry.attributes.position.array; + + // Per-particle gentle drift + for (let i = 0; i < count; i++) { + const i3 = i * 3; + posArray[i3] = basePositions[i3] + Math.sin(time + seeds[i3]) * 0.12; + posArray[i3 + 1] = basePositions[i3 + 1] + Math.cos(time * 0.7 + seeds[i3 + 1]) * 0.10; + posArray[i3 + 2] = basePositions[i3 + 2] + Math.sin(time * 0.4 + seeds[i3 + 2]) * 0.06; + } + points.current.geometry.attributes.position.needsUpdate = true; + + // Mouse parallax — gentle rotation of the entire field + points.current.rotation.y = THREE.MathUtils.lerp( + points.current.rotation.y, + pointer.x * 0.06, + 0.012 + ); + points.current.rotation.x = THREE.MathUtils.lerp( + points.current.rotation.x, + -pointer.y * 0.04, + 0.012 + ); + }); + + return ( + + + + ); +} + +/** + * HeroParticles — Exported wrapper + * Transparent R3F Canvas overlaying the hero section. + * pointer-events: none so clicks pass through to CTAs below. + */ +export default function HeroParticles() { + return ( + + + + ); +} diff --git a/src/index.css b/src/index.css index 6cbdcaf..de75e73 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,55 @@ +/* === Font Imports === */ +/* Instrument Serif: "The Authority" — headlines, section titles, pull-quotes */ +@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap'); +/* JetBrains Mono: "The Instrument" — data readouts, stat counters, HUD labels */ +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap'); +/* Inter: "The Engineer" — body text, UI, navigation (already system-available, loaded for consistency) */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap'); + @tailwind base; @tailwind components; @tailwind utilities; +/* === Base Layer: Global Resets & Defaults === */ +@layer base { + :root { + font-family: 'Inter', system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + /* Crane SVG theme-aware stroke/fill tokens */ + --crane-line-strong: rgba(0, 0, 0, 0.22); + --crane-line-medium: rgba(0, 0, 0, 0.14); + --crane-line-subtle: rgba(0, 0, 0, 0.08); + --crane-fill-surface: rgba(0, 0, 0, 0.04); + } + + html, body, #root { + height: 100%; + margin: 0; + padding: 0; + width: 100%; + background-color: #ffffff; + color: #1a1a1a; + } + + /* Dark mode base for landing page */ + .dark { + background-color: #09090b; + color: rgba(255, 255, 255, 0.92); + + --crane-line-strong: rgba(255, 255, 255, 0.16); + --crane-line-medium: rgba(255, 255, 255, 0.10); + --crane-line-subtle: rgba(255, 255, 255, 0.06); + --crane-fill-surface: rgba(255, 255, 255, 0.03); + } +} + +/* === Utility Animations === */ @layer utilities { .animate-in { animation-duration: 0.5s; @@ -21,13 +69,8 @@ } @keyframes fadeIn { - from { - opacity: 0; - } - - to { - opacity: 1; - } + from { opacity: 0; } + to { opacity: 1; } } @keyframes slideInBottom { @@ -35,52 +78,173 @@ transform: translateY(1rem); opacity: 0; } - to { transform: translateY(0); opacity: 1; } } -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; +/* === Scrollbar: Hidden Globally === */ +* { + -ms-overflow-style: none; + scrollbar-width: none; } - -html, -body, -#root { - height: 100%; - margin: 0; - padding: 0; - width: 100%; - background-color: #ffffff; - color: #1a1a1a; +*::-webkit-scrollbar { + display: none; } @layer utilities { - - /* Hide scrollbar for Chrome, Safari and Opera */ - .no-scrollbar::-webkit-scrollbar { - display: none; - } - - /* Hide scrollbar for IE, Edge and Firefox */ + .no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar { -ms-overflow-style: none; - /* IE and Edge */ scrollbar-width: none; - /* Firefox */ } +} - /* Type 1: Invisible but copyable */ - /* Type 1: Invisible but copyable */ + +/* === Construction Design Elements === */ + +/* Permit stamp — "VERIFIED" / "INSPECTED" badge treatment */ +@layer components { + .permit-stamp { + display: inline-flex; + align-items: center; + gap: 6px; + border: 1.5px solid currentColor; + padding: 3px 10px; + font-family: 'JetBrains Mono', monospace; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.18em; + text-transform: uppercase; + transform: rotate(-2deg); + border-radius: 3px; + opacity: 0.7; + } + .permit-stamp--accent { + color: #3b82f6; + border-color: rgba(59, 130, 246, 0.4); + } + .permit-stamp--verified { + color: #10b981; + border-color: rgba(16, 185, 129, 0.4); + } + .permit-stamp--danger { + color: #ef4444; + border-color: rgba(239, 68, 68, 0.4); + } +} + +/* Shingle divider — overlapping parallelogram pattern mimicking roof shingles */ +@layer components { + .shingle-divider { + width: 100%; + height: 20px; + background: + linear-gradient(135deg, rgba(0,0,0,0.06) 25%, transparent 25%) -24px 0, + linear-gradient(225deg, rgba(0,0,0,0.06) 25%, transparent 25%) -24px 0, + linear-gradient(315deg, rgba(0,0,0,0.06) 25%, transparent 25%), + linear-gradient(45deg, rgba(0,0,0,0.06) 25%, transparent 25%); + background-size: 48px 20px; + background-color: transparent; + } + .dark .shingle-divider { + background: + linear-gradient(135deg, rgba(255,255,255,0.04) 25%, transparent 25%) -24px 0, + linear-gradient(225deg, rgba(255,255,255,0.04) 25%, transparent 25%) -24px 0, + linear-gradient(315deg, rgba(255,255,255,0.04) 25%, transparent 25%), + linear-gradient(45deg, rgba(255,255,255,0.04) 25%, transparent 25%); + background-size: 48px 20px; + } +} + +/* Safety stripe — diagonal hazard pattern for urgent/warning elements */ +@layer components { + .safety-stripe { + background: repeating-linear-gradient( + -45deg, + transparent, + transparent 4px, + rgba(245, 158, 11, 0.12) 4px, + rgba(245, 158, 11, 0.12) 8px + ); + } +} + +/* Measurement tick marks — ruler/tape aesthetic for stat counters */ +@layer components { + .measure-ticks { + background-image: + repeating-linear-gradient(90deg, rgba(0,0,0,0.15) 0px, rgba(0,0,0,0.15) 1.5px, transparent 1.5px, transparent 24px), + repeating-linear-gradient(90deg, rgba(0,0,0,0.07) 0px, rgba(0,0,0,0.07) 1px, transparent 1px, transparent 6px); + background-size: 100% 100%, 100% 50%; + background-repeat: no-repeat; + background-position: bottom, bottom; + } + .dark .measure-ticks { + background-image: + repeating-linear-gradient(90deg, rgba(255,255,255,0.22) 0px, rgba(255,255,255,0.22) 1.5px, transparent 1.5px, transparent 24px), + repeating-linear-gradient(90deg, rgba(255,255,255,0.1) 0px, rgba(255,255,255,0.1) 1px, transparent 1px, transparent 6px); + } + .measure-ticks-top { + background-position: top, top; + } +} + +/* Blueprint grid — combined background-image + background-size */ +@layer components { + .blueprint-grid { + background-image: + linear-gradient(rgba(59,130,246,0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(59,130,246,0.1) 1px, transparent 1px); + background-size: 48px 48px; + } + .dark .blueprint-grid { + background-image: + linear-gradient(rgba(59,130,246,0.05) 1px, transparent 1px), + linear-gradient(90deg, rgba(59,130,246,0.05) 1px, transparent 1px); + } + .blueprint-grid-fine { + background-image: + linear-gradient(rgba(59,130,246,0.06) 1px, transparent 1px), + linear-gradient(90deg, rgba(59,130,246,0.06) 1px, transparent 1px), + linear-gradient(rgba(59,130,246,0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(59,130,246,0.1) 1px, transparent 1px); + background-size: 12px 12px, 12px 12px, 48px 48px, 48px 48px; + } + .dark .blueprint-grid-fine { + background-image: + linear-gradient(rgba(59,130,246,0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(59,130,246,0.03) 1px, transparent 1px), + linear-gradient(rgba(59,130,246,0.06) 1px, transparent 1px), + linear-gradient(90deg, rgba(59,130,246,0.06) 1px, transparent 1px); + } +} + +/* Mobile bottom sheet slide-up for Intelligence Map */ +@keyframes slideUpSheet { + from { transform: translateY(100%); } + to { transform: translateY(0); } +} + +/* Crane swing — gentle pendulum sway for suspended CTA card */ +@keyframes craneSwing { + 0%, 100% { transform: rotate(0deg); } + 25% { transform: rotate(1.5deg); } + 75% { transform: rotate(-1.5deg); } +} +@layer components { + .crane-swing { + animation: craneSwing 4s ease-in-out infinite; + transform-origin: top center; + } + .crane-swing:hover { + animation-play-state: paused; + } +} + +/* === Attribution (existing, preserved) === */ +@layer utilities { .attribution-ghost, .content-ref-x7, .sys-ref-x7, @@ -89,44 +253,25 @@ body, display: inline-block; opacity: 0; font-size: 1px; - /* Must have size to be selected */ color: transparent; - /* Text exists but is invisible */ position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); - /* Standard sr-only hide */ white-space: nowrap; user-select: text; - /* Critical */ pointer-events: none; z-index: -1; } - /* Type 2: Hidden until selected (General) */ - /* Type 2: Hidden until selected (General) */ .attribution-reveal { color: transparent; background: transparent; user-select: text; } - .attribution-reveal::selection { background: #3b82f6; color: white; } } - -/* Global Hidden Scrollbar (User Requested) */ -* { - -ms-overflow-style: none; - /* IE and Edge */ - scrollbar-width: none; - /* Firefox */ -} - -*::-webkit-scrollbar { - display: none; -} \ No newline at end of file diff --git a/src/pages/Landing.jsx b/src/pages/Landing.jsx index 7c69caa..83acfd2 100644 --- a/src/pages/Landing.jsx +++ b/src/pages/Landing.jsx @@ -1,8 +1,8 @@ import React, { useEffect, useRef } from 'react'; import Chatbot from '../components/Chatbot'; import { useAuth } from '../context/AuthContext'; -import { useTheme } from '../context/ThemeContext'; // Import useTheme -import { ArrowRight, CheckCircle2, Home, Star, ShieldCheck, Clock, Award, TrendingDown, Users, MapPin, Phone, Mail, Instagram, Twitter, Youtube, Sun, Moon, LogOut, LayoutDashboard, User, Menu, X } from 'lucide-react'; +import { useTheme } from '../context/ThemeContext'; +import { ArrowRight, CheckCircle2, Home, Star, ShieldCheck, Clock, Award, TrendingDown, Users, MapPin, Phone, Mail, Instagram, Twitter, Youtube, Sun, Moon, LogOut, LayoutDashboard, User, Menu, X, ChevronDown } from 'lucide-react'; import { Link } from 'react-router-dom'; import { SpotlightCard } from '../components/SpotlightCard'; @@ -12,7 +12,7 @@ import IntelligenceMap from '../components/IntelligenceMap'; import gsap from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; import Logo from '../assets/images/LynkedUp_Pro_F_logo_Y.png'; -import HeroImg from '../assets/images/hero.png'; +import HeroImg from '../assets/images/hero_aerial_scan.png'; import ProcessImg1 from '../assets/images/process.png'; import ProcessImg2 from '../assets/images/process-2.png'; import ProcessImg3 from '../assets/images/process-3.png'; @@ -27,6 +27,19 @@ const Landing = () => { const sectionsRef = useRef([]); const statRefs = useRef([]); const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false); + const cursorGlowRef = useRef(null); + + // Mouse-follow ambient light — tracks cursor across the page + useEffect(() => { + const handleMouseMove = (e) => { + if (cursorGlowRef.current) { + cursorGlowRef.current.style.setProperty('--glow-x', `${e.clientX}px`); + cursorGlowRef.current.style.setProperty('--glow-y', `${e.clientY}px`); + } + }; + window.addEventListener('mousemove', handleMouseMove); + return () => window.removeEventListener('mousemove', handleMouseMove); + }, []); useEffect(() => { // Console Signature (Info level to persist in prod) @@ -56,32 +69,29 @@ const Landing = () => { ); }); - // Animated Stat Counters + // Animated Stat Counters — proxy object approach (avoids GSAP innerText parsing conflicts) statRefs.current.forEach((el) => { if (!el) return; const target = parseFloat(el.dataset.target); const suffix = el.dataset.suffix || ''; + const counter = { value: 0 }; - gsap.fromTo(el, - { innerText: 0 }, - { - innerText: target, - duration: 2, - ease: "power1.out", - scrollTrigger: { - trigger: el, - start: "top 85%", - toggleActions: "play none none none", - }, - snap: { innerText: suffix === '%' ? 1 : 0.1 }, - onUpdate: function () { - const value = this.targets()[0].innerText; - const formattedValue = suffix === '%' ? Math.ceil(value) : Math.ceil(value); - el.innerText = formattedValue + suffix; - } + el.innerText = '0' + suffix; + + gsap.to(counter, { + value: target, + duration: 2, + ease: "power1.out", + scrollTrigger: { + trigger: el, + start: "top 85%", + toggleActions: "play none none none", + }, + onUpdate: () => { + el.innerText = Math.ceil(counter.value) + suffix; } - ); + }); }); }, []); @@ -95,8 +105,15 @@ const Landing = () => { return (
- {/* DITHER EFFECT - Fixed Overlay */} -
+ {/* CURSOR AMBIENT LIGHT — subtle glow that follows mouse, desktop only */} +
{/* Nav */} - {/* HERO SECTION */} -
- {/* Background Image with Overlay */} + {/* ============================================ */} + {/* HERO SECTION — "The Overhead View" */} + {/* Aerospace Precision & Hometown Trust */} + {/* ============================================ */} +
+ + {/* Layer 0: Background image */}
- Modern Roof -
+ +
+
-
-
- 🇺🇸 - Crafted to Level the Field for Those Who Build America + {/* Layer 1: Subtle ambient glow behind content */} +
+
+
+ + {/* Layer 3: Content */} +
+ + {/* Patriotic badge — identity + mission statement */} +
+ 🇺🇸 + + Crafted to Level the Field for Those Who Build{' '} + America +
-

- ROOFING
- REVOLUTIONIZED + {/* Headline — Inter font-black: BOLD, commanding, statement piece */} +

+ ROOFING +
+ + REVOLUTIONIZED +

-

+ {/* Subtitle — the value proposition with credibility */} +

Automated drone inspections. Instant AI estimates. -
- Restoring your home's integrity with 20+ years of precision. +
+ Restoring your home's integrity with{' '} + 20+ years of precision.

+ {/* Dual CTAs */}
- + {/* B2C: Homeowners */} + Schedule Demo - + + + {/* B2B: Contractors */} + + Start Free Trial + - - See The Process -
- {/* Patriotic Discount Banner */} -
-
+ {/* Discount banner — Veterans, Seniors, Active Duty */} +
+
-
-

Active Duty, Veterans & Seniors

-

Get an exclusive 15% Discount on all services.

+
+

Active Duty, Veterans & Seniors

+

Get an exclusive 15% Discount on all services.

-
-
+ + {/* Layer 4: HUD Annotations — desktop only */} +
+
+

+ + 2,847 PROPERTIES TRACKED +

+

+ DFW METRO · ZONE 2 +

+
+
+ +
+
+

+ + HAIL WATCH ACTIVE +

+

+ ETA 35 MIN · WIND 24 MPH +

+
+
+ + {/* Scroll indicator */} +
+ Scroll + +
+ +
+ + {/* ── Shingle Divider ── */} +
{/* PROCESS SECTION (From Sky to Safety) */}
@@ -342,57 +429,90 @@ const Landing = () => { {/* NEW INTELLIGENCE MAP SECTION */} - {/* FACT CHECK & STATS */} -
-
+ {/* ── Shingle Divider ── */} +
+ + {/* FACT CHECK & STATS — Blueprint grid + Measurement tape aesthetic */} +
+
-

Why Risk The Wait?

-

Delaying roof repairs leads to exponential damage. Here are the facts.

+ ⚠ Critical Data +

Why Risk The Wait?

+

Delaying roof repairs leads to exponential damage. Here are the facts.

-
-
statRefs.current[0] = el} - data-target="40" - data-suffix="%" - className="text-4xl font-black text-red-500 mb-2" - > - 0% + {/* Stat Card 1 — Insurance Denials */} +
+
+
+
statRefs.current[0] = el} + data-target="40" + data-suffix="%" + className="text-5xl font-mono font-bold text-red-500 mb-1 tracking-tight" + > + 0% +
+

Insurance Denials

+

Of claims are denied due to "lack of maintenance" if proof of regular inspection isn't provided.

-

Insurance Denials

-

Of claims are denied due to "lack of maintenance" if proof of regular inspection isn't provided.

+
+ ✕ High Risk +
+
-
-
statRefs.current[1] = el} - data-target="5" - data-suffix="yrs" - className="text-4xl font-black text-yellow-500 mb-2" - > - 0yrs + + {/* Stat Card 2 — Life Reduced */} +
+
+
+
statRefs.current[1] = el} + data-target="5" + data-suffix=" yrs" + className="text-5xl font-mono font-bold text-yellow-500 mb-1 tracking-tight" + > + 0 yrs +
+

Life Reduced

+

A minor leak left for 6 months can reduce your roof's lifespan by up to 5 years.

-

Life Reduced

-

A minor leak left for 6 months can reduce your roof's lifespan by up to 5 years.

+
+ ⚠ Caution +
+
-
-
statRefs.current[2] = el} - data-target="15" - data-suffix="m" - className="text-4xl font-black text-green-500 mb-2" - > - 0m + + {/* Stat Card 3 — Estimate Time */} +
+
+
+
statRefs.current[2] = el} + data-target="15" + data-suffix=" min" + className="text-5xl font-mono font-bold text-emerald-500 mb-1 tracking-tight" + > + 0 min +
+

Our Estimate Time

+

While others take days, we give you a quote before our drone even lands.

-

Our Estimate Time

-

While others take days, we give you a quote before our drone even lands.

+
+ ✓ Verified +
+
+ {/* ── Shingle Divider ── */} +
+ {/* INDUSTRIES THAT LOVE US */}
@@ -507,31 +627,190 @@ const Landing = () => {
+ {/* ── Shingle Divider ── */} +
+ + {/* ══════════════════════════════════════════════════ */} + {/* CRANE CTA — "One crane, one card, one moment" */} + {/* Construction signature element before footer */} + {/* ══════════════════════════════════════════════════ */} +
+ {/* Blueprint grid background */} +
+ +
+ + {/* Tower Crane SVG — full-scale blueprint line art */} +
+ +
+ + {/* CTA Card — "suspended" from crane, swings like a pendulum */} +
+ {/* Attachment point — visual connector to cable above */} +
+
+
+ + ✓ Project Ready + +

+ Ready to Build? +

+

+ Whether you protect one home or manage a thousand roofs, the future of roofing starts here. +

+ +
+ + Schedule Demo + + + + Start Free Trial + + +
+
+
+
+ + {/* ── Shingle Divider ── */} +
+ {/* FOOTER */} -