diff --git a/package.json b/package.json index 9b06203..80cd5ae 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@tailwindcss/typography": "^0.5.19", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", + "@types/three": "^0.175.0", "astro": "^6.1.2", "tailwindcss": "^3.4.19" }, @@ -24,6 +25,9 @@ "@giscus/react": "^3.1.0", "@pilcrowjs/object-parser": "^0.0.4", "@react-hook/intersection-observer": "^3.1.2", + "@react-three/drei": "^9.122.0", + "@react-three/fiber": "^8.18.0", + "@react-three/postprocessing": "^2.19.1", "@rehype-pretty/transformers": "^0.13.2", "@vercel/analytics": "^2.0.1", "@vercel/speed-insights": "^2.0.0", @@ -31,6 +35,7 @@ "ioredis": "^5.10.1", "lucide-react": "^0.468.0", "marked": "^15.0.12", + "postprocessing": "^6.39.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.6.0", @@ -40,6 +45,7 @@ "rehype-slug": "^6.0.0", "schema-dts": "^1.1.5", "shiki": "^3.23.0", + "three": "^0.175.0", "typewriter-effect": "^2.22.0", "unist-util-visit": "^5.1.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4e49a36..04b7bcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,15 @@ importers: '@react-hook/intersection-observer': specifier: ^3.1.2 version: 3.1.2(react@18.3.1) + '@react-three/drei': + specifier: ^9.122.0 + version: 9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0))(@types/react@18.3.28)(@types/three@0.175.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0)(use-sync-external-store@1.6.0(react@18.3.1)) + '@react-three/fiber': + specifier: ^8.18.0 + version: 8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0) + '@react-three/postprocessing': + specifier: ^2.19.1 + version: 2.19.1(@react-three/fiber@8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0))(@types/three@0.175.0)(react@18.3.1)(three@0.175.0) '@rehype-pretty/transformers': specifier: ^0.13.2 version: 0.13.2 @@ -50,6 +59,9 @@ importers: marked: specifier: ^15.0.12 version: 15.0.12 + postprocessing: + specifier: ^6.39.0 + version: 6.39.0(three@0.175.0) react: specifier: ^18.3.1 version: 18.3.1 @@ -77,6 +89,9 @@ importers: shiki: specifier: ^3.23.0 version: 3.23.0 + three: + specifier: ^0.175.0 + version: 0.175.0 typewriter-effect: specifier: ^2.22.0 version: 2.22.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -99,6 +114,9 @@ importers: '@types/react-dom': specifier: ^18.3.7 version: 18.3.7(@types/react@18.3.28) + '@types/three': + specifier: ^0.175.0 + version: 0.175.0 astro: specifier: ^6.1.2 version: 6.1.2(@types/node@24.12.0)(@upstash/redis@1.37.0)(@vercel/functions@3.4.3)(@vercel/kv@3.0.0)(ioredis@5.10.1)(jiti@1.21.7)(rollup@4.60.1)(typescript@5.7.3)(yaml@2.8.3) @@ -232,6 +250,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + 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'} @@ -609,6 +631,14 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@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'} @@ -652,6 +682,77 @@ packages: peerDependencies: react: '>=16.8' + '@react-spring/animated@9.7.5': + resolution: {integrity: sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/core@9.7.5': + resolution: {integrity: sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/rafz@9.7.5': + resolution: {integrity: sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==} + + '@react-spring/shared@9.7.5': + resolution: {integrity: sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/three@9.7.5': + resolution: {integrity: sha512-RxIsCoQfUqOS3POmhVHa1wdWS0wyHAUway73uRLp3GAL5U2iYVNdnzQsep6M2NZ994BlW8TcKuMtQHUqOsy6WA==} + peerDependencies: + '@react-three/fiber': '>=6.0' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + three: '>=0.126' + + '@react-spring/types@9.7.5': + resolution: {integrity: sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==} + + '@react-three/drei@9.122.0': + resolution: {integrity: sha512-SEO/F/rBCTjlLez7WAlpys+iGe9hty4rNgjZvgkQeXFSiwqD4Hbk/wNHMAbdd8vprO2Aj81mihv4dF5bC7D0CA==} + peerDependencies: + '@react-three/fiber': ^8 + react: ^18 + react-dom: ^18 + three: '>=0.137' + peerDependenciesMeta: + react-dom: + optional: true + + '@react-three/fiber@8.18.0': + resolution: {integrity: sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==} + peerDependencies: + expo: '>=43.0' + expo-asset: '>=8.4' + expo-file-system: '>=11.0' + expo-gl: '>=11.0' + react: '>=18 <19' + react-dom: '>=18 <19' + react-native: '>=0.64' + three: '>=0.133' + 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 + + '@react-three/postprocessing@2.19.1': + resolution: {integrity: sha512-7P25LOSToH/I6b3UipNK17IIFlX4FDUmWcaomfwu82+CzhXTOz8Fcc1ZXEZ7vFA/5Fr/2peNlXgXZJvoa+aCdA==} + peerDependencies: + '@react-three/fiber': ^8.0 + react: ^18.0 + three: '>= 0.138.0' + '@rehype-pretty/transformers@0.13.2': resolution: {integrity: sha512-p2ciQSwqy5Ip8aNUa9q6rdS/hJZXrxHYYfDVOHvKOsBu3t9HDmQ65YX6r9Qbl19vi160OAxmGF7MIoCRDJrRhg==} engines: {node: '>=18'} @@ -860,6 +961,9 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + '@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==} @@ -875,6 +979,9 @@ packages: '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/draco3d@1.4.10': + resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -899,6 +1006,9 @@ packages: '@types/node@24.12.0': resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} + '@types/offscreencanvas@2019.7.3': + resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} + '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} @@ -907,12 +1017,26 @@ packages: peerDependencies: '@types/react': ^18.0.0 + '@types/react-reconciler@0.26.7': + resolution: {integrity: sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==} + + '@types/react-reconciler@0.28.9': + resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} + peerDependencies: + '@types/react': '*' + '@types/react@18.3.28': resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + '@types/stats.js@0.17.4': + resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} + + '@types/three@0.175.0': + resolution: {integrity: sha512-ldMSBgtZOZ3g9kJ3kOZSEtZIEITmJOzu8eKVpkhf036GuNkM4mt0NXecrjCn5tMm1OblOF7dZehlaDypBfNokw==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -922,12 +1046,23 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/webxr@0.5.24': + resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} '@upstash/redis@1.37.0': resolution: {integrity: sha512-LqOJ3+XWPLSZ2rGSed5DYG3ixybxb8EhZu3yQqF7MdZX1wLBG/FRcI6xcUZXHy/SS7mmXWyadrud0HJHkOc+uw==} + '@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: @@ -1041,6 +1176,9 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@webgpu/types@0.1.69': + resolution: {integrity: sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==} + abbrev@3.0.1: resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -1120,11 +1258,17 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.12: resolution: {integrity: sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==} engines: {node: '>=6.0.0'} 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'} @@ -1148,10 +1292,18 @@ 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==} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camera-controls@2.10.1: + resolution: {integrity: sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w==} + peerDependencies: + three: '>=0.126.1' + caniuse-lite@1.0.30001782: resolution: {integrity: sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==} @@ -1226,6 +1378,15 @@ 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'} + crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} @@ -1285,6 +1446,9 @@ packages: destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + detect-gpu@5.0.70: + resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1318,6 +1482,9 @@ packages: domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + draco3d@1.5.7: + resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} + dset@3.1.4: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} @@ -1414,6 +1581,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-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -1465,6 +1638,9 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + glsl-noise@0.0.0: + resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -1517,6 +1693,9 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hls.js@1.6.15: + resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==} + html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -1533,6 +1712,12 @@ packages: hyphenate-style-name@1.1.0: resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -1593,10 +1778,21 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + is-wsl@3.1.1: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + its-fine@1.2.5: + resolution: {integrity: sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==} + peerDependencies: + react: '>=18.0' + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -1621,6 +1817,9 @@ packages: engines: {node: '>=6'} hasBin: true + 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'} @@ -1662,6 +1861,18 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + maath@0.10.8: + resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==} + peerDependencies: + '@types/three': '>=0.134.0' + three: '>=0.134.0' + + maath@0.6.0: + resolution: {integrity: sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==} + peerDependencies: + '@types/three': '>=0.144.0' + three: '>=0.144.0' + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1744,6 +1955,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.18.1: + resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==} + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -1875,6 +2094,12 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + n8ao@1.10.1: + resolution: {integrity: sha512-hhI1pC+BfOZBV1KMwynBrVlIm8wqLxj/abAWhF2nZ0qQKyzTSQa1QtLVS2veRiuoBQXojxobcnp0oe+PUoxf/w==} + peerDependencies: + postprocessing: '>=6.30.0' + three: '>=0.137' + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1975,6 +2200,10 @@ packages: resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==} engines: {node: '>=14.0.0'} + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -2076,10 +2305,21 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} + postprocessing@6.39.0: + resolution: {integrity: sha512-/G6JY8hs426lcto/pBZlnFSkyEo1fHsh4gy7FPJtq1SaSUOzJgDW6f6f1K/+aMOYzK/eQEefyOb3++jPPIUeDA==} + peerDependencies: + three: '>= 0.168.0 < 0.184.0' + + potpack@1.0.2: + resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + promise-worker-transferable@1.0.4: + resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2099,6 +2339,11 @@ packages: raf@3.4.1: resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + react-composer@5.0.3: + resolution: {integrity: sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==} + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -2112,6 +2357,12 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-reconciler@0.27.0: + resolution: {integrity: sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^18.0.0 + react-refresh@0.18.0: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} @@ -2122,6 +2373,15 @@ packages: peerDependencies: react: '>=16.8.0' + 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@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -2214,6 +2474,10 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -2251,6 +2515,9 @@ packages: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} + scheduler@0.21.0: + resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -2273,6 +2540,14 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + shiki@3.23.0: resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} @@ -2306,6 +2581,15 @@ packages: standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + 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==} + stream-replace-string@2.0.0: resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} @@ -2330,6 +2614,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' + svgo@4.0.1: resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} engines: {node: '>=16'} @@ -2351,6 +2640,20 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + three-mesh-bvh@0.7.8: + resolution: {integrity: sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==} + deprecated: Deprecated due to three.js version incompatibility. Please use v0.8.0, instead. + peerDependencies: + three: '>= 0.151.0' + + three-stdlib@2.36.1: + resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==} + peerDependencies: + three: '>=0.128.0' + + three@0.175.0: + resolution: {integrity: sha512-nNE3pnTHxXN/Phw768u0Grr7W4+rumGg/H6PgeseNJojkJtmeHJfZWi41Gp2mpXl1pg1pf1zjwR4McM1jTqkpg==} + tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} @@ -2376,6 +2679,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==} @@ -2395,6 +2711,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==} + typescript@5.7.3: resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} engines: {node: '>=14.17'} @@ -2525,9 +2844,18 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -2588,6 +2916,12 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + 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==} @@ -2598,6 +2932,11 @@ packages: resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} engines: {node: '>=4'} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} @@ -2624,6 +2963,48 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zustand@3.7.2: + resolution: {integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==} + engines: {node: '>=12.7.0'} + peerDependencies: + react: '>=16.8' + peerDependenciesMeta: + react: + optional: true + + 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.12: + resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} + 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==} @@ -2857,6 +3238,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -3153,6 +3536,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@mediapipe/tasks-vision@0.10.17': {} + + '@monogrid/gainmap-js@3.4.0(three@0.175.0)': + dependencies: + promise-worker-transferable: 1.0.4 + three: 0.175.0 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3196,6 +3586,107 @@ snapshots: dependencies: react: 18.3.1 + '@react-spring/animated@9.7.5(react@18.3.1)': + dependencies: + '@react-spring/shared': 9.7.5(react@18.3.1) + '@react-spring/types': 9.7.5 + react: 18.3.1 + + '@react-spring/core@9.7.5(react@18.3.1)': + dependencies: + '@react-spring/animated': 9.7.5(react@18.3.1) + '@react-spring/shared': 9.7.5(react@18.3.1) + '@react-spring/types': 9.7.5 + react: 18.3.1 + + '@react-spring/rafz@9.7.5': {} + + '@react-spring/shared@9.7.5(react@18.3.1)': + dependencies: + '@react-spring/rafz': 9.7.5 + '@react-spring/types': 9.7.5 + react: 18.3.1 + + '@react-spring/three@9.7.5(@react-three/fiber@8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0))(react@18.3.1)(three@0.175.0)': + dependencies: + '@react-spring/animated': 9.7.5(react@18.3.1) + '@react-spring/core': 9.7.5(react@18.3.1) + '@react-spring/shared': 9.7.5(react@18.3.1) + '@react-spring/types': 9.7.5 + '@react-three/fiber': 8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0) + react: 18.3.1 + three: 0.175.0 + + '@react-spring/types@9.7.5': {} + + '@react-three/drei@9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0))(@types/react@18.3.28)(@types/three@0.175.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0)(use-sync-external-store@1.6.0(react@18.3.1))': + dependencies: + '@babel/runtime': 7.29.2 + '@mediapipe/tasks-vision': 0.10.17 + '@monogrid/gainmap-js': 3.4.0(three@0.175.0) + '@react-spring/three': 9.7.5(@react-three/fiber@8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0))(react@18.3.1)(three@0.175.0) + '@react-three/fiber': 8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0) + '@use-gesture/react': 10.3.1(react@18.3.1) + camera-controls: 2.10.1(three@0.175.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.175.0)(three@0.175.0) + meshline: 3.3.1(three@0.175.0) + react: 18.3.1 + react-composer: 5.0.3(react@18.3.1) + stats-gl: 2.4.2(@types/three@0.175.0)(three@0.175.0) + stats.js: 0.17.0 + suspend-react: 0.1.3(react@18.3.1) + three: 0.175.0 + three-mesh-bvh: 0.7.8(three@0.175.0) + three-stdlib: 2.36.1(three@0.175.0) + troika-three-text: 0.52.4(three@0.175.0) + tunnel-rat: 0.1.2(@types/react@18.3.28)(react@18.3.1) + utility-types: 3.11.0 + zustand: 5.0.12(@types/react@18.3.28)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/three' + - immer + - use-sync-external-store + + '@react-three/fiber@8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0)': + dependencies: + '@babel/runtime': 7.29.2 + '@types/react-reconciler': 0.26.7 + '@types/webxr': 0.5.24 + base64-js: 1.5.1 + buffer: 6.0.3 + its-fine: 1.2.5(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-reconciler: 0.27.0(react@18.3.1) + react-use-measure: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + scheduler: 0.21.0 + suspend-react: 0.1.3(react@18.3.1) + three: 0.175.0 + zustand: 3.7.2(react@18.3.1) + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + + '@react-three/postprocessing@2.19.1(@react-three/fiber@8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0))(@types/three@0.175.0)(react@18.3.1)(three@0.175.0)': + dependencies: + '@react-three/fiber': 8.18.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.175.0) + buffer: 6.0.3 + maath: 0.6.0(@types/three@0.175.0)(three@0.175.0) + n8ao: 1.10.1(postprocessing@6.39.0(three@0.175.0))(three@0.175.0) + postprocessing: 6.39.0(three@0.175.0) + react: 18.3.1 + three: 0.175.0 + three-stdlib: 2.36.1(three@0.175.0) + transitivePeerDependencies: + - '@types/three' + '@rehype-pretty/transformers@0.13.2': {} '@rolldown/pluginutils@1.0.0-rc.3': {} @@ -3359,6 +3850,8 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.19(yaml@2.8.3) + '@tweenjs/tween.js@23.1.3': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.2 @@ -3384,6 +3877,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 @@ -3410,12 +3905,22 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/offscreencanvas@2019.7.3': {} + '@types/prop-types@15.7.15': {} '@types/react-dom@18.3.7(@types/react@18.3.28)': dependencies: '@types/react': 18.3.28 + '@types/react-reconciler@0.26.7': + dependencies: + '@types/react': 18.3.28 + + '@types/react-reconciler@0.28.9(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + '@types/react@18.3.28': dependencies: '@types/prop-types': 15.7.15 @@ -3425,12 +3930,25 @@ snapshots: dependencies: '@types/node': 24.12.0 + '@types/stats.js@0.17.4': {} + + '@types/three@0.175.0': + dependencies: + '@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.18.1 + '@types/trusted-types@2.0.7': {} '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} + '@types/webxr@0.5.24': {} + '@ungap/structured-clone@1.3.0': {} '@upstash/redis@1.37.0': @@ -3438,6 +3956,13 @@ snapshots: uncrypto: 0.1.3 optional: true + '@use-gesture/core@10.3.1': {} + + '@use-gesture/react@10.3.1(react@18.3.1)': + dependencies: + '@use-gesture/core': 10.3.1 + react: 18.3.1 + '@vercel/analytics@1.6.1(react@18.3.1)': optionalDependencies: react: 18.3.1 @@ -3499,6 +4024,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@webgpu/types@0.1.69': {} + abbrev@3.0.1: {} acorn-import-attributes@1.9.5(acorn@8.16.0): @@ -3655,8 +4182,14 @@ snapshots: balanced-match@4.0.4: {} + base64-js@1.5.1: {} + baseline-browser-mapping@2.10.12: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + binary-extensions@2.3.0: {} bindings@1.5.0: @@ -3681,8 +4214,17 @@ snapshots: node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.1) + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + camelcase-css@2.0.1: {} + camera-controls@2.10.1(three@0.175.0): + dependencies: + three: 0.175.0 + caniuse-lite@1.0.30001782: {} ccount@2.0.1: {} @@ -3737,6 +4279,16 @@ 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 + shebang-command: 2.0.0 + which: 2.0.2 + crossws@0.3.5: dependencies: uncrypto: 0.1.3 @@ -3787,6 +4339,10 @@ snapshots: destr@2.0.5: {} + detect-gpu@5.0.70: + dependencies: + webgl-constants: 1.1.1 + detect-libc@2.1.2: {} devalue@5.6.4: {} @@ -3819,6 +4375,8 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + draco3d@1.5.7: {} + dset@3.1.4: {} electron-to-chromium@1.5.328: {} @@ -3947,6 +4505,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + fflate@0.6.10: {} + + fflate@0.8.2: {} + file-uri-to-path@1.0.0: {} fill-range@7.1.1: @@ -3992,6 +4554,8 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 + glsl-noise@0.0.0: {} + graceful-fs@4.2.11: {} h3@1.15.10: @@ -4146,6 +4710,8 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 + hls.js@1.6.15: {} + html-escaper@3.0.3: {} html-void-elements@3.0.0: {} @@ -4161,6 +4727,10 @@ snapshots: hyphenate-style-name@1.1.0: {} + ieee754@1.2.1: {} + + immediate@3.0.6: {} + inline-style-parser@0.2.7: {} intersection-observer@0.10.0: {} @@ -4216,10 +4786,21 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@2.2.2: {} + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 + isexe@2.0.0: {} + + its-fine@1.2.5(@types/react@18.3.28)(react@18.3.1): + dependencies: + '@types/react-reconciler': 0.28.9(@types/react@18.3.28) + react: 18.3.1 + transitivePeerDependencies: + - '@types/react' + jiti@1.21.7: {} js-tokens@4.0.0: {} @@ -4235,6 +4816,10 @@ snapshots: json5@2.2.3: {} + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -4275,6 +4860,16 @@ snapshots: dependencies: react: 18.3.1 + maath@0.10.8(@types/three@0.175.0)(three@0.175.0): + dependencies: + '@types/three': 0.175.0 + three: 0.175.0 + + maath@0.6.0(@types/three@0.175.0)(three@0.175.0): + dependencies: + '@types/three': 0.175.0 + three: 0.175.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4470,6 +5065,12 @@ snapshots: merge2@1.4.1: {} + meshline@3.3.1(three@0.175.0): + dependencies: + three: 0.175.0 + + meshoptimizer@0.18.1: {} + micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.3.0 @@ -4759,6 +5360,11 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + n8ao@1.10.1(postprocessing@6.39.0(three@0.175.0))(three@0.175.0): + dependencies: + postprocessing: 6.39.0(three@0.175.0) + three: 0.175.0 + nanoid@3.3.11: {} neotraverse@0.6.18: {} @@ -4851,6 +5457,8 @@ snapshots: path-expression-matcher@1.2.0: {} + path-key@3.1.1: {} + path-parse@1.0.7: {} path-scurry@2.0.2: @@ -4926,8 +5534,19 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postprocessing@6.39.0(three@0.175.0): + dependencies: + three: 0.175.0 + + potpack@1.0.2: {} + prismjs@1.30.0: {} + promise-worker-transferable@1.0.4: + dependencies: + is-promise: 2.2.2 + lie: 3.3.0 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -4947,6 +5566,11 @@ snapshots: dependencies: performance-now: 2.1.0 + react-composer@5.0.3(react@18.3.1): + dependencies: + prop-types: 15.8.1 + react: 18.3.1 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -4959,6 +5583,12 @@ snapshots: react-is@16.13.1: {} + react-reconciler@0.27.0(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.21.0 + react-refresh@0.18.0: {} react-responsive@10.0.1(react@18.3.1): @@ -4969,6 +5599,12 @@ snapshots: react: 18.3.1 shallow-equal: 3.1.0 + react-use-measure@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -5129,6 +5765,8 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + require-from-string@2.0.2: {} + resolve-from@5.0.0: {} resolve@1.22.11: @@ -5201,6 +5839,10 @@ snapshots: sax@1.6.0: {} + scheduler@0.21.0: + dependencies: + loose-envify: 1.4.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -5245,6 +5887,12 @@ snapshots: '@img/sharp-win32-x64': 0.34.5 optional: true + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + shiki@3.23.0: dependencies: '@shikijs/core': 3.23.0 @@ -5286,6 +5934,13 @@ snapshots: standard-as-callback@2.1.0: {} + stats-gl@2.4.2(@types/three@0.175.0)(three@0.175.0): + dependencies: + '@types/three': 0.175.0 + three: 0.175.0 + + stats.js@0.17.0: {} + stream-replace-string@2.0.0: {} stringify-entities@4.0.4: @@ -5315,6 +5970,10 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + suspend-react@0.1.3(react@18.3.1): + dependencies: + react: 18.3.1 + svgo@4.0.1: dependencies: commander: 11.1.0 @@ -5369,6 +6028,22 @@ snapshots: dependencies: any-promise: 1.3.0 + three-mesh-bvh@0.7.8(three@0.175.0): + dependencies: + three: 0.175.0 + + three-stdlib@2.36.1(three@0.175.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.175.0 + + three@0.175.0: {} + tiny-inflate@1.0.3: {} tinyclip@0.1.12: {} @@ -5388,6 +6063,20 @@ snapshots: trim-lines@3.0.1: {} + troika-three-text@0.52.4(three@0.175.0): + dependencies: + bidi-js: 1.0.3 + three: 0.175.0 + troika-three-utils: 0.52.4(three@0.175.0) + troika-worker-utils: 0.52.0 + webgl-sdf-generator: 1.1.1 + + troika-three-utils@0.52.4(three@0.175.0): + dependencies: + three: 0.175.0 + + troika-worker-utils@0.52.0: {} + trough@2.2.0: {} ts-interface-checker@0.1.13: {} @@ -5399,6 +6088,14 @@ snapshots: tslib@2.8.1: optional: true + tunnel-rat@0.1.2(@types/react@18.3.28)(react@18.3.1): + dependencies: + zustand: 4.5.7(@types/react@18.3.28)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - immer + - react + typescript@5.7.3: optional: true @@ -5506,8 +6203,14 @@ snapshots: punycode: 2.3.1 optional: true + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + util-deprecate@1.0.2: {} + utility-types@3.11.0: {} + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -5543,6 +6246,10 @@ snapshots: web-namespaces@2.0.1: {} + webgl-constants@1.1.1: {} + + webgl-sdf-generator@1.1.1: {} + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -5552,6 +6259,10 @@ snapshots: which-pm-runs@1.1.0: {} + which@2.0.2: + dependencies: + isexe: 2.0.0 + xxhash-wasm@1.1.0: {} yallist@3.1.1: {} @@ -5566,4 +6277,21 @@ snapshots: zod@4.3.6: {} + zustand@3.7.2(react@18.3.1): + optionalDependencies: + react: 18.3.1 + + zustand@4.5.7(@types/react@18.3.28)(react@18.3.1): + dependencies: + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + react: 18.3.1 + + zustand@5.0.12(@types/react@18.3.28)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): + optionalDependencies: + '@types/react': 18.3.28 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + zwitch@2.0.4: {} diff --git a/src/components/background/index.tsx b/src/components/background/index.tsx index fa59c5b..b11cfed 100644 --- a/src/components/background/index.tsx +++ b/src/components/background/index.tsx @@ -351,8 +351,6 @@ const Background: React.FC = ({ style={{ cursor: "default" }} />
-
-
); }; diff --git a/src/components/hero/index.tsx b/src/components/hero/index.tsx index f76155f..261aa7c 100644 --- a/src/components/hero/index.tsx +++ b/src/components/hero/index.tsx @@ -1,8 +1,12 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, Suspense, lazy } from "react"; import Typewriter from "typewriter-effect"; import { THEMES } from "@/lib/themes"; import { applyTheme, getStoredThemeId } from "@/lib/themes/engine"; +// Preload void component — starts downloading when countdown begins +const voidImport = () => import("@/components/void"); +const VoidExperience = lazy(voidImport); + interface GithubData { status: { message: string } | null; commit: { message: string; repo: string; date: string; url: string } | null; @@ -467,9 +471,20 @@ export default function Hero() { .catch(() => { githubRef.current = { status: null, commit: null, tinkering: null }; }); }, []); - // Countdown timer + // Void token + preload during countdown + const voidTokenRef = useRef(null); useEffect(() => { if (phase !== "countdown") return; + + // Preload the void component bundle + voidImport(); + + // Fetch a signed token for the void visit + fetch("/api/void-token") + .then(r => r.json()) + .then(data => { voidTokenRef.current = data.token; }) + .catch(() => { voidTokenRef.current = null; }); + const interval = setInterval(() => { setCountdown(prev => { if (prev <= 1) { @@ -483,13 +498,20 @@ export default function Hero() { return () => clearInterval(interval); }, [phase]); - // Glitch → navigate to /enlighten + // Glitch → transition into void + const glitchRef = useRef(null); useEffect(() => { if (phase !== "glitch") return; + + // Apply glitch to all direct children of the layout wrapper + const wrapper = glitchRef.current?.closest("main")?.parentElement || document.body; const style = document.createElement("style"); style.textContent = ` + .hero-glitch-child { + animation: hero-glitch 3s ease-in forwards; + } @keyframes hero-glitch { - 0% { filter: none; transform: none; } + 0% { filter: none; transform: none; opacity: 1; } 5% { filter: hue-rotate(90deg) saturate(3); transform: skewX(2deg); } 10% { filter: invert(1); transform: skewX(-3deg) translateX(5px); } 15% { filter: hue-rotate(180deg) brightness(1.5); transform: scale(1.02); } @@ -501,19 +523,25 @@ export default function Hero() { 60% { filter: saturate(0) brightness(1.8); transform: scale(1.01); } 70% { filter: hue-rotate(180deg) brightness(0.3); transform: none; } 80% { filter: contrast(5) saturate(0); transform: skewX(-1deg); } - 90% { filter: brightness(0.1); transform: scale(0.99); } - 100% { filter: brightness(0); transform: none; } + 90% { filter: brightness(0.1); transform: scale(0.99); opacity: 0.1; } + 100% { filter: brightness(0); transform: none; opacity: 0; } } `; document.head.appendChild(style); - document.documentElement.style.animation = "hero-glitch 3s ease-in forwards"; + const children = Array.from(wrapper.children) as HTMLElement[]; + children.forEach(child => child.classList.add("hero-glitch-child")); + + // After glitch animation, transition to void phase const timeout = setTimeout(() => { - window.location.href = "/enlighten"; + children.forEach(child => child.classList.remove("hero-glitch-child")); + style.remove(); + setPhase("void"); }, 3000); + return () => { clearTimeout(timeout); - document.documentElement.style.animation = ""; + children.forEach(child => child.classList.remove("hero-glitch-child")); style.remove(); }; }, [phase]); @@ -567,8 +595,16 @@ export default function Hero() { const baseOptions = { delay: 35, deleteSpeed: 35, cursor: "|" }; + if (phase === "void") { + return ( + }> + + + ); + } + if (phase === "glitch") { - return
; + return
; } if (phase === "countdown") { diff --git a/src/components/void/index.tsx b/src/components/void/index.tsx new file mode 100644 index 0000000..b1edd3e --- /dev/null +++ b/src/components/void/index.tsx @@ -0,0 +1,177 @@ +import { useState, useCallback, useEffect, useRef } from "react"; +import { Canvas } from "@react-three/fiber"; +import VoidTypewriter from "./typewriter"; +import VoidWater from "./scenes/void-water"; + +const GLITCH_CSS = ` + .void-glitch-subtle { + animation: void-glitch-subtle 2s ease-in-out infinite; + } + .void-glitch-intense { + animation: void-glitch-intense 1.2s ease-in-out infinite; + } + .void-glitch-dissolve { + animation: void-glitch-dissolve 2s ease-in forwards; + } + + @keyframes void-glitch-subtle { + 0%, 100% { transform: none; filter: none; } + 3% { transform: skewX(0.5deg); filter: hue-rotate(15deg); } + 6% { transform: none; filter: none; } + 15% { transform: translateX(1px) skewX(-0.2deg); } + 17% { transform: none; } + 30% { transform: skewX(-0.3deg) translateY(0.5px); filter: saturate(1.5); } + 32% { transform: none; filter: none; } + 50% { transform: translateY(-1px); } + 52% { transform: none; } + 70% { transform: skewX(0.2deg) translateX(-0.5px); filter: hue-rotate(-10deg); } + 72% { transform: none; filter: none; } + 85% { transform: translateX(-1px) skewY(0.1deg); } + 87% { transform: none; } + } + + @keyframes void-glitch-intense { + 0%, 100% { transform: none; filter: none; } + 2% { transform: skewX(2deg) translateX(2px); filter: hue-rotate(60deg) saturate(3); } + 5% { transform: skewX(-1.5deg) translateY(-1px); filter: none; } + 8% { transform: none; } + 12% { transform: translateY(-3px) skewX(0.5deg); filter: hue-rotate(-90deg); } + 15% { transform: none; filter: none; } + 25% { transform: skewX(1.5deg) scale(1.005) translateX(-2px); filter: saturate(4); } + 28% { transform: none; filter: none; } + 40% { transform: skewX(-2deg) translateY(2px); filter: hue-rotate(120deg) saturate(2); } + 42% { transform: none; filter: none; } + 55% { transform: translateX(-3px) skewY(0.3deg); } + 58% { transform: none; } + 70% { transform: scale(1.01) skewX(1deg); filter: hue-rotate(-45deg) saturate(3); } + 73% { transform: none; filter: none; } + 85% { transform: skewX(-1deg) translateX(2px) translateY(-1px); filter: saturate(5); } + 88% { transform: none; filter: none; } + } + + @keyframes void-glitch-dissolve { + 0% { transform: none; filter: none; opacity: 1; } + 3% { transform: skewX(3deg) translateX(4px); filter: hue-rotate(90deg) saturate(4); } + 6% { transform: skewX(-2deg) translateY(-3px); opacity: 0.95; } + 10% { filter: hue-rotate(-120deg) saturate(3); opacity: 0.9; } + 15% { transform: translateX(-5px) skewX(2deg); filter: none; opacity: 0.85; } + 20% { transform: skewX(-3deg) scale(1.02); filter: hue-rotate(180deg) saturate(5); opacity: 0.8; } + 25% { transform: translateY(4px) skewX(1deg); opacity: 0.75; } + 30% { filter: saturate(6) hue-rotate(60deg); opacity: 0.7; } + 40% { transform: skewX(2deg) translateX(-3px); filter: hue-rotate(-90deg); opacity: 0.55; } + 50% { transform: skewX(-4deg) translateY(2px); filter: saturate(3); opacity: 0.4; } + 60% { filter: hue-rotate(150deg); opacity: 0.3; } + 70% { transform: scale(1.03) skewX(2deg); opacity: 0.2; } + 80% { transform: translateX(-2px); opacity: 0.1; } + 100% { transform: none; filter: none; opacity: 0; } + } +`; + +function getCorruption(segment: number): number { + if (segment < 8) return 0; + if (segment === 8) return 0.05; + if (segment === 9) return 0.08; + if (segment === 10) return 0.1; + if (segment === 11) return 0.13; + if (segment === 12) return 0.1; // "anyway" — dips + if (segment === 13) return 0.3; + if (segment === 14) return 0.6; + if (segment === 15) return 0.75; + if (segment === 16) return 0.9; + return 1.0; +} + +function getGlitchClass(segment: number, dissolving: boolean): string { + if (dissolving) return "void-glitch-dissolve"; + if (segment < 8) return ""; + if (segment <= 14) return "void-glitch-subtle"; + return "void-glitch-intense"; +} + +interface VoidExperienceProps { + token: string; +} + +export default function VoidExperience({ token }: VoidExperienceProps) { + const [activeSegment, setActiveSegment] = useState(0); + const [visitCount, setVisitCount] = useState(null); + const [dissolving, setDissolving] = useState(false); + + // Inject CSS once + useEffect(() => { + const style = document.createElement("style"); + style.textContent = GLITCH_CSS; + document.head.appendChild(style); + // Hide cursor during void experience + document.body.style.cursor = "none"; + return () => { + style.remove(); + document.body.style.cursor = ""; + }; + }, []); + + // Fetch + increment visit count on mount (with token verification) + useEffect(() => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); + + fetch("/api/void-visits", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ token }), + signal: controller.signal, + }) + .then(r => r.json()) + .then(data => setVisitCount(data.count ?? 1)) + .catch(() => setVisitCount(1)) + .finally(() => clearTimeout(timeout)); + + return () => { + controller.abort(); + clearTimeout(timeout); + }; + }, []); + + const handlePhaseComplete = useCallback(() => { + setDissolving(true); + setTimeout(() => { + window.location.href = "/about"; + }, 2000); + }, []); + + const handleSegmentChange = useCallback((index: number) => { + setActiveSegment(index); + }, []); + + const corruption = getCorruption(activeSegment); + const glitchClass = getGlitchClass(activeSegment, dissolving); + + return ( +
+ {/* 3D Canvas — filter applied directly to this element, not a parent */} +
+ + + +
+ + {/* Typewriter — same glitch class applied directly */} + {visitCount !== null && ( +
+ +
+ )} +
+ ); +} diff --git a/src/components/void/palette.ts b/src/components/void/palette.ts new file mode 100644 index 0000000..d2243e1 --- /dev/null +++ b/src/components/void/palette.ts @@ -0,0 +1,15 @@ +export const VOID = { + bg: "#000000", + text: "#FFFFFF", + red: "#CC2420", + dim: "#BDAE93", + gold: "#D79921", +} as const; + +export const VOID_RGB = { + bg: [0, 0, 0] as const, + text: [1, 1, 1] as const, + red: [0.8, 0.14, 0.13] as const, + dim: [0.74, 0.68, 0.58] as const, + gold: [0.84, 0.6, 0.13] as const, +}; diff --git a/src/components/void/phases/index.ts b/src/components/void/phases/index.ts new file mode 100644 index 0000000..07609d0 --- /dev/null +++ b/src/components/void/phases/index.ts @@ -0,0 +1,8 @@ +import type { Phase } from "../types"; +import { addVoidPhase, VOID_SEGMENT_COUNT } from "./void"; + +export { addVoidPhase }; + +export const PHASE_SEGMENT_COUNTS: Record = { + void: VOID_SEGMENT_COUNT, +}; diff --git a/src/components/void/phases/void.ts b/src/components/void/phases/void.ts new file mode 100644 index 0000000..81fa8e6 --- /dev/null +++ b/src/components/void/phases/void.ts @@ -0,0 +1,132 @@ +import type { TypewriterInstance, Segment } from "../types"; +import { buildSegments, T1 } from "../types"; +import { VOID } from "../palette"; + +export function createVoidSegments(visitCount: number): Segment[] { + return [ + // 0 + { + html: `so this is it`, + pause: 3500, + delay: T1, + }, + // 1 + { + html: `the void`, + pause: 4000, + delay: T1, + }, + // 2 + { + html: `not much here`, + pause: 3000, + }, + // 3 + { + html: `just dark water`, + pause: 4000, + delay: T1, + }, + // 4 + { + html: `you sat through the whole thing though`, + pause: 3500, + prePause: 1500, + }, + // 5 + { + html: `the countdown and everything`, + pause: 3000, + }, + // 6 + { + html: `imagine if you took that energy`, + pause: 3000, + prePause: 1500, + }, + // 7 + { + html: `and pointed it at something that matters`, + pause: 3500, + delay: T1, + }, + // 8 — the line that lands + { + html: `you'd be dangerous`, + pause: 4500, + delay: T1, + prePause: 1000, + }, + // 9 + { + html: `seriously`, + pause: 2500, + delay: T1, + prePause: 1500, + }, + // 10 + { + html: `don't waste that potential`, + pause: 3000, + }, + // 11 + { + html: `go build something cool`, + pause: 4000, + delay: T1, + }, + // 12 — deflection + { + html: `anyway`, + pause: 3000, + delay: T1, + prePause: 2000, + }, + // 13 — visitor count (corruption picks up) + { + html: `you're visitor #${Math.max(visitCount, 1)}`, + pause: 4000, + delay: T1, + prePause: 1500, + }, + // 14 — unstable + { + html: `this void is pretty unstable though`, + pause: 3000, + prePause: 1000, + }, + // 15 — resigned + { + html: `ah well`, + pause: 2500, + delay: T1, + prePause: 1000, + }, + // 16 — goodbye + { + html: `it's been nice knowing ya`, + pause: 2500, + delay: T1, + }, + // 17 — cut off, void wins + { + html: `see you on the other si`, + pause: 500, + delay: T1, + deleteMode: "none", + }, + ]; +} + +export const VOID_SEGMENT_COUNT = createVoidSegments(0).length; + +export function addVoidPhase( + tw: TypewriterInstance, + onComplete: () => void, + startSegment: number = 0, + onSegmentChange?: (index: number) => void, + visitCount: number = 0, +) { + const segments = createVoidSegments(visitCount); + buildSegments(tw, segments, onComplete, startSegment, 4000, onSegmentChange); +} diff --git a/src/components/void/scenes/void-water.tsx b/src/components/void/scenes/void-water.tsx new file mode 100644 index 0000000..4037f44 --- /dev/null +++ b/src/components/void/scenes/void-water.tsx @@ -0,0 +1,171 @@ +import { useRef, useMemo } from "react"; +import { useFrame } from "@react-three/fiber"; +import * as THREE from "three"; +import { SIMPLEX_3D, PLANE_VERT } from "../shaders/noise"; + +interface VoidWaterProps { + segment: number; + corruption: number; // 0-1, drives RGB split + color noise +} + +const waterFrag = ` +${SIMPLEX_3D} + +uniform float uTime; +uniform float uOpacity; +uniform float uCorruption; +varying vec2 vUv; + +// Sample the water height field — broad, slow waves +float waterHeight(vec2 p) { + float t = uTime; + + // Large primary waves — slow, dominant + float h = snoise(vec3(p * 0.4, t * 0.08)) * 0.6; + // Medium secondary swell — different direction via offset + h += snoise(vec3(p.yx * 0.7 + 2.0, t * 0.12)) * 0.3; + // Small surface detail + h += snoise(vec3(p * 1.5 + 5.0, t * 0.2)) * 0.1; + + return h; +} + +// Compute lighting for a given UV position +float computeLight(vec2 p) { + float eps = 0.08; + float h = waterHeight(p); + float hx = waterHeight(p + vec2(eps, 0.0)); + float hy = waterHeight(p + vec2(0.0, eps)); + + vec3 normal = normalize(vec3( + (h - hx) / eps * 2.0, + (h - hy) / eps * 2.0, + 1.0 + )); + + vec3 viewDir = vec3(0.0, 0.0, 1.0); + vec3 lightDir = normalize(vec3(0.4, 0.3, 1.0)); + vec3 halfDir = normalize(lightDir + viewDir); + + float diffuse = max(dot(normal, lightDir), 0.0); + float spec1 = pow(max(dot(normal, halfDir), 0.0), 12.0); + float spec2 = pow(max(dot(normal, halfDir), 0.0), 40.0); + float tilt = 1.0 - normal.z; + + return tilt * 0.12 + diffuse * 0.2 + spec1 * 0.5 + spec2 * 0.6; +} + +void main() { + vec2 p = (vUv - 0.5) * 4.0; + + // Circular vignette + float dist = length(vUv - 0.5) * 2.0; + float vignette = 1.0 - smoothstep(0.5, 1.0, dist); + + if (uCorruption < 0.01) { + // Clean path — original water + float light = computeLight(p); + float intensity = light * vignette * uOpacity; + vec3 color = vec3(0.3, 0.38, 0.5) * intensity; + gl_FragColor = vec4(color, intensity); + } else { + // Corrupted path — RGB channel separation + color noise + + // Chromatic offset increases with corruption + float offset = uCorruption * 0.15; + + // Sample lighting at offset positions for each channel + float lightR = computeLight(p + vec2(offset, offset * 0.5)); + float lightG = computeLight(p); + float lightB = computeLight(p - vec2(offset * 0.7, offset)); + + // Base water color per channel + vec3 baseColor = vec3(0.3, 0.38, 0.5); + float r = lightR * baseColor.r; + float g = lightG * baseColor.g; + float b = lightB * baseColor.b; + + // Color static — high-frequency noise injecting random color + float staticR = snoise(vec3(vUv * 80.0, uTime * 3.0)) * 0.5 + 0.5; + float staticG = snoise(vec3(vUv * 80.0 + 50.0, uTime * 3.5)) * 0.5 + 0.5; + float staticB = snoise(vec3(vUv * 80.0 + 100.0, uTime * 4.0)) * 0.5 + 0.5; + + float staticMix = uCorruption * 0.3; + r = mix(r, staticR * 0.4, staticMix); + g = mix(g, staticG * 0.3, staticMix); + b = mix(b, staticB * 0.5, staticMix); + + // Scan line glitch — horizontal bands that flicker + float scanline = step(0.92, snoise(vec3(0.0, vUv.y * 40.0, uTime * 5.0))); + r += scanline * uCorruption * 0.15; + + float avgLight = (lightR + lightG + lightB) / 3.0; + float intensity = avgLight * vignette * uOpacity; + + gl_FragColor = vec4(vec3(r, g, b) * vignette * uOpacity, intensity); + } +} +`; + +function getOpacityTarget(segment: number): number { + if (segment < 2) return 0; + if (segment === 2) return 0.5; + if (segment === 3) return 0.7; + if (segment === 4) return 0.85; + return 1.0; +} + +export default function VoidWater({ segment, corruption }: VoidWaterProps) { + const meshRef = useRef(null!); + const opacityRef = useRef(0); + const corruptionRef = useRef(0); + + const uniforms = useMemo(() => ({ + uTime: { value: 0 }, + uOpacity: { value: 0 }, + uCorruption: { value: 0 }, + }), []); + + const material = useMemo(() => new THREE.ShaderMaterial({ + vertexShader: PLANE_VERT, + fragmentShader: waterFrag, + uniforms, + transparent: true, + depthWrite: false, + blending: THREE.AdditiveBlending, + }), [uniforms]); + + useFrame((state, delta) => { + const target = getOpacityTarget(segment); + opacityRef.current = THREE.MathUtils.lerp(opacityRef.current, target, delta * 0.4); + corruptionRef.current = THREE.MathUtils.lerp(corruptionRef.current, corruption, delta * 2.0); + + const mesh = meshRef.current; + if (!mesh) return; + + if (opacityRef.current < 0.001) { + mesh.visible = false; + return; + } + + mesh.visible = true; + const t = state.clock.elapsedTime; + uniforms.uTime.value = t; + + // Gentle pulse — slow breathing modulation on opacity + const pulse = 1.0 + Math.sin(t * 0.4) * 0.08 + Math.sin(t * 0.7) * 0.04; + uniforms.uOpacity.value = opacityRef.current * pulse; + uniforms.uCorruption.value = corruptionRef.current; + }); + + return ( + + + + ); +} diff --git a/src/components/void/shaders/noise.ts b/src/components/void/shaders/noise.ts new file mode 100644 index 0000000..4372843 --- /dev/null +++ b/src/components/void/shaders/noise.ts @@ -0,0 +1,75 @@ +// Shared GLSL noise functions for void experience shaders +// 3D Simplex noise (Ashima Arts / Stefan Gustavson, MIT) + +export const SIMPLEX_3D = ` +vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } +vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } +vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); } +vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } + +float snoise(vec3 v) { + const vec2 C = vec2(1.0/6.0, 1.0/3.0); + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + + vec3 i = floor(v + dot(v, C.yyy)); + vec3 x0 = v - i + dot(i, C.xxx); + + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min(g.xyz, l.zxy); + vec3 i2 = max(g.xyz, l.zxy); + + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; + vec3 x3 = x0 - D.yyy; + + i = mod289(i); + vec4 p = permute(permute(permute( + i.z + vec4(0.0, i1.z, i2.z, 1.0)) + + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + + i.x + vec4(0.0, i1.x, i2.x, 1.0)); + + float n_ = 0.142857142857; + vec3 ns = n_ * D.wyz - D.xzx; + + vec4 j = p - 49.0 * floor(p * ns.z * ns.z); + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_); + + vec4 x = x_ * ns.x + ns.yyyy; + vec4 y = y_ * ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4(x.xy, y.xy); + vec4 b1 = vec4(x.zw, y.zw); + + vec4 s0 = floor(b0) * 2.0 + 1.0; + vec4 s1 = floor(b1) * 2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; + + vec3 p0 = vec3(a0.xy, h.x); + vec3 p1 = vec3(a0.zw, h.y); + vec3 p2 = vec3(a1.xy, h.z); + vec3 p3 = vec3(a1.zw, h.w); + + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3))); + p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w; + + vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); + m = m * m; + return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3))); +} +`; + +// Standard passthrough vertex shader used by all scene planes +export const PLANE_VERT = ` +varying vec2 vUv; + +void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +} +`; diff --git a/src/components/void/types.ts b/src/components/void/types.ts new file mode 100644 index 0000000..7ef355f --- /dev/null +++ b/src/components/void/types.ts @@ -0,0 +1,83 @@ +export interface TypewriterInstance { + typeString: (str: string) => TypewriterInstance; + pasteString: (str: string, node?: HTMLElement | null) => TypewriterInstance; + pauseFor: (ms: number) => TypewriterInstance; + deleteAll: (speed?: number | "natural") => TypewriterInstance; + deleteChars: (amount: number) => TypewriterInstance; + changeDelay: (delay: number | "natural") => TypewriterInstance; + changeDeleteSpeed: (speed: number | "natural") => TypewriterInstance; + callFunction: (cb: () => void) => TypewriterInstance; + start: () => TypewriterInstance; +} + +export type Phase = "void"; + +export const PHASE_ORDER: Phase[] = ["void"]; + +export const T1 = 55; +export const T2 = 35; +export const DELETE_SPEED = 15; + +export interface Segment { + html: string; + pause: number; + method?: "type" | "paste"; + delay?: number; + prePause?: number; + deleteMode?: "all" | "none"; + deleteSpeed?: number; +} + +export type PhaseBuilder = ( + tw: TypewriterInstance, + onComplete: () => void, + startSegment?: number, + onSegmentChange?: (index: number) => void, +) => void; + +export function buildSegments( + tw: TypewriterInstance, + segments: Segment[], + onComplete: () => void, + startSegment: number = 0, + initialPause: number = 0, + onSegmentChange?: (index: number) => void, +) { + if (startSegment === 0 && initialPause > 0) { + tw.pauseFor(initialPause); + } + + for (let i = startSegment; i < segments.length; i++) { + const seg = segments[i]; + const idx = i; + + tw.callFunction(() => onSegmentChange?.(idx)); + + if (seg.prePause && seg.prePause > 0) { + tw.pauseFor(seg.prePause); + } + + if (seg.delay !== undefined) { + tw.changeDelay(seg.delay); + } + + if (seg.method === "paste") { + tw.pasteString(seg.html, null); + } else { + tw.typeString(seg.html); + } + + tw.pauseFor(seg.pause); + + if (seg.delay !== undefined) { + tw.changeDelay(T2); + } + + const mode = seg.deleteMode ?? "all"; + if (mode === "all") { + tw.deleteAll(seg.deleteSpeed ?? DELETE_SPEED); + } + } + + tw.callFunction(onComplete); +} diff --git a/src/components/void/typewriter.tsx b/src/components/void/typewriter.tsx new file mode 100644 index 0000000..8ef4c26 --- /dev/null +++ b/src/components/void/typewriter.tsx @@ -0,0 +1,104 @@ +import { useRef, useEffect } from "react"; +import Typewriter from "typewriter-effect"; +import type { TypewriterInstance } from "./types"; +import { addVoidPhase } from "./phases"; + +const GLITCH_CHARS = "!<>-_\\/[]{}—=+*^?#________"; + +interface VoidTypewriterProps { + startSegment: number; + onPhaseComplete: () => void; + onSegmentChange: (index: number) => void; + visitCount: number; + corruption: number; +} + +function getTextNodes(node: Node): Text[] { + const nodes: Text[] = []; + const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT); + let current: Node | null; + while ((current = walker.nextNode())) { + if (current.textContent && current.textContent.trim().length > 0) { + nodes.push(current as Text); + } + } + return nodes; +} + +export default function VoidTypewriter({ startSegment, onPhaseComplete, onSegmentChange, visitCount, corruption }: VoidTypewriterProps) { + const containerRef = useRef(null); + const corruptionRef = useRef(corruption); + corruptionRef.current = corruption; + + const handleInit = (tw: TypewriterInstance): void => { + addVoidPhase(tw, onPhaseComplete, startSegment, onSegmentChange, visitCount); + tw.start(); + }; + + // 404-style character replacement glitch — intensity scales with corruption + useEffect(() => { + const pendingResets: ReturnType[] = []; + + const interval = setInterval(() => { + const c = corruptionRef.current; + if (c <= 0 || !containerRef.current) return; + + const triggerChance = c * 0.4; + if (Math.random() > triggerChance) return; + + const textNodes = getTextNodes(containerRef.current); + if (textNodes.length === 0) return; + + const originals = textNodes.map(n => n.textContent || ""); + const charChance = c * 0.4; + + textNodes.forEach((node, i) => { + const text = originals[i]; + const glitched = text.split("").map(char => { + if (char === " ") return char; + if (Math.random() < charChance) { + return GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)]; + } + return char; + }).join(""); + node.textContent = glitched; + }); + + const resetMs = Math.max(40, 120 - c * 80); + const id = setTimeout(() => { + textNodes.forEach((node, i) => { + if (node.parentNode) { + node.textContent = originals[i]; + } + }); + }, resetMs); + pendingResets.push(id); + }, 60); + + return () => { + clearInterval(interval); + pendingResets.forEach(clearTimeout); + }; + }, []); + + return ( +
+
+ +
+
+ ); +} diff --git a/src/pages/api/void-token.ts b/src/pages/api/void-token.ts new file mode 100644 index 0000000..797aa55 --- /dev/null +++ b/src/pages/api/void-token.ts @@ -0,0 +1,25 @@ +import type { APIRoute } from "astro"; + +const SECRET = import.meta.env.VOID_SECRET || process.env.VOID_SECRET; + +async function sign(timestamp: string): Promise { + const data = new TextEncoder().encode(timestamp + SECRET); + const hash = await crypto.subtle.digest("SHA-256", data); + return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, "0")).join(""); +} + +export const GET: APIRoute = async () => { + if (!SECRET) { + return new Response(JSON.stringify({ token: "dev" }), { + headers: { "Content-Type": "application/json" }, + }); + } + + const timestamp = Date.now().toString(); + const signature = await sign(timestamp); + const token = `${timestamp}:${signature}`; + + return new Response(JSON.stringify({ token }), { + headers: { "Content-Type": "application/json" }, + }); +}; diff --git a/src/pages/api/void-visits.ts b/src/pages/api/void-visits.ts new file mode 100644 index 0000000..cf8478a --- /dev/null +++ b/src/pages/api/void-visits.ts @@ -0,0 +1,114 @@ +import type { APIRoute } from "astro"; +import Redis from "ioredis"; + +const SECRET = import.meta.env.VOID_SECRET || process.env.VOID_SECRET; +const TOKEN_WINDOW_MS = 10 * 60 * 1000; // 10 minutes + +let redis: Redis | null = null; + +function getRedis(): Redis | null { + if (redis) return redis; + const url = import.meta.env.REDIS_URL || process.env.REDIS_URL; + if (!url) return null; + redis = new Redis(url); + return redis; +} + +async function sign(timestamp: string): Promise { + const data = new TextEncoder().encode(timestamp + SECRET); + const hash = await crypto.subtle.digest("SHA-256", data); + return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, "0")).join(""); +} + +async function hashIp(ip: string): Promise { + const data = new TextEncoder().encode(ip + SECRET); + const hash = await crypto.subtle.digest("SHA-256", data); + return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, "0")).join("").slice(0, 32); +} + +function getClientIp(request: Request): string { + // x-vercel-forwarded-for is Vercel's trusted header (can't be spoofed) + // Fall back to last entry in x-forwarded-for (Vercel appends real IP at end) + return request.headers.get("x-vercel-forwarded-for") + || request.headers.get("x-forwarded-for")?.split(",").at(-1)?.trim() + || request.headers.get("x-real-ip") + || "unknown"; +} + +export const POST: APIRoute = async ({ request }) => { + const r = getRedis(); + + // No secret or no Redis — dev mode, return 1 + if (!SECRET || !r) { + return new Response(JSON.stringify({ count: 1 }), { + headers: { "Content-Type": "application/json" }, + }); + } + + // Parse body + let body: { token?: string } = {}; + try { body = await request.json(); } catch {} + + const token = body.token; + if (!token || token === "dev") { + return new Response(JSON.stringify({ error: "missing token" }), { status: 400 }); + } + + const [ts, sig] = token.split(":"); + if (!ts || !sig) { + return new Response(JSON.stringify({ error: "invalid token" }), { status: 400 }); + } + + // Check token age + const age = Date.now() - parseInt(ts, 10); + if (isNaN(age) || age < 0 || age > TOKEN_WINDOW_MS) { + return new Response(JSON.stringify({ error: "expired token" }), { status: 400 }); + } + + // Verify signature + const expected = await sign(ts); + if (sig !== expected) { + return new Response(JSON.stringify({ error: "invalid token" }), { status: 400 }); + } + + try { + // Atomic one-time-use check: SET NX returns "OK" only if key didn't exist + const tokenKey = `void:token:${sig.slice(0, 16)}`; + const isNew = await r.set(tokenKey, "pending", "EX", 600, "NX"); + + if (!isNew) { + // Token already used — return the stored count + const storedCount = await r.get(tokenKey); + const count = storedCount && storedCount !== "pending" ? parseInt(storedCount, 10) : 1; + return new Response(JSON.stringify({ count }), { + headers: { "Content-Type": "application/json" }, + }); + } + + // Check IP dedup + const ip = getClientIp(request); + const ipKey = `void:ip:${await hashIp(ip)}`; + const existingCount = await r.get(ipKey); + + let count: number; + if (existingCount) { + // Same IP — return their existing number + count = parseInt(existingCount, 10); + } else { + // New visitor — increment global counter + count = await r.incr("void:count"); + await r.set(ipKey, count.toString()); + } + + // Update token key with the actual count (for replay lookups) + await r.set(tokenKey, count.toString(), "EX", 600); + + return new Response(JSON.stringify({ count }), { + headers: { "Content-Type": "application/json" }, + }); + } catch { + return new Response(JSON.stringify({ count: 1 }), { + headers: { "Content-Type": "application/json" }, + }); + } +}; diff --git a/src/pages/enlighten.astro b/src/pages/enlighten.astro deleted file mode 100644 index a237370..0000000 --- a/src/pages/enlighten.astro +++ /dev/null @@ -1,17 +0,0 @@ ---- -export const prerender = false; -import "@/style/globals.css" -import Void from "@/components/hero/void"; ---- - - - - - - - ... - - - - - diff --git a/src/style/globals.css b/src/style/globals.css index 15f1cf2..6eca536 100644 --- a/src/style/globals.css +++ b/src/style/globals.css @@ -41,37 +41,6 @@ @apply bg-purple/50 } } -/* CRT overlay — canvas only */ -.crt-scanlines { - background: repeating-linear-gradient( - to bottom, - transparent 0px, - transparent 2px, - rgb(var(--color-foreground) / 0.06) 2px, - rgb(var(--color-foreground) / 0.06) 4px - ); - animation: crt-scroll 12s linear infinite; - z-index: 1; -} - -.crt-bloom { - box-shadow: inset 0 0 100px 30px rgb(var(--color-background) / 0.3); - background: radial-gradient( - ellipse at center, - transparent 50%, - rgb(var(--color-background) / 0.25) 100% - ); - z-index: 2; -} - -@keyframes crt-scroll { - 0% { - background-position: 0 0; - } - 100% { - background-position: 0 200px; - } -} @media (prefers-reduced-motion: reduce) { *, *::before, *::after {