feat: home page beauty
This commit is contained in:
parent
00f7b5b086
commit
d2773ffd14
24
components.json
Normal file
24
components.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"registries": {
|
||||
"@react-bits": "https://reactbits.dev/r/{name}.json"
|
||||
}
|
||||
}
|
||||
623
package-lock.json
generated
623
package-lock.json
generated
@ -8,20 +8,26 @@
|
||||
"name": "profit-planet-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@gsap/react": "^2.1.2",
|
||||
"@headlessui/react": "^2.2.9",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@lottiefiles/react-lottie-player": "^3.6.0",
|
||||
"@react-pdf/renderer": "^4.3.0",
|
||||
"@react-three/drei": "^10.7.7",
|
||||
"@react-three/fiber": "^9.5.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindplus/elements": "^1.0.15",
|
||||
"@tailwindui/react": "^0.1.1",
|
||||
"axios": "^1.12.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"country-flag-icons": "^1.5.21",
|
||||
"country-select-js": "^2.1.0",
|
||||
"gsap": "^3.14.2",
|
||||
"intl-tel-input": "^25.15.0",
|
||||
"lucide-react": "^0.562.0",
|
||||
"motion": "^12.23.22",
|
||||
"next": "^16.0.7",
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
@ -33,6 +39,9 @@
|
||||
"react-pdf": "^10.1.0",
|
||||
"react-phone-number-input": "^3.4.12",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"three": "^0.167.1",
|
||||
"winston": "^3.17.0",
|
||||
"yup": "^1.7.1",
|
||||
"zustand": "^5.0.8"
|
||||
@ -1634,6 +1643,12 @@
|
||||
"kuler": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dimforge/rapier3d-compat": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
||||
"integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz",
|
||||
@ -1874,6 +1889,16 @@
|
||||
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@gsap/react": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@gsap/react/-/react-2.1.2.tgz",
|
||||
"integrity": "sha512-JqliybO1837UcgH2hVOM4VO+38APk3ECNrsuSM4MuXp+rbf+/2IG2K1YJiqfTcXQHH7XlA0m3ykniFYstfq0Iw==",
|
||||
"license": "SEE LICENSE AT https://gsap.com/standard-license",
|
||||
"peerDependencies": {
|
||||
"gsap": "^3.12.5",
|
||||
"react": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@headlessui/react": {
|
||||
"version": "2.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz",
|
||||
@ -2465,6 +2490,24 @@
|
||||
"react": "16 - 19"
|
||||
}
|
||||
},
|
||||
"node_modules/@mediapipe/tasks-vision": {
|
||||
"version": "0.10.17",
|
||||
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
|
||||
"integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@monogrid/gainmap-js": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
|
||||
"integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"promise-worker-transferable": "^1.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">= 0.159.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas": {
|
||||
"version": "0.1.80",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz",
|
||||
@ -3117,6 +3160,95 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/drei": {
|
||||
"version": "10.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
|
||||
"integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@mediapipe/tasks-vision": "0.10.17",
|
||||
"@monogrid/gainmap-js": "^3.0.6",
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"camera-controls": "^3.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"detect-gpu": "^5.0.56",
|
||||
"glsl-noise": "^0.0.0",
|
||||
"hls.js": "^1.5.17",
|
||||
"maath": "^0.10.8",
|
||||
"meshline": "^3.3.1",
|
||||
"stats-gl": "^2.2.8",
|
||||
"stats.js": "^0.17.0",
|
||||
"suspend-react": "^0.1.3",
|
||||
"three-mesh-bvh": "^0.8.3",
|
||||
"three-stdlib": "^2.35.6",
|
||||
"troika-three-text": "^0.52.4",
|
||||
"tunnel-rat": "^0.1.2",
|
||||
"use-sync-external-store": "^1.4.0",
|
||||
"utility-types": "^3.11.0",
|
||||
"zustand": "^5.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-three/fiber": "^9.0.0",
|
||||
"react": "^19",
|
||||
"react-dom": "^19",
|
||||
"three": ">=0.159"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-three/fiber": {
|
||||
"version": "9.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz",
|
||||
"integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.8",
|
||||
"@types/webxr": "*",
|
||||
"base64-js": "^1.5.1",
|
||||
"buffer": "^6.0.3",
|
||||
"its-fine": "^2.0.0",
|
||||
"react-use-measure": "^2.1.7",
|
||||
"scheduler": "^0.27.0",
|
||||
"suspend-react": "^0.1.3",
|
||||
"use-sync-external-store": "^1.4.0",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": ">=43.0",
|
||||
"expo-asset": ">=8.4",
|
||||
"expo-file-system": ">=11.0",
|
||||
"expo-gl": ">=11.0",
|
||||
"react": ">=19 <19.3",
|
||||
"react-dom": ">=19 <19.3",
|
||||
"react-native": ">=0.78",
|
||||
"three": ">=0.156"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"expo": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-asset": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-file-system": {
|
||||
"optional": true
|
||||
},
|
||||
"expo-gl": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-types/shared": {
|
||||
"version": "3.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.0.tgz",
|
||||
@ -3523,6 +3655,12 @@
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tweenjs/tween.js": {
|
||||
"version": "23.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
@ -3534,6 +3672,12 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/draco3d": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
|
||||
"integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@ -3565,11 +3709,16 @@
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/offscreencanvas": {
|
||||
"version": "2019.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
|
||||
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.15.tgz",
|
||||
"integrity": "sha512-+kLxJpaJzXybyDyFXYADyP1cznTO8HSuBpenGlnKOAkH4hyNINiywvXS/tGJhsrGGP/gM185RA3xpjY0Yg4erA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@ -3586,12 +3735,49 @@
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-reconciler": {
|
||||
"version": "0.28.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
|
||||
"integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/stats.js": {
|
||||
"version": "0.17.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
|
||||
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.182.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.182.0.tgz",
|
||||
"integrity": "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dimforge/rapier3d-compat": "~0.12.0",
|
||||
"@tweenjs/tween.js": "~23.1.3",
|
||||
"@types/stats.js": "*",
|
||||
"@types/webxr": ">=0.5.17",
|
||||
"@webgpu/types": "*",
|
||||
"fflate": "~0.8.2",
|
||||
"meshoptimizer": "~0.22.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/triple-beam": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
|
||||
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
||||
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.44.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz",
|
||||
@ -4150,6 +4336,30 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@use-gesture/core": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
|
||||
"integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@use-gesture/react": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
|
||||
"integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@use-gesture/core": "10.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webgpu/types": {
|
||||
"version": "0.1.68",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.68.tgz",
|
||||
"integrity": "sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/abs-svg-path": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
|
||||
@ -4625,6 +4835,30 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||
@ -4684,6 +4918,19 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camera-controls": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz",
|
||||
"integrity": "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.0.0",
|
||||
"npm": ">=10.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.126.1"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001745",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz",
|
||||
@ -4731,6 +4978,18 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/class-variance-authority": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
||||
"integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://polar.sh/cva"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
@ -4873,11 +5132,28 @@
|
||||
"integrity": "sha512-T7gM2MT6S06lGqqkkBCmWFlyryKuaBgbeJFFxZttT+GT6pwl63r5KuLQszkfbtL9YEu+8JvrRayfvyrZd9I++g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
@ -5146,6 +5422,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-gpu": {
|
||||
"version": "5.0.70",
|
||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
|
||||
"integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"webgl-constants": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz",
|
||||
@ -5175,6 +5460,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/draco3d": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
|
||||
"integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@ -5926,6 +6217,12 @@
|
||||
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@ -6277,6 +6574,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/glsl-noise": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
|
||||
"integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/goober": {
|
||||
"version": "2.1.16",
|
||||
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
|
||||
@ -6312,6 +6615,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gsap": {
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.14.2.tgz",
|
||||
"integrity": "sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA==",
|
||||
"license": "Standard 'no charge' license: https://gsap.com/standard-license.",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@ -6403,6 +6713,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hls.js": {
|
||||
"version": "1.6.15",
|
||||
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.15.tgz",
|
||||
"integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/hsl-to-hex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz",
|
||||
@ -6424,6 +6740,26 @@
|
||||
"integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@ -6434,6 +6770,12 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
@ -6771,6 +7113,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
|
||||
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||
@ -6945,7 +7293,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/iterator.prototype": {
|
||||
@ -6966,6 +7313,18 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/its-fine": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
|
||||
"integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react-reconciler": "^0.28.9"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jay-peg": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz",
|
||||
@ -7122,6 +7481,15 @@
|
||||
"integrity": "sha512-RN3q3gImZ91BvRDYjWp7ICz3gRn81mW5L4SW+2afzNCC0I/nkXstBgZThQGTE3S/9q5J90FH4dP+TXx8NhdZKg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lie": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
||||
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immediate": "~3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.30.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
||||
@ -7453,6 +7821,25 @@
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.562.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
|
||||
"integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/maath": {
|
||||
"version": "0.10.8",
|
||||
"resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
|
||||
"integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/three": ">=0.134.0",
|
||||
"three": ">=0.134.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.19",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
|
||||
@ -7523,6 +7910,21 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/meshline": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
|
||||
"integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">=0.137"
|
||||
}
|
||||
},
|
||||
"node_modules/meshoptimizer": {
|
||||
"version": "0.22.0",
|
||||
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz",
|
||||
"integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
@ -8051,7 +8453,6 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -8859,6 +9260,12 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/potpack": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
|
||||
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@ -8869,6 +9276,16 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/promise-worker-transferable": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
|
||||
"integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"is-promise": "^2.1.0",
|
||||
"lie": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
@ -9080,6 +9497,21 @@
|
||||
"react-dom": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-use-measure": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
|
||||
"integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13",
|
||||
"react-dom": ">=16.13"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@ -9428,7 +9860,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
@ -9441,7 +9872,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -9557,6 +9987,32 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/stats-gl": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
|
||||
"integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/three": "*",
|
||||
"three": "^0.170.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/three": "*",
|
||||
"three": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/stats-gl/node_modules/three": {
|
||||
"version": "0.170.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
|
||||
"integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stats.js": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
|
||||
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stop-iteration-iterator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
||||
@ -9765,6 +10221,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/suspend-react": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
|
||||
"integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-arc-to-cubic-bezier": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
|
||||
@ -9777,6 +10242,16 @@
|
||||
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
|
||||
"integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.13",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
|
||||
@ -9784,6 +10259,15 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
|
||||
"integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
|
||||
@ -9821,6 +10305,45 @@
|
||||
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.167.1",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.167.1.tgz",
|
||||
"integrity": "sha512-gYTLJA/UQip6J/tJvl91YYqlZF47+D/kxiWrbTon35ZHlXEN0VOo+Qke2walF1/x92v55H6enomymg4Dak52kw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/three-mesh-bvh": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz",
|
||||
"integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">= 0.159.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-stdlib": {
|
||||
"version": "2.36.1",
|
||||
"resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
|
||||
"integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/draco3d": "^1.4.0",
|
||||
"@types/offscreencanvas": "^2019.6.4",
|
||||
"@types/webxr": "^0.5.2",
|
||||
"draco3d": "^1.4.1",
|
||||
"fflate": "^0.6.9",
|
||||
"potpack": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.128.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-stdlib/node_modules/fflate": {
|
||||
"version": "0.6.10",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
|
||||
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-case": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
|
||||
@ -9916,6 +10439,36 @@
|
||||
"node": ">= 14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-three-text": {
|
||||
"version": "0.52.4",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
|
||||
"integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bidi-js": "^1.0.2",
|
||||
"troika-three-utils": "^0.52.4",
|
||||
"troika-worker-utils": "^0.52.0",
|
||||
"webgl-sdf-generator": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-three-utils": {
|
||||
"version": "0.52.4",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz",
|
||||
"integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-worker-utils": {
|
||||
"version": "0.52.0",
|
||||
"resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
|
||||
"integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
||||
@ -9948,6 +10501,43 @@
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-rat": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
|
||||
"integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"zustand": "^4.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-rat/node_modules/zustand": {
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
||||
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "^1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@ -10208,6 +10798,15 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utility-types": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
|
||||
"integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-compatible-readable-stream": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz",
|
||||
@ -10231,11 +10830,21 @@
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webgl-constants": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
|
||||
"integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
|
||||
},
|
||||
"node_modules/webgl-sdf-generator": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
|
||||
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
|
||||
@ -9,20 +9,26 @@
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gsap/react": "^2.1.2",
|
||||
"@headlessui/react": "^2.2.9",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@lottiefiles/react-lottie-player": "^3.6.0",
|
||||
"@react-pdf/renderer": "^4.3.0",
|
||||
"@react-three/drei": "^10.7.7",
|
||||
"@react-three/fiber": "^9.5.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindplus/elements": "^1.0.15",
|
||||
"@tailwindui/react": "^0.1.1",
|
||||
"axios": "^1.12.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"country-flag-icons": "^1.5.21",
|
||||
"country-select-js": "^2.1.0",
|
||||
"gsap": "^3.14.2",
|
||||
"intl-tel-input": "^25.15.0",
|
||||
"lucide-react": "^0.562.0",
|
||||
"motion": "^12.23.22",
|
||||
"next": "^16.0.7",
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
@ -34,6 +40,9 @@
|
||||
"react-pdf": "^10.1.0",
|
||||
"react-phone-number-input": "^3.4.12",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"three": "^0.167.1",
|
||||
"winston": "^3.17.0",
|
||||
"yup": "^1.7.1",
|
||||
"zustand": "^5.0.8"
|
||||
|
||||
372
src/app/components/Beams.tsx
Normal file
372
src/app/components/Beams.tsx
Normal file
@ -0,0 +1,372 @@
|
||||
import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo, FC, ReactNode } from 'react';
|
||||
|
||||
import * as THREE from 'three';
|
||||
|
||||
import { Canvas, useFrame } from '@react-three/fiber';
|
||||
import { PerspectiveCamera } from '@react-three/drei';
|
||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||
|
||||
type UniformValue = THREE.IUniform<unknown> | unknown;
|
||||
|
||||
interface ExtendMaterialConfig {
|
||||
header: string;
|
||||
vertexHeader?: string;
|
||||
fragmentHeader?: string;
|
||||
material?: THREE.MeshPhysicalMaterialParameters & { fog?: boolean };
|
||||
uniforms?: Record<string, UniformValue>;
|
||||
vertex?: Record<string, string>;
|
||||
fragment?: Record<string, string>;
|
||||
}
|
||||
|
||||
type ShaderWithDefines = THREE.ShaderLibShader & {
|
||||
defines?: Record<string, string | number | boolean>;
|
||||
};
|
||||
|
||||
function extendMaterial<T extends THREE.Material = THREE.Material>(
|
||||
BaseMaterial: new (params?: THREE.MaterialParameters) => T,
|
||||
cfg: ExtendMaterialConfig
|
||||
): THREE.ShaderMaterial {
|
||||
const physical = THREE.ShaderLib.physical as ShaderWithDefines;
|
||||
const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;
|
||||
const baseDefines = physical.defines ?? {};
|
||||
|
||||
const uniforms: Record<string, THREE.IUniform> = THREE.UniformsUtils.clone(baseUniforms);
|
||||
|
||||
const defaults = new BaseMaterial(cfg.material || {}) as T & {
|
||||
color?: THREE.Color;
|
||||
roughness?: number;
|
||||
metalness?: number;
|
||||
envMap?: THREE.Texture;
|
||||
envMapIntensity?: number;
|
||||
};
|
||||
|
||||
if (defaults.color) uniforms.diffuse.value = defaults.color;
|
||||
if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;
|
||||
if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;
|
||||
if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;
|
||||
if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;
|
||||
|
||||
Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {
|
||||
uniforms[key] =
|
||||
u !== null && typeof u === 'object' && 'value' in u
|
||||
? (u as THREE.IUniform<unknown>)
|
||||
: ({ value: u } as THREE.IUniform<unknown>);
|
||||
});
|
||||
|
||||
let vert = `${cfg.header}\n${cfg.vertexHeader ?? ''}\n${baseVert}`;
|
||||
let frag = `${cfg.header}\n${cfg.fragmentHeader ?? ''}\n${baseFrag}`;
|
||||
|
||||
for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {
|
||||
vert = vert.replace(inc, `${inc}\n${code}`);
|
||||
}
|
||||
for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {
|
||||
frag = frag.replace(inc, `${inc}\n${code}`);
|
||||
}
|
||||
|
||||
const mat = new THREE.ShaderMaterial({
|
||||
defines: { ...baseDefines },
|
||||
uniforms,
|
||||
vertexShader: vert,
|
||||
fragmentShader: frag,
|
||||
lights: true,
|
||||
fog: !!cfg.material?.fog
|
||||
});
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
const CanvasWrapper: FC<{ children: ReactNode }> = ({ children }) => (
|
||||
<Canvas dpr={[1, 2]} frameloop="always" className="w-full h-full relative">
|
||||
{children}
|
||||
</Canvas>
|
||||
);
|
||||
|
||||
const hexToNormalizedRGB = (hex: string): [number, number, number] => {
|
||||
const clean = hex.replace('#', '');
|
||||
const r = parseInt(clean.substring(0, 2), 16);
|
||||
const g = parseInt(clean.substring(2, 4), 16);
|
||||
const b = parseInt(clean.substring(4, 6), 16);
|
||||
return [r / 255, g / 255, b / 255];
|
||||
};
|
||||
|
||||
const noise = `
|
||||
float random (in vec2 st) {
|
||||
return fract(sin(dot(st.xy,
|
||||
vec2(12.9898,78.233)))*
|
||||
43758.5453123);
|
||||
}
|
||||
float noise (in vec2 st) {
|
||||
vec2 i = floor(st);
|
||||
vec2 f = fract(st);
|
||||
float a = random(i);
|
||||
float b = random(i + vec2(1.0, 0.0));
|
||||
float c = random(i + vec2(0.0, 1.0));
|
||||
float d = random(i + vec2(1.0, 1.0));
|
||||
vec2 u = f * f * (3.0 - 2.0 * f);
|
||||
return mix(a, b, u.x) +
|
||||
(c - a)* u.y * (1.0 - u.x) +
|
||||
(d - b) * u.x * u.y;
|
||||
}
|
||||
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
|
||||
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
|
||||
vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
|
||||
float cnoise(vec3 P){
|
||||
vec3 Pi0 = floor(P);
|
||||
vec3 Pi1 = Pi0 + vec3(1.0);
|
||||
Pi0 = mod(Pi0, 289.0);
|
||||
Pi1 = mod(Pi1, 289.0);
|
||||
vec3 Pf0 = fract(P);
|
||||
vec3 Pf1 = Pf0 - vec3(1.0);
|
||||
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
||||
vec4 iy = vec4(Pi0.yy, Pi1.yy);
|
||||
vec4 iz0 = Pi0.zzzz;
|
||||
vec4 iz1 = Pi1.zzzz;
|
||||
vec4 ixy = permute(permute(ix) + iy);
|
||||
vec4 ixy0 = permute(ixy + iz0);
|
||||
vec4 ixy1 = permute(ixy + iz1);
|
||||
vec4 gx0 = ixy0 / 7.0;
|
||||
vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
|
||||
gx0 = fract(gx0);
|
||||
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
|
||||
vec4 sz0 = step(gz0, vec4(0.0));
|
||||
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
|
||||
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
|
||||
vec4 gx1 = ixy1 / 7.0;
|
||||
vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
|
||||
gx1 = fract(gx1);
|
||||
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
|
||||
vec4 sz1 = step(gz1, vec4(0.0));
|
||||
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
|
||||
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
|
||||
vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
|
||||
vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
|
||||
vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
|
||||
vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
|
||||
vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
|
||||
vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
|
||||
vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
|
||||
vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
|
||||
vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));
|
||||
g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;
|
||||
vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));
|
||||
g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;
|
||||
float n000 = dot(g000, Pf0);
|
||||
float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));
|
||||
float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));
|
||||
float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));
|
||||
float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));
|
||||
float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));
|
||||
float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));
|
||||
float n111 = dot(g111, Pf1);
|
||||
vec3 fade_xyz = fade(Pf0);
|
||||
vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);
|
||||
vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);
|
||||
float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);
|
||||
return 2.2 * n_xyz;
|
||||
}
|
||||
`;
|
||||
|
||||
interface BeamsProps {
|
||||
beamWidth?: number;
|
||||
beamHeight?: number;
|
||||
beamNumber?: number;
|
||||
lightColor?: string;
|
||||
speed?: number;
|
||||
noiseIntensity?: number;
|
||||
scale?: number;
|
||||
rotation?: number;
|
||||
}
|
||||
|
||||
const Beams: FC<BeamsProps> = ({
|
||||
beamWidth = 2,
|
||||
beamHeight = 15,
|
||||
beamNumber = 12,
|
||||
lightColor = '#ffffff',
|
||||
speed = 2,
|
||||
noiseIntensity = 1.75,
|
||||
scale = 0.2,
|
||||
rotation = 0
|
||||
}) => {
|
||||
const meshRef = useRef<THREE.Mesh<THREE.BufferGeometry, THREE.ShaderMaterial>>(null!);
|
||||
|
||||
const beamMaterial = useMemo(
|
||||
() =>
|
||||
extendMaterial(THREE.MeshStandardMaterial, {
|
||||
header: `
|
||||
varying vec3 vEye;
|
||||
varying float vNoise;
|
||||
varying vec2 vUv;
|
||||
varying vec3 vPosition;
|
||||
uniform float time;
|
||||
uniform float uSpeed;
|
||||
uniform float uNoiseIntensity;
|
||||
uniform float uScale;
|
||||
${noise}`,
|
||||
vertexHeader: `
|
||||
float getPos(vec3 pos) {
|
||||
vec3 noisePos =
|
||||
vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;
|
||||
return cnoise(noisePos);
|
||||
}
|
||||
vec3 getCurrentPos(vec3 pos) {
|
||||
vec3 newpos = pos;
|
||||
newpos.z += getPos(pos);
|
||||
return newpos;
|
||||
}
|
||||
vec3 getNormal(vec3 pos) {
|
||||
vec3 curpos = getCurrentPos(pos);
|
||||
vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));
|
||||
vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));
|
||||
vec3 tangentX = normalize(nextposX - curpos);
|
||||
vec3 tangentZ = normalize(nextposZ - curpos);
|
||||
return normalize(cross(tangentZ, tangentX));
|
||||
}`,
|
||||
fragmentHeader: '',
|
||||
vertex: {
|
||||
'#include <begin_vertex>': `transformed.z += getPos(transformed.xyz);`,
|
||||
'#include <beginnormal_vertex>': `objectNormal = getNormal(position.xyz);`
|
||||
},
|
||||
fragment: {
|
||||
'#include <dithering_fragment>': `
|
||||
float randomNoise = noise(gl_FragCoord.xy);
|
||||
gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`
|
||||
},
|
||||
material: { fog: true },
|
||||
uniforms: {
|
||||
diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),
|
||||
time: { shared: true, mixed: true, linked: true, value: 0 },
|
||||
roughness: 0.3,
|
||||
metalness: 0.3,
|
||||
uSpeed: { shared: true, mixed: true, linked: true, value: speed },
|
||||
envMapIntensity: 10,
|
||||
uNoiseIntensity: noiseIntensity,
|
||||
uScale: scale
|
||||
}
|
||||
}),
|
||||
[speed, noiseIntensity, scale]
|
||||
);
|
||||
|
||||
return (
|
||||
<CanvasWrapper>
|
||||
<group rotation={[0, 0, degToRad(rotation)]}>
|
||||
<PlaneNoise ref={meshRef} material={beamMaterial} count={beamNumber} width={beamWidth} height={beamHeight} />
|
||||
<DirLight color={lightColor} position={[0, 3, 10]} />
|
||||
</group>
|
||||
<ambientLight intensity={1} />
|
||||
<color attach="background" args={['#000000']} />
|
||||
<PerspectiveCamera makeDefault position={[0, 0, 20]} fov={30} />
|
||||
</CanvasWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
function createStackedPlanesBufferGeometry(
|
||||
n: number,
|
||||
width: number,
|
||||
height: number,
|
||||
spacing: number,
|
||||
heightSegments: number
|
||||
): THREE.BufferGeometry {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
const numVertices = n * (heightSegments + 1) * 2;
|
||||
const numFaces = n * heightSegments * 2;
|
||||
const positions = new Float32Array(numVertices * 3);
|
||||
const indices = new Uint32Array(numFaces * 3);
|
||||
const uvs = new Float32Array(numVertices * 2);
|
||||
|
||||
let vertexOffset = 0;
|
||||
let indexOffset = 0;
|
||||
let uvOffset = 0;
|
||||
const totalWidth = n * width + (n - 1) * spacing;
|
||||
const xOffsetBase = -totalWidth / 2;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const xOffset = xOffsetBase + i * (width + spacing);
|
||||
const uvXOffset = Math.random() * 300;
|
||||
const uvYOffset = Math.random() * 300;
|
||||
|
||||
for (let j = 0; j <= heightSegments; j++) {
|
||||
const y = height * (j / heightSegments - 0.5);
|
||||
const v0 = [xOffset, y, 0];
|
||||
const v1 = [xOffset + width, y, 0];
|
||||
positions.set([...v0, ...v1], vertexOffset * 3);
|
||||
|
||||
const uvY = j / heightSegments;
|
||||
uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);
|
||||
|
||||
if (j < heightSegments) {
|
||||
const a = vertexOffset,
|
||||
b = vertexOffset + 1,
|
||||
c = vertexOffset + 2,
|
||||
d = vertexOffset + 3;
|
||||
indices.set([a, b, c, c, b, d], indexOffset);
|
||||
indexOffset += 6;
|
||||
}
|
||||
vertexOffset += 2;
|
||||
uvOffset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
|
||||
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
|
||||
geometry.computeVertexNormals();
|
||||
return geometry;
|
||||
}
|
||||
|
||||
const MergedPlanes = forwardRef<
|
||||
THREE.Mesh<THREE.BufferGeometry, THREE.ShaderMaterial>,
|
||||
{
|
||||
material: THREE.ShaderMaterial;
|
||||
width: number;
|
||||
count: number;
|
||||
height: number;
|
||||
}
|
||||
>(({ material, width, count, height }, ref) => {
|
||||
const mesh = useRef<THREE.Mesh<THREE.BufferGeometry, THREE.ShaderMaterial>>(null!);
|
||||
useImperativeHandle(ref, () => mesh.current);
|
||||
const geometry = useMemo(
|
||||
() => createStackedPlanesBufferGeometry(count, width, height, 0, 100),
|
||||
[count, width, height]
|
||||
);
|
||||
useFrame((_, delta) => {
|
||||
mesh.current.material.uniforms.time.value += 0.1 * delta;
|
||||
});
|
||||
return <mesh ref={mesh} geometry={geometry} material={material} />;
|
||||
});
|
||||
MergedPlanes.displayName = 'MergedPlanes';
|
||||
|
||||
const PlaneNoise = forwardRef<
|
||||
THREE.Mesh<THREE.BufferGeometry, THREE.ShaderMaterial>,
|
||||
{
|
||||
material: THREE.ShaderMaterial;
|
||||
width: number;
|
||||
count: number;
|
||||
height: number;
|
||||
}
|
||||
>((props, ref) => (
|
||||
<MergedPlanes ref={ref} material={props.material} width={props.width} count={props.count} height={props.height} />
|
||||
));
|
||||
PlaneNoise.displayName = 'PlaneNoise';
|
||||
|
||||
const DirLight: FC<{ position: [number, number, number]; color: string }> = ({ position, color }) => {
|
||||
const dir = useRef<THREE.DirectionalLight>(null!);
|
||||
useEffect(() => {
|
||||
if (!dir.current) return;
|
||||
const cam = dir.current.shadow.camera as THREE.Camera & {
|
||||
top: number;
|
||||
bottom: number;
|
||||
left: number;
|
||||
right: number;
|
||||
far: number;
|
||||
};
|
||||
cam.top = 24;
|
||||
cam.bottom = -24;
|
||||
cam.left = -24;
|
||||
cam.right = 24;
|
||||
cam.far = 64;
|
||||
dir.current.shadow.bias = -0.004;
|
||||
}, []);
|
||||
return <directionalLight ref={dir} color={color} intensity={1} position={position} />;
|
||||
};
|
||||
|
||||
export default Beams;
|
||||
198
src/app/components/Crosshair.tsx
Normal file
198
src/app/components/Crosshair.tsx
Normal file
@ -0,0 +1,198 @@
|
||||
'use client'
|
||||
|
||||
import React, { useEffect, useRef, RefObject } from 'react'
|
||||
import { gsap } from 'gsap'
|
||||
|
||||
const lerp = (a: number, b: number, n: number): number => (1 - n) * a + n * b
|
||||
|
||||
const getMousePos = (e: MouseEvent, container?: HTMLElement | null): { x: number; y: number } => {
|
||||
if (container) {
|
||||
const bounds = container.getBoundingClientRect()
|
||||
return {
|
||||
x: e.clientX - bounds.left,
|
||||
y: e.clientY - bounds.top,
|
||||
}
|
||||
}
|
||||
return { x: e.clientX, y: e.clientY }
|
||||
}
|
||||
|
||||
interface CrosshairProps {
|
||||
color?: string
|
||||
containerRef?: RefObject<HTMLDivElement | null> | null
|
||||
}
|
||||
|
||||
const Crosshair: React.FC<CrosshairProps> = ({ color = 'white', containerRef = null }) => {
|
||||
const cursorRef = useRef<HTMLDivElement>(null)
|
||||
const lineHorizontalRef = useRef<HTMLDivElement>(null)
|
||||
const lineVerticalRef = useRef<HTMLDivElement>(null)
|
||||
const filterXRef = useRef<SVGFETurbulenceElement>(null)
|
||||
const filterYRef = useRef<SVGFETurbulenceElement>(null)
|
||||
|
||||
let mouse = { x: 0, y: 0 }
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (ev: Event) => {
|
||||
const mouseEvent = ev as MouseEvent
|
||||
mouse = getMousePos(mouseEvent, containerRef?.current || undefined)
|
||||
|
||||
if (containerRef?.current) {
|
||||
const bounds = containerRef.current.getBoundingClientRect()
|
||||
if (
|
||||
mouseEvent.clientX < bounds.left ||
|
||||
mouseEvent.clientX > bounds.right ||
|
||||
mouseEvent.clientY < bounds.top ||
|
||||
mouseEvent.clientY > bounds.bottom
|
||||
) {
|
||||
gsap.to([lineHorizontalRef.current, lineVerticalRef.current].filter(Boolean), { opacity: 0 })
|
||||
} else {
|
||||
gsap.to([lineHorizontalRef.current, lineVerticalRef.current].filter(Boolean), { opacity: 1 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const target: HTMLElement | Window = containerRef?.current || window
|
||||
target.addEventListener('mousemove', handleMouseMove)
|
||||
|
||||
const renderedStyles: {
|
||||
[key: string]: { previous: number; current: number; amt: number }
|
||||
} = {
|
||||
tx: { previous: 0, current: 0, amt: 0.15 },
|
||||
ty: { previous: 0, current: 0, amt: 0.15 },
|
||||
}
|
||||
|
||||
gsap.set([lineHorizontalRef.current, lineVerticalRef.current].filter(Boolean), { opacity: 0 })
|
||||
|
||||
const onMouseMove = (ev: Event) => {
|
||||
const mouseEvent = ev as MouseEvent
|
||||
mouse = getMousePos(mouseEvent, containerRef?.current || undefined)
|
||||
|
||||
renderedStyles.tx.previous = renderedStyles.tx.current = mouse.x
|
||||
renderedStyles.ty.previous = renderedStyles.ty.current = mouse.y
|
||||
|
||||
gsap.to([lineHorizontalRef.current, lineVerticalRef.current].filter(Boolean), {
|
||||
duration: 0.9,
|
||||
ease: 'Power3.easeOut',
|
||||
opacity: 1,
|
||||
})
|
||||
|
||||
requestAnimationFrame(render)
|
||||
|
||||
target.removeEventListener('mousemove', onMouseMove)
|
||||
}
|
||||
|
||||
target.addEventListener('mousemove', onMouseMove)
|
||||
|
||||
const primitiveValues = { turbulence: 0 }
|
||||
|
||||
const tl = gsap
|
||||
.timeline({
|
||||
paused: true,
|
||||
onStart: () => {
|
||||
if (lineHorizontalRef.current) {
|
||||
lineHorizontalRef.current.style.filter = 'url(#filter-noise-x)'
|
||||
}
|
||||
if (lineVerticalRef.current) {
|
||||
lineVerticalRef.current.style.filter = 'url(#filter-noise-y)'
|
||||
}
|
||||
},
|
||||
onUpdate: () => {
|
||||
if (filterXRef.current && filterYRef.current) {
|
||||
filterXRef.current.setAttribute('baseFrequency', primitiveValues.turbulence.toString())
|
||||
filterYRef.current.setAttribute('baseFrequency', primitiveValues.turbulence.toString())
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
if (lineHorizontalRef.current) lineHorizontalRef.current.style.filter = 'none'
|
||||
if (lineVerticalRef.current) lineVerticalRef.current.style.filter = 'none'
|
||||
},
|
||||
})
|
||||
.to(primitiveValues, {
|
||||
duration: 0.5,
|
||||
ease: 'power1',
|
||||
startAt: { turbulence: 1 },
|
||||
turbulence: 0,
|
||||
})
|
||||
|
||||
const enter = () => tl.restart()
|
||||
const leave = () => {
|
||||
tl.progress(1).kill()
|
||||
}
|
||||
|
||||
const render = () => {
|
||||
renderedStyles.tx.current = mouse.x
|
||||
renderedStyles.ty.current = mouse.y
|
||||
|
||||
for (const key in renderedStyles) {
|
||||
const style = renderedStyles[key]
|
||||
style.previous = lerp(style.previous, style.current, style.amt)
|
||||
}
|
||||
|
||||
if (lineHorizontalRef.current && lineVerticalRef.current) {
|
||||
gsap.set(lineVerticalRef.current, { x: renderedStyles.tx.previous })
|
||||
gsap.set(lineHorizontalRef.current, { y: renderedStyles.ty.previous })
|
||||
}
|
||||
|
||||
requestAnimationFrame(render)
|
||||
}
|
||||
|
||||
const links: NodeListOf<HTMLAnchorElement> = containerRef?.current
|
||||
? containerRef.current.querySelectorAll('a')
|
||||
: document.querySelectorAll('a')
|
||||
|
||||
links.forEach(link => {
|
||||
link.addEventListener('mouseenter', enter)
|
||||
link.addEventListener('mouseleave', leave)
|
||||
})
|
||||
|
||||
return () => {
|
||||
target.removeEventListener('mousemove', handleMouseMove)
|
||||
target.removeEventListener('mousemove', onMouseMove)
|
||||
links.forEach(link => {
|
||||
link.removeEventListener('mouseenter', enter)
|
||||
link.removeEventListener('mouseleave', leave)
|
||||
})
|
||||
}
|
||||
}, [containerRef])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={cursorRef}
|
||||
className={`${containerRef ? 'absolute' : 'fixed'} top-0 left-0 w-full h-full pointer-events-none z-[10000]`}
|
||||
>
|
||||
<svg className="absolute top-0 left-0 w-full h-full">
|
||||
<defs>
|
||||
<filter id="filter-noise-x">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.000001"
|
||||
numOctaves="1"
|
||||
ref={filterXRef}
|
||||
/>
|
||||
<feDisplacementMap in="SourceGraphic" scale="40" />
|
||||
</filter>
|
||||
<filter id="filter-noise-y">
|
||||
<feTurbulence
|
||||
type="fractalNoise"
|
||||
baseFrequency="0.000001"
|
||||
numOctaves="1"
|
||||
ref={filterYRef}
|
||||
/>
|
||||
<feDisplacementMap in="SourceGraphic" scale="40" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
<div
|
||||
ref={lineHorizontalRef}
|
||||
className="absolute w-full h-px pointer-events-none opacity-0 translate-y-1/2"
|
||||
style={{ background: color }}
|
||||
/>
|
||||
<div
|
||||
ref={lineVerticalRef}
|
||||
className="absolute h-full w-px pointer-events-none opacity-0 translate-x-1/2"
|
||||
style={{ background: color }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Crosshair
|
||||
223
src/app/components/SplitText.tsx
Normal file
223
src/app/components/SplitText.tsx
Normal file
@ -0,0 +1,223 @@
|
||||
import React, { useRef, useEffect, useState } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
||||
import { SplitText as GSAPSplitText } from 'gsap/SplitText';
|
||||
import { useGSAP } from '@gsap/react';
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger, GSAPSplitText, useGSAP);
|
||||
|
||||
export interface SplitTextProps {
|
||||
text: string;
|
||||
className?: string;
|
||||
delay?: number;
|
||||
duration?: number;
|
||||
ease?: string | ((t: number) => number);
|
||||
splitType?: 'chars' | 'words' | 'lines' | 'words, chars';
|
||||
from?: gsap.TweenVars;
|
||||
to?: gsap.TweenVars;
|
||||
threshold?: number;
|
||||
rootMargin?: string;
|
||||
tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span';
|
||||
textAlign?: React.CSSProperties['textAlign'];
|
||||
onLetterAnimationComplete?: () => void;
|
||||
}
|
||||
|
||||
const SplitText: React.FC<SplitTextProps> = ({
|
||||
text,
|
||||
className = '',
|
||||
delay = 50,
|
||||
duration = 1.25,
|
||||
ease = 'power3.out',
|
||||
splitType = 'chars',
|
||||
from = { opacity: 0, y: 40 },
|
||||
to = { opacity: 1, y: 0 },
|
||||
threshold = 0.1,
|
||||
rootMargin = '-100px',
|
||||
tag = 'p',
|
||||
textAlign = 'center',
|
||||
onLetterAnimationComplete
|
||||
}) => {
|
||||
const ref = useRef<HTMLParagraphElement>(null);
|
||||
const animationCompletedRef = useRef(false);
|
||||
const onCompleteRef = useRef(onLetterAnimationComplete);
|
||||
const [fontsLoaded, setFontsLoaded] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
onCompleteRef.current = onLetterAnimationComplete;
|
||||
}, [onLetterAnimationComplete]);
|
||||
|
||||
// Reset animation completion when text changes so we can re-split/re-animate
|
||||
useEffect(() => {
|
||||
animationCompletedRef.current = false;
|
||||
}, [text]);
|
||||
|
||||
useEffect(() => {
|
||||
if (document.fonts.status === 'loaded') {
|
||||
setFontsLoaded(true);
|
||||
} else {
|
||||
document.fonts.ready.then(() => {
|
||||
setFontsLoaded(true);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useGSAP(
|
||||
() => {
|
||||
if (!ref.current || !text || !fontsLoaded) return;
|
||||
if (animationCompletedRef.current) return;
|
||||
|
||||
const el = ref.current as HTMLElement & {
|
||||
_rbsplitInstance?: GSAPSplitText;
|
||||
};
|
||||
|
||||
if (el._rbsplitInstance) {
|
||||
try {
|
||||
el._rbsplitInstance.revert();
|
||||
} catch (_) {}
|
||||
el._rbsplitInstance = undefined;
|
||||
}
|
||||
|
||||
const startPct = (1 - threshold) * 100;
|
||||
const marginMatch = /^(-?\d+(?:\.\d+)?)(px|em|rem|%)?$/.exec(rootMargin);
|
||||
const marginValue = marginMatch ? parseFloat(marginMatch[1]) : 0;
|
||||
const marginUnit = marginMatch ? marginMatch[2] || 'px' : 'px';
|
||||
const sign =
|
||||
marginValue === 0
|
||||
? ''
|
||||
: marginValue < 0
|
||||
? `-=${Math.abs(marginValue)}${marginUnit}`
|
||||
: `+=${marginValue}${marginUnit}`;
|
||||
const start = `top ${startPct}%${sign}`;
|
||||
|
||||
let targets: Element[] = [];
|
||||
const assignTargets = (self: GSAPSplitText) => {
|
||||
if (splitType.includes('chars') && (self as GSAPSplitText).chars?.length)
|
||||
targets = (self as GSAPSplitText).chars;
|
||||
if (!targets.length && splitType.includes('words') && self.words.length) targets = self.words;
|
||||
if (!targets.length && splitType.includes('lines') && self.lines.length) targets = self.lines;
|
||||
if (!targets.length) targets = self.chars || self.words || self.lines;
|
||||
};
|
||||
|
||||
const splitInstance = new GSAPSplitText(el, {
|
||||
type: splitType,
|
||||
smartWrap: true,
|
||||
autoSplit: splitType === 'lines',
|
||||
linesClass: 'split-line',
|
||||
wordsClass: 'split-word',
|
||||
charsClass: 'split-char',
|
||||
reduceWhiteSpace: false,
|
||||
onSplit: (self: GSAPSplitText) => {
|
||||
assignTargets(self);
|
||||
return gsap.fromTo(
|
||||
targets,
|
||||
{ ...from },
|
||||
{
|
||||
...to,
|
||||
duration,
|
||||
ease,
|
||||
stagger: delay / 1000,
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
start,
|
||||
once: true,
|
||||
fastScrollEnd: true,
|
||||
anticipatePin: 0.4
|
||||
},
|
||||
onComplete: () => {
|
||||
animationCompletedRef.current = true;
|
||||
onCompleteRef.current?.();
|
||||
},
|
||||
willChange: 'transform, opacity',
|
||||
force3D: true
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
el._rbsplitInstance = splitInstance;
|
||||
|
||||
return () => {
|
||||
ScrollTrigger.getAll().forEach(st => {
|
||||
if (st.trigger === el) st.kill();
|
||||
});
|
||||
try {
|
||||
splitInstance.revert();
|
||||
} catch (_) {}
|
||||
el._rbsplitInstance = undefined;
|
||||
};
|
||||
},
|
||||
{
|
||||
dependencies: [
|
||||
text,
|
||||
delay,
|
||||
duration,
|
||||
ease,
|
||||
splitType,
|
||||
JSON.stringify(from),
|
||||
JSON.stringify(to),
|
||||
threshold,
|
||||
rootMargin,
|
||||
fontsLoaded
|
||||
],
|
||||
scope: ref
|
||||
}
|
||||
);
|
||||
|
||||
const renderTag = () => {
|
||||
const style: React.CSSProperties = {
|
||||
textAlign,
|
||||
wordWrap: 'break-word',
|
||||
willChange: 'transform, opacity'
|
||||
};
|
||||
const classes = `split-parent overflow-hidden inline-block whitespace-normal ${className}`;
|
||||
|
||||
switch (tag) {
|
||||
case 'h1':
|
||||
return (
|
||||
<h1 ref={ref} style={style} className={classes}>
|
||||
{text}
|
||||
</h1>
|
||||
);
|
||||
case 'h2':
|
||||
return (
|
||||
<h2 ref={ref} style={style} className={classes}>
|
||||
{text}
|
||||
</h2>
|
||||
);
|
||||
case 'h3':
|
||||
return (
|
||||
<h3 ref={ref} style={style} className={classes}>
|
||||
{text}
|
||||
</h3>
|
||||
);
|
||||
case 'h4':
|
||||
return (
|
||||
<h4 ref={ref} style={style} className={classes}>
|
||||
{text}
|
||||
</h4>
|
||||
);
|
||||
case 'h5':
|
||||
return (
|
||||
<h5 ref={ref} style={style} className={classes}>
|
||||
{text}
|
||||
</h5>
|
||||
);
|
||||
case 'h6':
|
||||
return (
|
||||
<h6 ref={ref} style={style} className={classes}>
|
||||
{text}
|
||||
</h6>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<p ref={ref} style={style} className={classes}>
|
||||
{text}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return renderTag();
|
||||
};
|
||||
|
||||
export default SplitText;
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useRouter, usePathname } from 'next/navigation'
|
||||
import Image from 'next/image'
|
||||
import {
|
||||
Dialog,
|
||||
@ -61,11 +61,13 @@ export default function Header({ setGlobalLoggingOut }: HeaderProps) {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [animateIn, setAnimateIn] = useState(false)
|
||||
const [scrollY, setScrollY] = useState(0)
|
||||
const user = useAuthStore(s => s.user)
|
||||
const logout = useAuthStore(s => s.logout)
|
||||
const accessToken = useAuthStore(s => s.accessToken)
|
||||
const refreshAuthToken = useAuthStore(s => s.refreshAuthToken)
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
const [hasReferralPerm, setHasReferralPerm] = useState(false)
|
||||
const [adminMgmtOpen, setAdminMgmtOpen] = useState(false)
|
||||
@ -116,6 +118,38 @@ export default function Header({ setGlobalLoggingOut }: HeaderProps) {
|
||||
setAnimateIn(true)
|
||||
}, [])
|
||||
|
||||
// Home-page scroll listener: reveal header after first scroll with slight parallax
|
||||
useEffect(() => {
|
||||
if (!mounted) return
|
||||
|
||||
if (pathname !== '/') {
|
||||
// non-home: header always visible
|
||||
setScrollY(100)
|
||||
return
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
const y = window.scrollY || window.pageYOffset || 0
|
||||
setScrollY(y)
|
||||
}
|
||||
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
// virtual scroll so header can reveal even if page cannot scroll
|
||||
setScrollY(prev => {
|
||||
const next = prev + e.deltaY
|
||||
return Math.max(0, Math.min(next, 200))
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
window.addEventListener('wheel', handleWheel, { passive: true })
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
window.removeEventListener('wheel', handleWheel)
|
||||
}
|
||||
}, [mounted, pathname])
|
||||
|
||||
// Fetch user permissions and set hasReferralPerm
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
@ -273,13 +307,15 @@ export default function Header({ setGlobalLoggingOut }: HeaderProps) {
|
||||
((user as any)?.roles?.includes?.('admin'))
|
||||
)
|
||||
const isAdmin = mounted && rawIsAdmin
|
||||
const headerVisible = pathname !== '/' ? animateIn : animateIn && scrollY > 24
|
||||
const parallaxOffset = pathname === '/' ? Math.max(-16, -scrollY * 0.15) : 0
|
||||
|
||||
return (
|
||||
<header
|
||||
// Remove bottom border when admin subheader is present to avoid a blue line under the gold bar
|
||||
className={`relative isolate z-10 shadow-lg shadow-black/30 after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:bg-[radial-gradient(circle_at_20%_20%,rgba(56,124,255,0.18),transparent_55%),radial-gradient(circle_at_80%_35%,rgba(139,92,246,0.16),transparent_60%)] ${isAdmin ? '' : 'border-b border-white/10'} ${animateIn ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-4'} transition-all duration-500 ease-out`}
|
||||
className={`fixed top-0 left-0 w-full isolate z-10 shadow-lg shadow-black/30 after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:bg-[radial-gradient(circle_at_20%_20%,rgba(56,124,255,0.18),transparent_55%),radial-gradient(circle_at_80%_35%,rgba(139,92,246,0.16),transparent_60%)] ${isAdmin ? '' : 'border-b border-white/10'} ${headerVisible ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-6 pointer-events-none'} transition-all duration-500 ease-out`}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #0F1D37 0%, #0A162A 50%, #081224 100%)',
|
||||
transform: `translateY(${parallaxOffset}px)`,
|
||||
}}
|
||||
>
|
||||
<nav
|
||||
|
||||
402
src/app/components/waves.tsx
Normal file
402
src/app/components/waves.tsx
Normal file
@ -0,0 +1,402 @@
|
||||
'use client';
|
||||
|
||||
import React, { useRef, useEffect, CSSProperties } from 'react';
|
||||
|
||||
class Grad {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
constructor(x: number, y: number, z: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
dot2(x: number, y: number): number {
|
||||
return this.x * x + this.y * y;
|
||||
}
|
||||
}
|
||||
|
||||
class Noise {
|
||||
grad3: Grad[];
|
||||
p: number[];
|
||||
perm: number[];
|
||||
gradP: Grad[];
|
||||
|
||||
constructor(seed = 0) {
|
||||
this.grad3 = [
|
||||
new Grad(1, 1, 0),
|
||||
new Grad(-1, 1, 0),
|
||||
new Grad(1, -1, 0),
|
||||
new Grad(-1, -1, 0),
|
||||
new Grad(1, 0, 1),
|
||||
new Grad(-1, 0, 1),
|
||||
new Grad(1, 0, -1),
|
||||
new Grad(-1, 0, -1),
|
||||
new Grad(0, 1, 1),
|
||||
new Grad(0, -1, 1),
|
||||
new Grad(0, 1, -1),
|
||||
new Grad(0, -1, -1),
|
||||
];
|
||||
this.p = [
|
||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240,
|
||||
21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88,
|
||||
237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83,
|
||||
111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216,
|
||||
80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186,
|
||||
3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
|
||||
17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
|
||||
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,
|
||||
238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
|
||||
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128,
|
||||
195, 78, 66, 215, 61, 156, 180,
|
||||
];
|
||||
this.perm = new Array(512);
|
||||
this.gradP = new Array(512);
|
||||
this.seed(seed);
|
||||
}
|
||||
seed(seed: number) {
|
||||
if (seed > 0 && seed < 1) seed *= 65536;
|
||||
seed = Math.floor(seed);
|
||||
if (seed < 256) seed |= seed << 8;
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const v = i & 1 ? this.p[i] ^ (seed & 255) : this.p[i] ^ ((seed >> 8) & 255);
|
||||
this.perm[i] = this.perm[i + 256] = v;
|
||||
this.gradP[i] = this.gradP[i + 256] = this.grad3[v % 12];
|
||||
}
|
||||
}
|
||||
fade(t: number): number {
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
lerp(a: number, b: number, t: number): number {
|
||||
return (1 - t) * a + t * b;
|
||||
}
|
||||
perlin2(x: number, y: number): number {
|
||||
let X = Math.floor(x),
|
||||
Y = Math.floor(y);
|
||||
x -= X;
|
||||
y -= Y;
|
||||
X &= 255;
|
||||
Y &= 255;
|
||||
const n00 = this.gradP[X + this.perm[Y]].dot2(x, y);
|
||||
const n01 = this.gradP[X + this.perm[Y + 1]].dot2(x, y - 1);
|
||||
const n10 = this.gradP[X + 1 + this.perm[Y]].dot2(x - 1, y);
|
||||
const n11 = this.gradP[X + 1 + this.perm[Y + 1]].dot2(x - 1, y - 1);
|
||||
const u = this.fade(x);
|
||||
return this.lerp(this.lerp(n00, n10, u), this.lerp(n01, n11, u), this.fade(y));
|
||||
}
|
||||
}
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
wave: { x: number; y: number };
|
||||
cursor: { x: number; y: number; vx: number; vy: number };
|
||||
}
|
||||
|
||||
interface Mouse {
|
||||
x: number;
|
||||
y: number;
|
||||
lx: number;
|
||||
ly: number;
|
||||
sx: number;
|
||||
sy: number;
|
||||
v: number;
|
||||
vs: number;
|
||||
a: number;
|
||||
set: boolean;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
lineColor: string;
|
||||
waveSpeedX: number;
|
||||
waveSpeedY: number;
|
||||
waveAmpX: number;
|
||||
waveAmpY: number;
|
||||
friction: number;
|
||||
tension: number;
|
||||
maxCursorMove: number;
|
||||
xGap: number;
|
||||
yGap: number;
|
||||
}
|
||||
|
||||
export interface WavesProps {
|
||||
lineColor?: string;
|
||||
backgroundColor?: string;
|
||||
waveSpeedX?: number;
|
||||
waveSpeedY?: number;
|
||||
waveAmpX?: number;
|
||||
waveAmpY?: number;
|
||||
xGap?: number;
|
||||
yGap?: number;
|
||||
friction?: number;
|
||||
tension?: number;
|
||||
maxCursorMove?: number;
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Waves: React.FC<WavesProps> = ({
|
||||
lineColor = '#fff',
|
||||
backgroundColor = 'rgba(255, 255, 255, 0.2)',
|
||||
waveSpeedX = 0.0125,
|
||||
waveSpeedY = 0.005,
|
||||
waveAmpX = 32,
|
||||
waveAmpY = 16,
|
||||
xGap = 10,
|
||||
yGap = 32,
|
||||
friction = 0.925,
|
||||
tension = 0.005,
|
||||
maxCursorMove = 100,
|
||||
style = {},
|
||||
className = '',
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
|
||||
const boundingRef = useRef({ width: 0, height: 0, left: 0, top: 0 });
|
||||
const noiseRef = useRef(new Noise(Math.random()));
|
||||
const linesRef = useRef<Point[][]>([]);
|
||||
const mouseRef = useRef<Mouse>({
|
||||
x: -10,
|
||||
y: 0,
|
||||
lx: 0,
|
||||
ly: 0,
|
||||
sx: 0,
|
||||
sy: 0,
|
||||
v: 0,
|
||||
vs: 0,
|
||||
a: 0,
|
||||
set: false,
|
||||
});
|
||||
const configRef = useRef<Config>({
|
||||
lineColor,
|
||||
waveSpeedX,
|
||||
waveSpeedY,
|
||||
waveAmpX,
|
||||
waveAmpY,
|
||||
friction,
|
||||
tension,
|
||||
maxCursorMove,
|
||||
xGap,
|
||||
yGap,
|
||||
});
|
||||
const frameIdRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
configRef.current = {
|
||||
lineColor,
|
||||
waveSpeedX,
|
||||
waveSpeedY,
|
||||
waveAmpX,
|
||||
waveAmpY,
|
||||
friction,
|
||||
tension,
|
||||
maxCursorMove,
|
||||
xGap,
|
||||
yGap,
|
||||
};
|
||||
}, [
|
||||
lineColor,
|
||||
waveSpeedX,
|
||||
waveSpeedY,
|
||||
waveAmpX,
|
||||
waveAmpY,
|
||||
friction,
|
||||
tension,
|
||||
maxCursorMove,
|
||||
xGap,
|
||||
yGap,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
const container = containerRef.current;
|
||||
if (!canvas || !container) return;
|
||||
ctxRef.current = canvas.getContext('2d');
|
||||
|
||||
function setSize() {
|
||||
if (!container) return;
|
||||
const rect = container.getBoundingClientRect();
|
||||
boundingRef.current = {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
};
|
||||
if (canvas) {
|
||||
canvas.width = rect.width;
|
||||
canvas.height = rect.height;
|
||||
}
|
||||
}
|
||||
|
||||
function setLines() {
|
||||
const { width, height } = boundingRef.current;
|
||||
linesRef.current = [];
|
||||
const oWidth = width + 200;
|
||||
const oHeight = height + 30;
|
||||
const { xGap, yGap } = configRef.current;
|
||||
const totalLines = Math.ceil(oWidth / xGap);
|
||||
const totalPoints = Math.ceil(oHeight / yGap);
|
||||
const xStart = (width - xGap * totalLines) / 2;
|
||||
const yStart = (height - yGap * totalPoints) / 2;
|
||||
for (let i = 0; i <= totalLines; i++) {
|
||||
const pts: Point[] = [];
|
||||
for (let j = 0; j <= totalPoints; j++) {
|
||||
pts.push({
|
||||
x: xStart + xGap * i,
|
||||
y: yStart + yGap * j,
|
||||
wave: { x: 0, y: 0 },
|
||||
cursor: { x: 0, y: 0, vx: 0, vy: 0 },
|
||||
});
|
||||
}
|
||||
linesRef.current.push(pts);
|
||||
}
|
||||
}
|
||||
|
||||
function movePoints(time: number) {
|
||||
const lines = linesRef.current;
|
||||
const mouse = mouseRef.current;
|
||||
const noise = noiseRef.current;
|
||||
const { waveSpeedX, waveSpeedY, waveAmpX, waveAmpY, friction, tension, maxCursorMove } = configRef.current;
|
||||
|
||||
lines.forEach(pts => {
|
||||
pts.forEach(p => {
|
||||
const move = noise.perlin2((p.x + time * waveSpeedX) * 0.002, (p.y + time * waveSpeedY) * 0.0015) * 12;
|
||||
p.wave.x = Math.cos(move) * waveAmpX;
|
||||
p.wave.y = Math.sin(move) * waveAmpY;
|
||||
|
||||
const dx = p.x - mouse.sx;
|
||||
const dy = p.y - mouse.sy;
|
||||
const dist = Math.hypot(dx, dy);
|
||||
const l = Math.max(175, mouse.vs);
|
||||
if (dist < l) {
|
||||
const s = 1 - dist / l;
|
||||
const f = Math.cos(dist * 0.001) * s;
|
||||
p.cursor.vx += Math.cos(mouse.a) * f * l * mouse.vs * 0.00065;
|
||||
p.cursor.vy += Math.sin(mouse.a) * f * l * mouse.vs * 0.00065;
|
||||
}
|
||||
|
||||
p.cursor.vx += (0 - p.cursor.x) * tension;
|
||||
p.cursor.vy += (0 - p.cursor.y) * tension;
|
||||
p.cursor.vx *= friction;
|
||||
p.cursor.vy *= friction;
|
||||
p.cursor.x += p.cursor.vx * 2;
|
||||
p.cursor.y += p.cursor.vy * 2;
|
||||
p.cursor.x = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.x));
|
||||
p.cursor.y = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.y));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function moved(point: Point, withCursor = true): { x: number; y: number } {
|
||||
const x = point.x + point.wave.x + (withCursor ? point.cursor.x : 0);
|
||||
const y = point.y + point.wave.y + (withCursor ? point.cursor.y : 0);
|
||||
return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };
|
||||
}
|
||||
|
||||
function drawLines() {
|
||||
const { width, height } = boundingRef.current;
|
||||
const ctx = ctxRef.current;
|
||||
if (!ctx) return;
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = configRef.current.lineColor;
|
||||
|
||||
linesRef.current.forEach(points => {
|
||||
let p1 = moved(points[0], false);
|
||||
ctx.moveTo(p1.x, p1.y);
|
||||
points.forEach((p, idx) => {
|
||||
const isLast = idx === points.length - 1;
|
||||
p1 = moved(p, !isLast);
|
||||
const p2 = moved(points[idx + 1] || points[points.length - 1], !isLast);
|
||||
ctx.lineTo(p1.x, p1.y);
|
||||
if (isLast) ctx.moveTo(p2.x, p2.y);
|
||||
});
|
||||
});
|
||||
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function tick(t: number) {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const mouse = mouseRef.current;
|
||||
mouse.sx += (mouse.x - mouse.sx) * 0.1;
|
||||
mouse.sy += (mouse.y - mouse.sy) * 0.1;
|
||||
const dx = mouse.x - mouse.lx;
|
||||
const dy = mouse.y - mouse.ly;
|
||||
const d = Math.hypot(dx, dy);
|
||||
mouse.v = d;
|
||||
mouse.vs += (d - mouse.vs) * 0.1;
|
||||
mouse.vs = Math.min(100, mouse.vs);
|
||||
mouse.lx = mouse.x;
|
||||
mouse.ly = mouse.y;
|
||||
mouse.a = Math.atan2(dy, dx);
|
||||
container.style.setProperty('--x', `${mouse.sx}px`);
|
||||
container.style.setProperty('--y', `${mouse.sy}px`);
|
||||
|
||||
movePoints(t);
|
||||
drawLines();
|
||||
frameIdRef.current = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
setSize();
|
||||
setLines();
|
||||
}
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
updateMouse(e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
function onTouchMove(e: TouchEvent) {
|
||||
const touch = e.touches[0];
|
||||
updateMouse(touch.clientX, touch.clientY);
|
||||
}
|
||||
|
||||
function updateMouse(x: number, y: number) {
|
||||
const mouse = mouseRef.current;
|
||||
const b = boundingRef.current;
|
||||
mouse.x = x - b.left;
|
||||
mouse.y = y - b.top;
|
||||
if (!mouse.set) {
|
||||
mouse.sx = mouse.x;
|
||||
mouse.sy = mouse.y;
|
||||
mouse.lx = mouse.x;
|
||||
mouse.ly = mouse.y;
|
||||
mouse.set = true;
|
||||
}
|
||||
}
|
||||
|
||||
setSize();
|
||||
setLines();
|
||||
frameIdRef.current = requestAnimationFrame(tick);
|
||||
window.addEventListener('resize', onResize);
|
||||
window.addEventListener('mousemove', onMouseMove);
|
||||
window.addEventListener('touchmove', onTouchMove, { passive: false });
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
window.removeEventListener('touchmove', onTouchMove);
|
||||
if (frameIdRef.current !== null) cancelAnimationFrame(frameIdRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
backgroundColor,
|
||||
...style,
|
||||
}}
|
||||
className={`fixed inset-0 w-full h-full overflow-hidden ${className}`}
|
||||
>
|
||||
<canvas ref={canvasRef} className="block w-full h-full" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Waves;
|
||||
@ -130,12 +130,10 @@ export default function LoginForm() {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
// Background remains
|
||||
backgroundImage: 'url(/images/misc/marble_bluegoldwhite_BG.jpg)',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
// REMOVE marble image so Waves shows through
|
||||
background: 'transparent',
|
||||
// Subtle padding to breathe on mobile
|
||||
padding: isMobile ? '0.75rem' : '1.5rem'
|
||||
padding: isMobile ? '0.75rem' : '1.5rem',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@ -5,12 +5,11 @@ import { useRouter } from 'next/navigation'
|
||||
import LoginForm from './components/LoginForm'
|
||||
import PageLayout from '../components/PageLayout'
|
||||
import useAuthStore from '../store/authStore'
|
||||
import GlobalAnimatedBackground from '../background/GlobalAnimatedBackground'
|
||||
import { ToastProvider } from '../components/toast/toastComponent'
|
||||
import PageTransitionEffect from '../components/animation/pageTransitionEffect'
|
||||
import Waves from '../components/waves'
|
||||
|
||||
export default function LoginPage() {
|
||||
const [showBackground, setShowBackground] = useState(false)
|
||||
const [hasHydrated, setHasHydrated] = useState(false)
|
||||
const router = useRouter()
|
||||
const user = useAuthStore(state => state.user)
|
||||
@ -27,14 +26,6 @@ export default function LoginPage() {
|
||||
}
|
||||
}, [user, router])
|
||||
|
||||
// Responsive background detection
|
||||
useEffect(() => {
|
||||
const handleResize = () => setShowBackground(window.innerWidth >= 768)
|
||||
handleResize() // Initial check
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [])
|
||||
|
||||
// Don't render if user is already logged in (only after hydration to avoid SSR mismatch)
|
||||
if (hasHydrated && user) {
|
||||
return (
|
||||
@ -58,13 +49,24 @@ export default function LoginPage() {
|
||||
<ToastProvider>
|
||||
<PageLayout showFooter={true}>
|
||||
<div
|
||||
className="relative w-full flex flex-col min-h-screen"
|
||||
style={{
|
||||
backgroundImage: 'url(/images/misc/marble_bluegoldwhite_BG.jpg)',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
}}
|
||||
className="relative w-full flex flex-col min-h-screen overflow-hidden"
|
||||
style={{ backgroundImage: 'none', background: 'none' }}
|
||||
>
|
||||
{/* Waves background */}
|
||||
<Waves
|
||||
className="pointer-events-none"
|
||||
lineColor="#0f172a"
|
||||
backgroundColor="rgba(245, 245, 240, 1)"
|
||||
waveSpeedX={0.02}
|
||||
waveSpeedY={0.01}
|
||||
waveAmpX={40}
|
||||
waveAmpY={20}
|
||||
friction={0.9}
|
||||
tension={0.01}
|
||||
maxCursorMove={120}
|
||||
xGap={12}
|
||||
yGap={36}
|
||||
/>
|
||||
<div className="relative z-10 flex-1 flex items-center justify-center">
|
||||
<div className="w-full">
|
||||
<LoginForm />
|
||||
|
||||
196
src/app/page.tsx
196
src/app/page.tsx
@ -1,134 +1,94 @@
|
||||
'use client';
|
||||
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { gsap } from 'gsap';
|
||||
import PageLayout from './components/PageLayout';
|
||||
import Crosshair from './components/Crosshair';
|
||||
import Waves from './components/waves';
|
||||
import SplitText from './components/SplitText';
|
||||
|
||||
export default function HomePage() {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const handleLoginClick = () => {
|
||||
if (!containerRef.current) {
|
||||
router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
gsap.to(containerRef.current, {
|
||||
opacity: 0,
|
||||
duration: 0.6,
|
||||
ease: 'power2.out',
|
||||
onComplete: () => router.push('/login'),
|
||||
});
|
||||
};
|
||||
|
||||
// Ensure LOGIN never stays stuck after scrolling / wheel
|
||||
useEffect(() => {
|
||||
const resetHover = () => setIsHover(false);
|
||||
window.addEventListener('wheel', resetHover, { passive: true });
|
||||
window.addEventListener('scroll', resetHover, { passive: true });
|
||||
return () => {
|
||||
window.removeEventListener('wheel', resetHover);
|
||||
window.removeEventListener('scroll', resetHover);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
{/* Hero Section */}
|
||||
<section
|
||||
id="hero"
|
||||
className="relative isolate min-h-screen flex flex-col justify-center"
|
||||
>
|
||||
{/* ...existing code (pattern SVG + blurred polygon) ... */}
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="absolute inset-x-0 top-0 -z-10 h-256 w-full mask-[radial-gradient(32rem_32rem_at_center,white,transparent)] stroke-gray-900/10 dark:stroke-white/10"
|
||||
>
|
||||
<defs>
|
||||
<pattern
|
||||
x="50%"
|
||||
y={-1}
|
||||
id="hero-pattern"
|
||||
width={200}
|
||||
height={200}
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M.5 200V.5H200" fill="none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<svg x="50%" y={-1} className="overflow-visible fill-gray-100 dark:fill-gray-800">
|
||||
<path
|
||||
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
|
||||
strokeWidth={0}
|
||||
/>
|
||||
</svg>
|
||||
<rect fill="url(#hero-pattern)" width="100%" height="100%" strokeWidth={0} />
|
||||
</svg>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute top-0 right-0 left-1/2 -z-10 -ml-24 transform-gpu overflow-hidden blur-3xl lg:ml-24 xl:ml-48"
|
||||
ref={containerRef}
|
||||
className="min-h-screen flex items-center justify-center relative overflow-hidden bg-black text-white"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
clipPath:
|
||||
'polygon(63.1% 29.5%, 100% 17.1%, 76.6% 3%, 48.4% 0%, 44.6% 4.7%, 54.5% 25.3%, 59.8% 49%, 55.2% 57.8%, 44.4% 57.2%, 27.8% 47.9%, 35.1% 81.5%, 0% 97.7%, 39.2% 100%, 35.2% 81.4%, 97.2% 52.8%, 63.1% 29.5%)',
|
||||
}}
|
||||
className="aspect-801/1036 w-200.25 bg-linear-to-tr from-[#ff80b5] to-[#9089fc] opacity-30 dark:opacity-30"
|
||||
{/* Waves background (reverted settings) */}
|
||||
<Waves
|
||||
className="pointer-events-none"
|
||||
lineColor="#0f172a"
|
||||
backgroundColor="rgba(245, 245, 240, 1)"
|
||||
waveSpeedX={0.02}
|
||||
waveSpeedY={0.01}
|
||||
waveAmpX={40}
|
||||
waveAmpY={20}
|
||||
friction={0.9}
|
||||
tension={0.01}
|
||||
maxCursorMove={120}
|
||||
xGap={12}
|
||||
yGap={36}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Background layers */}
|
||||
<div className="absolute inset-0 -z-30 bg-white dark:bg-gray-950" />
|
||||
<div className="absolute inset-0 -z-20 bg-gradient-to-b from-gray-900/95 via-gray-900/80 to-gray-900 dark:from-gray-900/95 dark:via-gray-900/80 dark:to-gray-900" />
|
||||
<div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(circle_at_30%_20%,rgba(255,255,255,0.18),transparent_65%)] dark:bg-[radial-gradient(circle_at_35%_25%,rgba(255,255,255,0.08),transparent_60%)]" />
|
||||
<div className="pointer-events-none absolute inset-0 -z-10 mix-blend-overlay [background-image:linear-gradient(rgba(255,255,255,0.04)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.04)_1px,transparent_1px)] bg-[size:40px_40px]" />
|
||||
|
||||
{/* Content wrapper */}
|
||||
<div className="mx-auto max-w-7xl px-4 py-12 sm:px-6 sm:py-20 lg:px-8 lg:py-32 flex w-full items-center">
|
||||
<div className="mx-auto max-w-2xl gap-x-8 lg:mx-0 lg:flex lg:max-w-none lg:items-center lg:gap-x-14">
|
||||
<div className="relative w-full lg:max-w-xl lg:shrink-0 xl:max-w-2xl">
|
||||
<h1 className="text-3xl font-semibold tracking-tight text-pretty text-white sm:text-4xl md:text-5xl lg:text-6xl xl:text-7xl">
|
||||
Profit Planet
|
||||
</h1>
|
||||
<p className="mt-3 text-lg italic text-gray-300 sm:mt-4 sm:text-xl md:text-2xl">
|
||||
Building a Community that will bring change
|
||||
</p>
|
||||
<p className="mt-6 text-base font-medium text-pretty text-gray-400 sm:mt-8 sm:text-lg md:text-xl lg:max-w-none">
|
||||
Profit Planet is a platform building a vibrant community where members access diverse services and products from within. Users enjoy benefits like cashback and discounts, fostering a smart, rewarding ecosystem.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-col gap-4 sm:mt-10 sm:flex-row sm:items-center sm:gap-x-6">
|
||||
<h1 className="z-10">
|
||||
<a
|
||||
href="/shop"
|
||||
className="rounded-md bg-indigo-500 px-4 py-3 text-base font-semibold text-white shadow-xs hover:bg-indigo-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 text-center sm:px-3.5 sm:py-2.5 sm:text-sm"
|
||||
onMouseEnter={() => setIsHover(true)}
|
||||
onMouseLeave={() => setIsHover(false)}
|
||||
onClick={handleLoginClick}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
Shop
|
||||
</a>
|
||||
<a href="/login" className="text-base font-semibold text-white text-center sm:text-sm/6">
|
||||
Login <span aria-hidden="true">→</span>
|
||||
<SplitText
|
||||
key={isHover ? 'login' : 'profit-planet'}
|
||||
text={isHover ? 'LOGIN' : 'PROFIT PLANET'}
|
||||
tag="span"
|
||||
className={`text-9xl md:text-9xl font-bold transition-colors duration-300 ${
|
||||
isHover ? 'text-black' : 'text-gray-500'
|
||||
}`}
|
||||
delay={100}
|
||||
duration={0.6}
|
||||
ease="power3.out"
|
||||
splitType="chars"
|
||||
from={{ opacity: 0, y: 40 }}
|
||||
to={{ opacity: 1, y: 0 }}
|
||||
threshold={0.1}
|
||||
rootMargin="-100px"
|
||||
textAlign="center"
|
||||
/>
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
<Crosshair containerRef={containerRef} color="#0f172a" />
|
||||
</div>
|
||||
</div>
|
||||
{/* Mobile-hidden image gallery */}
|
||||
<div className="hidden lg:flex mt-8 lg:mt-0 justify-end gap-4 xl:gap-8">
|
||||
<div className="flex flex-col space-y-4 xl:space-y-8">
|
||||
<div className="relative w-32 xl:w-44">
|
||||
<img
|
||||
alt=""
|
||||
src="https://images.unsplash.com/photo-1557804506-669a67965ba0?auto=format&fit=crop&h=528&q=80"
|
||||
className="aspect-2/3 w-full rounded-xl bg-gray-700/5 object-cover shadow-lg"
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-xl ring-1 ring-white/10 ring-inset" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 xl:space-y-8 pt-8 xl:pt-16">
|
||||
<div className="relative w-32 xl:w-44">
|
||||
<img
|
||||
alt=""
|
||||
src="https://images.unsplash.com/photo-1485217988980-11786ced9454?auto=format&fit=crop&h=528&q=80"
|
||||
className="aspect-2/3 w-full rounded-xl bg-gray-700/5 object-cover shadow-lg"
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-xl ring-1 ring-white/10 ring-inset" />
|
||||
</div>
|
||||
<div className="relative w-32 xl:w-44">
|
||||
<img
|
||||
alt=""
|
||||
src="https://images.unsplash.com/photo-1559136555-9303baea8ebd?auto=format&fit=crop&crop=focalpoint&fp-x=.4&w=396&h=528&q=80"
|
||||
className="aspect-2/3 w-full rounded-xl bg-gray-700/5 object-cover shadow-lg"
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-xl ring-1 ring-white/10 ring-inset" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 xl:space-y-8">
|
||||
<div className="relative w-32 xl:w-44">
|
||||
<img
|
||||
alt=""
|
||||
src="https://images.unsplash.com/photo-1670272504528-790c24957dda?auto=format&fit=crop&crop=left&w=400&h=528&q=80"
|
||||
className="aspect-2/3 w-full rounded-xl bg-gray-700/5 object-cover shadow-lg"
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-xl ring-1 ring-white/10 ring-inset" />
|
||||
</div>
|
||||
<div className="relative w-32 xl:w-44">
|
||||
<img
|
||||
alt=""
|
||||
src="https://images.unsplash.com/photo-1670272505284-8faba1c31f7d?auto=format&fit=crop&h=528&q=80"
|
||||
className="aspect-2/3 w-full rounded-xl bg-gray-700/5 object-cover shadow-lg"
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 rounded-xl ring-1 ring-white/10 ring-inset" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user