diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..ad523c9 --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "lytsup-dev", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "port": 3000 + } + ] +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..dfb5d1e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,29 @@ +# Dependency directories +node_modules/ + +# Next.js build output +.next/ +out/ + +# Local env files +.env*.local + +# Debug logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Git files +.git +.gitignore +.gitattributes + +# Editor settings +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..79458fb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# Stage 1: Install dependencies only when needed +FROM node:20-alpine AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json package-lock.json* ./ +RUN npm ci --legacy-peer-deps + +# Stage 2: Rebuild the source code only when needed +FROM node:20-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Disable Next.js telemetry data compilation +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN npm run build + +# Stage 3: Production image, copy all the files and run next +FROM node:20-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +# hostname binds to all network interfaces, allowing container ports to map correctly +ENV HOSTNAME="0.0.0.0" + +# server.js is created by next build from the standalone output +CMD ["node", "server.js"] diff --git a/next.config.ts b/next.config.ts index e9ffa30..68a6c64 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "standalone", }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index f8d4d18..731e472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,34 @@ "name": "lytsup", "version": "0.1.0", "dependencies": { + "@gsap/react": "^2.1.2", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.6.1", + "@tsparticles/engine": "^4.3.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^12.42.2", + "gsap": "^3.15.0", + "lenis": "^1.3.25", + "lottie-react": "^2.4.1", + "lucide-react": "^0.468.0", + "motion": "^12.42.2", "next": "16.2.10", + "next-themes": "^0.4.6", + "ogl": "^1.0.11", "react": "19.2.4", - "react-dom": "19.2.4" + "react-countup": "^6.5.3", + "react-dom": "19.2.4", + "react-fast-marquee": "^1.6.5", + "react-parallax-tilt": "^1.7.333", + "react-tsparticles": "^2.12.2", + "react-use": "^17.6.1", + "split-type": "^0.3.4", + "swiper": "^14.0.5", + "tailwind-merge": "^3.6.0", + "tailwindcss-animate": "^1.0.7", + "three": "^0.185.1", + "tsparticles": "^4.3.2" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -19,6 +44,9 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.2.10", + "eslint-config-prettier": "^10.1.8", + "prettier": "^3.9.5", + "prettier-plugin-tailwindcss": "^0.8.0", "tailwindcss": "^4", "typescript": "^5" } @@ -228,6 +256,15 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", @@ -276,6 +313,12 @@ "node": ">=6.9.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.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -453,6 +496,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "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/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1069,7 +1122,6 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1083,6 +1135,24 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mediapipe/tasks-vision": { + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", + "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==", + "license": "Apache-2.0" + }, + "node_modules/@monogrid/gainmap-js": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz", + "integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==", + "license": "MIT", + "dependencies": { + "promise-worker-transferable": "^1.0.4" + }, + "peerDependencies": { + "three": ">= 0.159.0" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -1306,6 +1376,94 @@ "node": ">=12.4.0" } }, + "node_modules/@react-three/drei": { + "version": "10.7.7", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz", + "integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mediapipe/tasks-vision": "0.10.17", + "@monogrid/gainmap-js": "^3.0.6", + "@use-gesture/react": "^10.3.1", + "camera-controls": "^3.1.0", + "cross-env": "^7.0.3", + "detect-gpu": "^5.0.56", + "glsl-noise": "^0.0.0", + "hls.js": "^1.5.17", + "maath": "^0.10.8", + "meshline": "^3.3.1", + "stats-gl": "^2.2.8", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.8.3", + "three-stdlib": "^2.35.6", + "troika-three-text": "^0.52.4", + "tunnel-rat": "^0.1.2", + "use-sync-external-store": "^1.4.0", + "utility-types": "^3.11.0", + "zustand": "^5.0.1" + }, + "peerDependencies": { + "@react-three/fiber": "^9.0.0", + "react": "^19", + "react-dom": "^19", + "three": ">=0.159" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz", + "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/webxr": "*", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "its-fine": "^2.0.0", + "react-use-measure": "^2.1.7", + "scheduler": "^0.27.0", + "suspend-react": "^0.1.3", + "use-sync-external-store": "^1.4.0", + "zustand": "^5.0.3" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-file-system": ">=11.0", + "expo-gl": ">=11.0", + "react": ">=19 <19.3", + "react-dom": ">=19 <19.3", + "react-native": ">=0.78", + "three": ">=0.156" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1605,6 +1763,727 @@ "tailwindcss": "4.3.2" } }, + "node_modules/@tsparticles/animation-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/animation-utils/-/animation-utils-4.3.2.tgz", + "integrity": "sha512-x4i/F24myZsk/qJEkREbOpzoWJtAbW8cL0Pan0Y5SdmKkG0dyLHw/fTG2uttIA3eeXhPH8JN648pSZMvbNJItw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/basic": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/basic/-/basic-4.3.2.tgz", + "integrity": "sha512-8XTjwZqrxdfT1WUBvhjip1uksdeYBG0k0mr87vebIs7EwCSO//Rau1XYrHE69mSBwYvIqEhLNcwAixHbypG44Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-blend": "4.3.2", + "@tsparticles/plugin-hex-color": "4.3.2", + "@tsparticles/plugin-hsl-color": "4.3.2", + "@tsparticles/plugin-move": "4.3.2", + "@tsparticles/plugin-rgb-color": "4.3.2", + "@tsparticles/shape-circle": "4.3.2", + "@tsparticles/updater-opacity": "4.3.2", + "@tsparticles/updater-out-modes": "4.3.2", + "@tsparticles/updater-paint": "4.3.2", + "@tsparticles/updater-size": "4.3.2" + } + }, + "node_modules/@tsparticles/canvas-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/canvas-utils/-/canvas-utils-4.3.2.tgz", + "integrity": "sha512-SN0O1sFhaCH5NfkZK9KawY9dK8YhiDqwPbqv7/Y9/oKmdreXQ2GSkaPOHOGmVDAkl3LYTI8Ip/JfNZaJ1GxDJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/engine": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/engine/-/engine-4.3.2.tgz", + "integrity": "sha512-k4U7uahhxt7bPjEPHu+qrJXjHRysy/QMN+2/KK4d0/V7CElt5/oeR+H9QB95Osy/PtUCCi6EH/LS7k0443qlJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/@tsparticles/interaction-external-attract": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-attract/-/interaction-external-attract-4.3.2.tgz", + "integrity": "sha512-dMODX9wNrro+E4mjooFSpA9GsGTecqaNzBaSxHY4wUd4Vqlwms3nGfcLs8mr5C6oOz9+i8ADClN30HbcK1n6hw==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-bounce": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-bounce/-/interaction-external-bounce-4.3.2.tgz", + "integrity": "sha512-xRa4HT1TZ2T+TdAr3Y6P99hDC3vfvz04QPFr7QaZxnkZDpqeXSzT3VdWa4+PhWnV8s+VzmDI4MWqk5b6dfyq4w==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-bubble": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-bubble/-/interaction-external-bubble-4.3.2.tgz", + "integrity": "sha512-DkFnv+ydwe/ZFsex7S1fmBfaDNFXIZCbBxEmYWylKkiWcm1yTe5A5YzqkEdQZC7tglppf3Jdou9cQXzwoD5HrA==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-connect": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-connect/-/interaction-external-connect-4.3.2.tgz", + "integrity": "sha512-hZI01JrXCJLVO4l8jq5nB/6U+ghE7SjOoqbUqR8sM/PiNA6jQupibweyhvK8R/4Rh+soAYZHZ1uBFxXwpXOb5A==", + "license": "MIT", + "dependencies": { + "@tsparticles/canvas-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-destroy": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-destroy/-/interaction-external-destroy-4.3.2.tgz", + "integrity": "sha512-HNb4zbZQwMIurFGT/TeyJ2NVlIrBRgVuc93mRIWIA1vOdFZMEkkE1mgP55sOii/vASp/UAB+dXaaJP37gaC46w==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-drag": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-drag/-/interaction-external-drag-4.3.2.tgz", + "integrity": "sha512-vIxKHosmGjSDV7JVwJ3lpGjzJGFRNtgzEtIXrVh/QtZv0kSEmsj1+c5Xw43Oxz1xO6eO68DYFgPlixf4egxU6Q==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-grab": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-grab/-/interaction-external-grab-4.3.2.tgz", + "integrity": "sha512-WITZJ4u36yPEcnVljpfWhMc8aJn07Zz8Eq7u5umERBTKF0QdLrMWseM3HgSv7AOV04fp86CDciVLOa2Ivdwcrw==", + "license": "MIT", + "dependencies": { + "@tsparticles/canvas-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-parallax": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-parallax/-/interaction-external-parallax-4.3.2.tgz", + "integrity": "sha512-sdP7OI7pMbKfvI819M9Ini3Lm8/OR3Lt3jXWb7gP8bDUxZ0zbWa5gmkZjWwyW8bFzBMssjSY1J1GhkZzTMv6Yg==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-pause": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-pause/-/interaction-external-pause-4.3.2.tgz", + "integrity": "sha512-nuIoXWAFb1wVNk29iiGJHjN6XaPlUJjsLsF1Tl9QhKAlXvrXk08zZsU7QVJ4bzMhyQ94IZdzNAVPd4cvOhVrHQ==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-push": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-push/-/interaction-external-push-4.3.2.tgz", + "integrity": "sha512-2buFHr/73pdHMXaFPUFjdY0STWysvEGJpSYSAwmvt58WHVbM1z2XwBMpozQwRe15UNDw3H5ddeiVkIKuqwjZlQ==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-remove": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-remove/-/interaction-external-remove-4.3.2.tgz", + "integrity": "sha512-iJW2VuguF+cIUEHnrdFcVWTZMJnZ7j0q74Jut3FQy4XyGTzD2P8UCswbfpnZHQssGIPMupp7bHNIXOOchLTrtw==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-repulse": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-repulse/-/interaction-external-repulse-4.3.2.tgz", + "integrity": "sha512-o0RyOjQROCqpJzjaMcNFew9afvJdKJB94E5XYoJCYK6Pwhp0BFMariXe91/yTdguIYrjUwJMQ5bvyBejP4Ygzw==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-slow": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-slow/-/interaction-external-slow-4.3.2.tgz", + "integrity": "sha512-wMdbZ6xD0QQJ1W077iJvHyCJ2kKHpE4+/xu0B7ffTHesn6vYv4K+CWUVvBTwVC5sJVq4Q2Av974qOfxIhMY7ZQ==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-external-trail": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-external-trail/-/interaction-external-trail-4.3.2.tgz", + "integrity": "sha512-lpmkLRtUF+00dGjQnwXyotR5R5XHipQxAk/FrOsVRNNi2+IGazJE7+LmJHDJdtI6GoFwfC+uriwTqS67MlSWfA==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-particles-attract": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-attract/-/interaction-particles-attract-4.3.2.tgz", + "integrity": "sha512-X2GUryt0e5tkeQmIAt7QDqiq3OtNXE72QrM2a+T8E3T0Cvq9/UqrOtlCm6VKlel9OV2NAUi3BH84oCvOcqfYFg==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-particles-collisions": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-collisions/-/interaction-particles-collisions-4.3.2.tgz", + "integrity": "sha512-dLBgNXAdRJN2bFTYPEK3lvSeJFWOT1gv22aGOBatPPxLZ+mH5w3oH8LE2noa7tBpiyG4tVEzMzZUIdp57SemPg==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/interaction-particles-links": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/interaction-particles-links/-/interaction-particles-links-4.3.2.tgz", + "integrity": "sha512-h0bZcpj2tFItovtzCMyHCqu2Fhz+/N9oXQctvmD9sf7C7eya26ZsISEgPqqFIAvdwLYhmQTk5fns4d/6Y7yRWg==", + "license": "MIT", + "dependencies": { + "@tsparticles/canvas-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-absorbers": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-absorbers/-/plugin-absorbers-4.3.2.tgz", + "integrity": "sha512-g1CeYTQi7EvBKECHr/3uo5YKS9+YfefD2l75rwzdFEwALCEFOOjTINBrwwx7UWjYSn1Hpe0oIRMVQ02wXFziFw==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + }, + "peerDependenciesMeta": { + "@tsparticles/plugin-interactivity": { + "optional": true + } + } + }, + "node_modules/@tsparticles/plugin-blend": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-blend/-/plugin-blend-4.3.2.tgz", + "integrity": "sha512-5iUDwzvcOtiaFvsbL3y+UzneGrlNwITB8HlNCfjp675wrg9xh7ILHa3/ZYHii1qC8gkKNva0w2N+wET9n+XWBQ==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-easing-quad": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-easing-quad/-/plugin-easing-quad-4.3.2.tgz", + "integrity": "sha512-yrh6KSD+UEY8aW3hnfzVL84neUwCZ1ZqU/cj5jTHDQ7zFbYu8QA6zhR30kYHI645VmCaMjp+zuZ2ii6rwXsVIQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-emitters": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-emitters/-/plugin-emitters-4.3.2.tgz", + "integrity": "sha512-wKUw3lkGwUOBNtRC2XuzVLwh6Nur112GG9T+O43quio+J5YjOMGEsfeFaWL3R7kgrj0d+VvQlJESsyZeccgQlg==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2" + }, + "peerDependenciesMeta": { + "@tsparticles/plugin-interactivity": { + "optional": true + } + } + }, + "node_modules/@tsparticles/plugin-emitters-shape-circle": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-emitters-shape-circle/-/plugin-emitters-shape-circle-4.3.2.tgz", + "integrity": "sha512-MEZcQ8sMx9nHPIlO5+pC6HXFbxtL54Kq9OrVNxsvsKCf03v2Rnf63jlqXeWkxx0XYUNpYSumc39IzQuaouyXPw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-emitters": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-emitters-shape-square": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-emitters-shape-square/-/plugin-emitters-shape-square-4.3.2.tgz", + "integrity": "sha512-sXYep9YCfwsAC/efjUD3jnU0QJ0R0dOcU6oKXLWieMrAmHoa/mP7aN/ZEUntkta8cmi95bswUc8rshBez3DplQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/plugin-emitters": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-hex-color": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-hex-color/-/plugin-hex-color-4.3.2.tgz", + "integrity": "sha512-uRNSHx/d3hByVYAwPHKKoKxy7s0QujhNUpsgDuL92JODKCjCFm8SudbkW3STSXVznhY5bCcu4iAF+7zKVgVXCg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-hsl-color": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-hsl-color/-/plugin-hsl-color-4.3.2.tgz", + "integrity": "sha512-kvRux8WnToyy9Dr8ROYvNDnE0HPW5h7x3wBROOghlazGYvYPO4nJBFvcmvvISRF6Ghsm9bzY255Qun2vACv2tA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-interactivity": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-interactivity/-/plugin-interactivity-4.3.2.tgz", + "integrity": "sha512-GIOiyrSn2Zf+cWxLPeUeYG4qjSeGzDcocbSiLcAaPPjZ5J+D0LOBsrrb6qvqMvUj0qCkjKF4cIf7OgznhkdZkw==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-move": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-move/-/plugin-move-4.3.2.tgz", + "integrity": "sha512-8nSDxWhxVWl9JzX/fSZwz5ZVzEMMFJLF3vizjfag2zE+jc4Nidfs9ZOPrD4yxpKSfZiv1ZwJpoOhT2ch4sVTug==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/plugin-rgb-color": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/plugin-rgb-color/-/plugin-rgb-color-4.3.2.tgz", + "integrity": "sha512-/4xZBlEWqqG7VscJzC2VimA62fxDboe4XO1+m8LeM2ytxrhEpNk14TeBJt+zCc8jBkFjpKOdmTZ0VYyuK4FtDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/shape-circle": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-circle/-/shape-circle-4.3.2.tgz", + "integrity": "sha512-IOStpEafsK9vIZsXtrO1zpqF3pW5ydstD8db4omioAnoYFfKwbgyfkQz9l4pDnJy8636RbnQNVbYGlyxKuJ0cw==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/shape-emoji": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-emoji/-/shape-emoji-4.3.2.tgz", + "integrity": "sha512-et8OLZpt1fJsBBEhb0NHp++MeMF3Q3VZq1JMzdaI6i1rCm8h8yfEbi96Izz+e1OFYKpY/qZfyVxrLnW33VYFJg==", + "license": "MIT", + "dependencies": { + "@tsparticles/canvas-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/shape-image": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-image/-/shape-image-4.3.2.tgz", + "integrity": "sha512-EtKkH7DMI+2rcF9n5VCJWKcyI/jX+B/OfucM/9MThG5qApqLBzfZvxRQRFG2agZKIHByGmXtfZyou4y2XPuq9g==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/shape-line": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-line/-/shape-line-4.3.2.tgz", + "integrity": "sha512-ph8q+9vx8BxYb1JToPde6v0Vejfg+hPGEWYhD5J5orrLcxqjmGimbRd+ycFV+fgHcmFDzfzlVUtiR7U+F3mgNg==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/shape-polygon": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-polygon/-/shape-polygon-4.3.2.tgz", + "integrity": "sha512-ebpHYQlk0khRXFtG1WWQIQSIedb23OEICWuhTAmh87lVkFkNeU5synyS6UqxSOXda098BiRnO++P69Sfq12E5w==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/shape-square": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-square/-/shape-square-4.3.2.tgz", + "integrity": "sha512-U3q5TdvDclXSAM6ott4evZaWt00o+mEmO0RUsIGZR4W0DdaaZTa0zmeitQnM/wJnkwpXUqVWF3H88FS1MOy9ag==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/shape-star": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-star/-/shape-star-4.3.2.tgz", + "integrity": "sha512-i8AquyuaJ3GH3qsWIepVbsdxCM0Tl0ZFSmJni8AC+ly2Py7+Ig/uJpxM9oC5YkNdcAU6vosMgAS3xUobVQDvsg==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/shape-text": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/shape-text/-/shape-text-4.3.2.tgz", + "integrity": "sha512-1r57n5WYWIToasVVPHfpLfY5RcuY1iP+Aa2fq8fWmtJklHXUKH4j3lMSJjTsejm/ieYANlZGPmPaX1bTO6INXw==", + "license": "MIT", + "dependencies": { + "@tsparticles/canvas-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/slim": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/slim/-/slim-4.3.2.tgz", + "integrity": "sha512-VDCC1xod/Ak9Pn+OD/5AT6y3ccPR3n+EHQBZJKin81yYE/+POfNZ+tDSg/cMmrNBW6MFobKVJpp2kO0AoYFvrw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/basic": "4.3.2", + "@tsparticles/engine": "4.3.2", + "@tsparticles/interaction-external-attract": "4.3.2", + "@tsparticles/interaction-external-bounce": "4.3.2", + "@tsparticles/interaction-external-bubble": "4.3.2", + "@tsparticles/interaction-external-connect": "4.3.2", + "@tsparticles/interaction-external-destroy": "4.3.2", + "@tsparticles/interaction-external-grab": "4.3.2", + "@tsparticles/interaction-external-parallax": "4.3.2", + "@tsparticles/interaction-external-pause": "4.3.2", + "@tsparticles/interaction-external-push": "4.3.2", + "@tsparticles/interaction-external-remove": "4.3.2", + "@tsparticles/interaction-external-repulse": "4.3.2", + "@tsparticles/interaction-external-slow": "4.3.2", + "@tsparticles/interaction-particles-attract": "4.3.2", + "@tsparticles/interaction-particles-collisions": "4.3.2", + "@tsparticles/interaction-particles-links": "4.3.2", + "@tsparticles/plugin-easing-quad": "4.3.2", + "@tsparticles/plugin-interactivity": "4.3.2", + "@tsparticles/shape-emoji": "4.3.2", + "@tsparticles/shape-image": "4.3.2", + "@tsparticles/shape-line": "4.3.2", + "@tsparticles/shape-polygon": "4.3.2", + "@tsparticles/shape-square": "4.3.2", + "@tsparticles/shape-star": "4.3.2", + "@tsparticles/updater-life": "4.3.2", + "@tsparticles/updater-paint": "4.3.2", + "@tsparticles/updater-rotate": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-destroy": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-destroy/-/updater-destroy-4.3.2.tgz", + "integrity": "sha512-aTkmklndTYdUWwCTswn2gUxfersRQMrZa++pXPc9EgamoTnsTAcWGNb4x89sSviwjSt5Oyw78LadZZZfRvX2Ew==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-life": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-life/-/updater-life-4.3.2.tgz", + "integrity": "sha512-RL3803nFKsCsGQavLSZyEiFWhto9R4+QQ/tsqt8PNmHOC7nmI96ZVGVXDknaSuCM4OwuwrDawIdrZUv6OSEFSQ==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-opacity": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-opacity/-/updater-opacity-4.3.2.tgz", + "integrity": "sha512-Mw4WN/E+ZQQkL683/oyLkebDMpSpbqDRgx2GhMqWOQNTDby5RYG6wSrq0cGtg0aLZmkoYDYLz5Al7wjPfX0/LA==", + "license": "MIT", + "dependencies": { + "@tsparticles/animation-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-out-modes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-out-modes/-/updater-out-modes-4.3.2.tgz", + "integrity": "sha512-lGiYbrFGaAmBakCrEf3Xq5W+LzDkvfINav0MGxCe/acf7u3yvjOUkVH0Co1zc/ju43nU5U2ZO88Anqk4xKtoPA==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-paint": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-paint/-/updater-paint-4.3.2.tgz", + "integrity": "sha512-cp020EtLL0zjsILL1w07r8n8wmwzziuHwtQ0GqGaGq+SikrdoK6yT/sXbyAylJqDypNaloVy3NZMq4wNQtxLOg==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-roll": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-roll/-/updater-roll-4.3.2.tgz", + "integrity": "sha512-5PG/uZx8EcwKV2WeyZ9ww4DyE/HjUcuF4loIAJSt2qNP66pwzMnZ0LqD+YOCRWhhm+/ESry3JNHaAWmJY6yp/A==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-rotate": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-rotate/-/updater-rotate-4.3.2.tgz", + "integrity": "sha512-vDp7sg+FQQ9NRY6Nh2Joccd5oqmv6W1Y0L56AWrxjc6wk29IRuVeRo1QuoqRLzqq/xLJBhsbkyKXZG0wiE8Blg==", + "license": "MIT", + "dependencies": { + "@tsparticles/animation-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-size": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-size/-/updater-size-4.3.2.tgz", + "integrity": "sha512-U7YtYqH0MvALOS4Nr33soOFJ63n8qS9Xujl9BcNZnbmRBf4xgOnTC1TztMvGO19GANTBwq4QKsKQiwFlwAk4qw==", + "license": "MIT", + "dependencies": { + "@tsparticles/animation-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-tilt": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-tilt/-/updater-tilt-4.3.2.tgz", + "integrity": "sha512-iPI0Fc86Nyuxvm0Dx+aPOZ6d7fDZ4SNk+TulCbMQz9Zf5/p6nYB9CDjAuvmTDCLqzZCMUtrwbbzJfLKR7R5Gog==", + "license": "MIT", + "dependencies": { + "@tsparticles/animation-utils": "4.3.2" + }, + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-twinkle": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-twinkle/-/updater-twinkle-4.3.2.tgz", + "integrity": "sha512-zjk9sStqw4fZMOa9IzmBILNG3bavTMH2vaaVzNjj6s11lstoQVch6UjQKU0T7E8ugrtdKWptIcwvBajFeNpeDQ==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tsparticles/updater-wobble": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tsparticles/updater-wobble/-/updater-wobble-4.3.2.tgz", + "integrity": "sha512-nRKKFkjUgFoQG+WpDASlqj4hUPwafgHb9pLjuqAN7ko0udvajspF01nuXFgDznEIVcVU9pXfq9KfQPiU+xyjiQ==", + "license": "MIT", + "peerDependencies": { + "@tsparticles/engine": "4.3.2" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", @@ -1616,6 +2495,12 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/draco3d": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", + "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -1623,6 +2508,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1647,6 +2538,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.17", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", @@ -1667,6 +2564,41 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.185.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.185.1.tgz", + "integrity": "sha512-db1xTb+EgYF2didW+eudSvVPtn75zo+fGsY8ShQrJY/B5ZBmC2Fiaykv3aImHAlCNEGuMPkPGXBJGLwzu5mC7A==", + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "fflate": "~0.8.2", + "meshoptimizer": "~1.1.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.64.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", @@ -2316,6 +3248,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/@xobotyi/scrollbar-width": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", + "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", @@ -2609,6 +3565,26 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.10.43", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", @@ -2621,6 +3597,15 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/brace-expansion": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", @@ -2679,6 +3664,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.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -2739,6 +3748,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.30001805", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", @@ -2776,12 +3798,33 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "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/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2816,11 +3859,43 @@ "dev": true, "license": "MIT" }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/countup.js": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.10.1.tgz", + "integrity": "sha512-UHW/BsPDgVZfN919D4iu2HPv+jJxZUuaR3EhT0ScFZaD446GiQtkMTgOnOyJXdA2AewdlyUk9UvERlTHTbVpcw==", + "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", @@ -2831,11 +3906,32 @@ "node": ">= 8" } }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2960,6 +4056,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-gpu": { + "version": "5.0.70", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", + "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==", + "license": "MIT", + "dependencies": { + "webgl-constants": "^1.1.1" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2983,6 +4088,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", @@ -3026,6 +4137,15 @@ "node": ">=10.13.0" } }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -3348,6 +4468,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", @@ -3658,7 +4794,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -3705,6 +4840,17 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", + "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" + }, + "node_modules/fastest-stable-stringify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", + "integrity": "sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==", + "license": "MIT" + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -3715,6 +4861,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3795,6 +4947,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/framer-motion": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", + "integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.42.2", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3972,6 +5151,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==", + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3992,6 +5177,12 @@ "dev": true, "license": "ISC" }, + "node_modules/gsap": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", + "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", + "license": "Standard 'no charge' license: https://gsap.com/standard-license." + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4103,6 +5294,38 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hls.js": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz", + "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==", + "license": "Apache-2.0" + }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, + "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", @@ -4113,6 +5336,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", @@ -4140,6 +5369,15 @@ "node": ">=0.8.19" } }, + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "license": "MIT", + "dependencies": { + "css-in-js-utils": "^3.1.0" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4441,6 +5679,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", @@ -4597,7 +5841,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": { @@ -4618,6 +5861,18 @@ "node": ">= 0.4" } }, + "node_modules/its-fine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", + "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.9" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, "node_modules/jiti": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", @@ -4628,6 +5883,12 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4751,6 +6012,37 @@ "node": ">=0.10" } }, + "node_modules/lenis": { + "version": "1.3.25", + "resolved": "https://registry.npmjs.org/lenis/-/lenis-1.3.25.tgz", + "integrity": "sha512-mOKxayErlaONK8fm4LN3XNd99Qu4plTpn9h9qf8wxzjGrJDzuD84FYzZ81HCd6ZsWp++VWVwOzL286Pf2s2u4A==", + "license": "MIT", + "workspaces": [ + "packages/*", + "playground", + "playground/*" + ], + "funding": { + "type": "github", + "url": "https://github.com/sponsors/darkroomengineering" + }, + "peerDependencies": { + "@nuxt/kit": ">=3.0.0", + "react": ">=17.0.0", + "vue": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "react": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4765,6 +6057,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -5074,6 +6375,25 @@ "loose-envify": "cli.js" } }, + "node_modules/lottie-react": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lottie-react/-/lottie-react-2.4.1.tgz", + "integrity": "sha512-LQrH7jlkigIIv++wIyrOYFLHSKQpEY4zehPicL9bQsrt1rnoKRYCYgpCUe5maqylNtacy58/sQDZTkwMcTRxZw==", + "license": "MIT", + "dependencies": { + "lottie-web": "^5.10.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lottie-web": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz", + "integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -5084,6 +6404,25 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/maath": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", + "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.134.0", + "three": ">=0.134.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -5104,6 +6443,12 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5114,6 +6459,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": "1.1.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz", + "integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==", + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -5151,6 +6511,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/motion": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.42.2.tgz", + "integrity": "sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.42.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz", + "integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5158,6 +6559,26 @@ "dev": true, "license": "MIT" }, + "node_modules/nano-css": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.6.2.tgz", + "integrity": "sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==", + "license": "Unlicense", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "css-tree": "^1.1.2", + "csstype": "^3.1.2", + "fastest-stable-stringify": "^2.0.2", + "inline-style-prefixer": "^7.0.1", + "rtl-css-js": "^1.16.1", + "stacktrace-js": "^2.0.2", + "stylis": "^4.3.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, "node_modules/nanoid": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", @@ -5252,6 +6673,16 @@ } } }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -5432,6 +6863,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ogl": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz", + "integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==", + "license": "Unlicense" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5527,7 +6964,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" @@ -5598,6 +7034,12 @@ "node": "^10 || ^12 || >=14" } }, + "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", @@ -5608,6 +7050,111 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.8.0.tgz", + "integrity": "sha512-V8ITGH87yuBDF6JpEZTOVlUz/saAwqb8f3HRgUj8Lh+tGCcrmorhsLpYqzygwFwK0PE2Ib6Mv3M7T/uE2tZV1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "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", @@ -5660,6 +7207,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-countup": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz", + "integrity": "sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==", + "license": "MIT", + "dependencies": { + "countup.js": "^2.8.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-dom": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", @@ -5672,6 +7231,16 @@ "react": "^19.2.4" } }, + "node_modules/react-fast-marquee": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/react-fast-marquee/-/react-fast-marquee-1.6.5.tgz", + "integrity": "sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.8.0 || ^18.0.0", + "react-dom": ">= 16.8.0 || ^18.0.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5679,6 +7248,94 @@ "dev": true, "license": "MIT" }, + "node_modules/react-parallax-tilt": { + "version": "1.7.333", + "resolved": "https://registry.npmjs.org/react-parallax-tilt/-/react-parallax-tilt-1.7.333.tgz", + "integrity": "sha512-sW2rnqjD3RcyHb3KLm2XvEJaBU7xH6exAzQAwlb20l6tUhhv14bpB05EcxXwSy3f7mCBeDdCdUy1uHFEb+rfMQ==", + "license": "MIT", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-tsparticles": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/react-tsparticles/-/react-tsparticles-2.12.2.tgz", + "integrity": "sha512-/nrEbyL8UROXKIMXe+f+LZN2ckvkwV2Qa+GGe/H26oEIc+wq/ybSG9REDwQiSt2OaDQGu0MwmA4BKmkL6wAWcA==", + "deprecated": "@tsparticles/react is the new version, please use that", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "tsparticles-engine": "^2.12.0" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/react-universal-interface": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", + "integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==", + "peerDependencies": { + "react": "*", + "tslib": "*" + } + }, + "node_modules/react-use": { + "version": "17.6.1", + "resolved": "https://registry.npmjs.org/react-use/-/react-use-17.6.1.tgz", + "integrity": "sha512-uibb3pgzV4LFsYPHyXYGu7dD2+pyk/ZJlPH+AizBR3zolqPWyCleKcWWbUQaKSKfydwgnQ3ymGm1Ab3/saGHCA==", + "license": "Unlicense", + "dependencies": { + "@types/js-cookie": "^3.0.0", + "@xobotyi/scrollbar-width": "^1.9.5", + "copy-to-clipboard": "^3.3.1", + "fast-deep-equal": "^3.1.3", + "fast-shallow-equal": "^1.0.0", + "js-cookie": "^3.0.0", + "nano-css": "^5.6.2", + "react-universal-interface": "^0.6.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.1.0", + "set-harmonic-interval": "^1.0.1", + "throttle-debounce": "^3.0.1", + "ts-easing": "^0.2.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", + "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5723,6 +7380,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "2.0.0-next.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", @@ -5778,6 +7450,15 @@ "node": ">=0.10.0" } }, + "node_modules/rtl-css-js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", + "integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5863,6 +7544,18 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5907,6 +7600,15 @@ "node": ">= 0.4" } }, + "node_modules/set-harmonic-interval": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", + "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==", + "license": "Unlicense", + "engines": { + "node": ">=6.9" + } + }, "node_modules/set-proto": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", @@ -5984,7 +7686,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" @@ -5997,7 +7698,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" @@ -6079,6 +7779,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6088,6 +7797,12 @@ "node": ">=0.10.0" } }, + "node_modules/split-type": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/split-type/-/split-type-0.3.4.tgz", + "integrity": "sha512-otEk9vnD8qwfLsk3Lx0gz+qRkNIJCx0mlyL47ImP/DjMuV39d75Lpfwjn9fHteDRz0aoOblSzQjSNT9+Sswxcg==", + "license": "ISC" + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -6095,6 +7810,77 @@ "dev": true, "license": "MIT" }, + "node_modules/stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "license": "MIT", + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, + "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", @@ -6269,6 +8055,12 @@ } } }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6295,6 +8087,44 @@ "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/swiper": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-14.0.5.tgz", + "integrity": "sha512-SMSKS0sj+lPIFzngfIKb5cyBtzmAuJw7rXgXdyYiDReb+pSpJXuNljHYgT4DXa5Stnl2Ew2/Lam5ipv4PIqZ7A==", + "funding": [ + { + "type": "custom", + "url": "https://sponsors.nolimits4web.com" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nolimits4web" + } + ], + "license": "MIT", + "engines": { + "node": ">= 4.7.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", @@ -6302,6 +8132,15 @@ "dev": true, "license": "MIT" }, + "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.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", @@ -6316,6 +8155,53 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/three": { + "version": "0.185.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz", + "integrity": "sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==", + "license": "MIT" + }, + "node_modules/three-mesh-bvh": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz", + "integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==", + "license": "MIT", + "peerDependencies": { + "three": ">= 0.159.0" + } + }, + "node_modules/three-stdlib": { + "version": "2.36.1", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz", + "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==", + "license": "MIT", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "potpack": "^1.0.1" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/three-stdlib/node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "license": "MIT" + }, + "node_modules/throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -6377,6 +8263,42 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/troika-three-text": { + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz", + "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.52.4", + "troika-worker-utils": "^0.52.0", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.52.4", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz", + "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz", + "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -6390,6 +8312,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-easing": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", + "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==", + "license": "Unlicense" + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -6422,6 +8350,101 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsparticles": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-4.3.2.tgz", + "integrity": "sha512-4seFHUmXUx7vChyA03VArYXkT1ltV6zWq047j7VEtrSZpY+6sh9+bZ8wrTYjjoTrYqZSNW8pHbavqGH9rq8Z7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "license": "MIT", + "dependencies": { + "@tsparticles/engine": "4.3.2", + "@tsparticles/interaction-external-drag": "4.3.2", + "@tsparticles/interaction-external-trail": "4.3.2", + "@tsparticles/plugin-absorbers": "4.3.2", + "@tsparticles/plugin-emitters": "4.3.2", + "@tsparticles/plugin-emitters-shape-circle": "4.3.2", + "@tsparticles/plugin-emitters-shape-square": "4.3.2", + "@tsparticles/shape-text": "4.3.2", + "@tsparticles/slim": "4.3.2", + "@tsparticles/updater-destroy": "4.3.2", + "@tsparticles/updater-roll": "4.3.2", + "@tsparticles/updater-tilt": "4.3.2", + "@tsparticles/updater-twinkle": "4.3.2", + "@tsparticles/updater-wobble": "4.3.2" + } + }, + "node_modules/tsparticles-engine": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/tsparticles-engine/-/tsparticles-engine-2.12.0.tgz", + "integrity": "sha512-ZjDIYex6jBJ4iMc9+z0uPe7SgBnmb6l+EJm83MPIsOny9lPpetMsnw/8YJ3xdxn8hV+S3myTpTN1CkOVmFv0QQ==", + "deprecated": "starting from tsparticles v3 the packages are now moved to @tsparticles/package-name instead of tsparticles-package-name", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true, + "license": "MIT" + }, + "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", @@ -6656,11 +8679,39 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -6813,6 +8864,35 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 3983f9b..1a5deaa 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,34 @@ "lint": "eslint" }, "dependencies": { + "@gsap/react": "^2.1.2", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.6.1", + "@tsparticles/engine": "^4.3.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^12.42.2", + "gsap": "^3.15.0", + "lenis": "^1.3.25", + "lottie-react": "^2.4.1", + "lucide-react": "^0.468.0", + "motion": "^12.42.2", "next": "16.2.10", + "next-themes": "^0.4.6", + "ogl": "^1.0.11", "react": "19.2.4", - "react-dom": "19.2.4" + "react-countup": "^6.5.3", + "react-dom": "19.2.4", + "react-fast-marquee": "^1.6.5", + "react-parallax-tilt": "^1.7.333", + "react-tsparticles": "^2.12.2", + "react-use": "^17.6.1", + "split-type": "^0.3.4", + "swiper": "^14.0.5", + "tailwind-merge": "^3.6.0", + "tailwindcss-animate": "^1.0.7", + "three": "^0.185.1", + "tsparticles": "^4.3.2" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -20,6 +45,9 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.2.10", + "eslint-config-prettier": "^10.1.8", + "prettier": "^3.9.5", + "prettier-plugin-tailwindcss": "^0.8.0", "tailwindcss": "^4", "typescript": "^5" } diff --git a/public/images/1.jpg b/public/images/1.jpg new file mode 100644 index 0000000..163b3da Binary files /dev/null and b/public/images/1.jpg differ diff --git a/public/images/10.jpg b/public/images/10.jpg new file mode 100644 index 0000000..93ac0ef Binary files /dev/null and b/public/images/10.jpg differ diff --git a/public/images/11.jpg b/public/images/11.jpg new file mode 100644 index 0000000..ba30a4c Binary files /dev/null and b/public/images/11.jpg differ diff --git a/public/images/12.jpg b/public/images/12.jpg new file mode 100644 index 0000000..b7c21d0 Binary files /dev/null and b/public/images/12.jpg differ diff --git a/public/images/13.jpg b/public/images/13.jpg new file mode 100644 index 0000000..5c96e4b Binary files /dev/null and b/public/images/13.jpg differ diff --git a/public/images/14.jpg b/public/images/14.jpg new file mode 100644 index 0000000..dd523ef Binary files /dev/null and b/public/images/14.jpg differ diff --git a/public/images/15.jpg b/public/images/15.jpg new file mode 100644 index 0000000..a45fc22 Binary files /dev/null and b/public/images/15.jpg differ diff --git a/public/images/16.jpg b/public/images/16.jpg new file mode 100644 index 0000000..8e7e140 Binary files /dev/null and b/public/images/16.jpg differ diff --git a/public/images/17.jpg b/public/images/17.jpg new file mode 100644 index 0000000..f2bae2e Binary files /dev/null and b/public/images/17.jpg differ diff --git a/public/images/2.jpg b/public/images/2.jpg new file mode 100644 index 0000000..45ffc80 Binary files /dev/null and b/public/images/2.jpg differ diff --git a/public/images/3.jpg b/public/images/3.jpg new file mode 100644 index 0000000..3c5f70c Binary files /dev/null and b/public/images/3.jpg differ diff --git a/public/images/4.jpg b/public/images/4.jpg new file mode 100644 index 0000000..724e22d Binary files /dev/null and b/public/images/4.jpg differ diff --git a/public/images/5.jpg b/public/images/5.jpg new file mode 100644 index 0000000..355c0fc Binary files /dev/null and b/public/images/5.jpg differ diff --git a/public/images/6.jpg b/public/images/6.jpg new file mode 100644 index 0000000..d62bea0 Binary files /dev/null and b/public/images/6.jpg differ diff --git a/public/images/7.jpg b/public/images/7.jpg new file mode 100644 index 0000000..c56a363 Binary files /dev/null and b/public/images/7.jpg differ diff --git a/public/images/8.jpg b/public/images/8.jpg new file mode 100644 index 0000000..d2bd4fd Binary files /dev/null and b/public/images/8.jpg differ diff --git a/public/images/9.jpg b/public/images/9.jpg new file mode 100644 index 0000000..3485ed8 Binary files /dev/null and b/public/images/9.jpg differ diff --git a/public/images/lytsup_app.png b/public/images/lytsup_app.png new file mode 100644 index 0000000..4cfe0fb Binary files /dev/null and b/public/images/lytsup_app.png differ diff --git a/src/app/favicon.ico b/src/app/favicon.ico index 718d6fe..5e5100c 100644 Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css index a2dc41e..3b4ff5d 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,26 +1,240 @@ @import "tailwindcss"; -:root { - --background: #ffffff; - --foreground: #171717; -} +@theme { + --color-primary: #FFC72C; + --color-dark: #090909; + --color-accent-pink: #FF2E88; + --color-accent-purple: #8B3DFF; + --color-accent-orange: #FF6B35; + --color-accent-lime: #C8FF37; + + --font-sora: var(--font-sora), sans-serif; + --font-inter: var(--font-inter), sans-serif; + + --animate-float: float 6s ease-in-out infinite; + --animate-pulse-glow: pulse-glow 2s ease-in-out infinite; + --animate-shimmer: shimmer 2s linear infinite; -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; + @keyframes float { + 0%, 100% { transform: translateY(0px) rotate(0deg); } + 50% { transform: translateY(-10px) rotate(3deg); } + } + @keyframes pulse-glow { + 0%, 100% { opacity: 0.6; filter: drop-shadow(0 0 5px rgba(255, 46, 136, 0.4)); } + 50% { opacity: 1; filter: drop-shadow(0 0 20px rgba(255, 46, 136, 0.8)); } + } + @keyframes shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } } } -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; +:root { + --font-sora: 'Sora', sans-serif; + --font-inter: 'Inter', sans-serif; +} + +/* Custom Scrollbar */ +::-webkit-scrollbar { + width: 8px; +} +::-webkit-scrollbar-track { + background: #090909; +} +::-webkit-scrollbar-thumb { + background: #333333; + border-radius: 4px; +} +::-webkit-scrollbar-thumb:hover { + background: #FFC72C; +} + +/* Base Body Styles */ +html, body { + background-color: #090909; + color: #FFFFFF; + font-family: var(--font-inter), sans-serif; + overflow-x: hidden; + scroll-behavior: smooth; +} + +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-sora), sans-serif; +} + +/* Custom Utilities */ +.glassmorphism { + background: rgba(18, 18, 18, 0.65); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.glassmorphism-light { + background: rgba(255, 255, 255, 0.03); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.glassmorphism-yellow { + background: rgba(255, 199, 44, 0.08); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 199, 44, 0.2); +} + +.glassmorphism-pink { + background: rgba(255, 46, 136, 0.08); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 46, 136, 0.2); +} + +.glassmorphism-purple { + background: rgba(139, 61, 255, 0.08); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(139, 61, 255, 0.2); +} + +.text-glow-yellow { + text-shadow: 0 0 15px rgba(255, 199, 44, 0.6); +} + +.text-glow-pink { + text-shadow: 0 0 15px rgba(255, 46, 136, 0.6); +} + +.text-glow-purple { + text-shadow: 0 0 15px rgba(139, 61, 255, 0.6); +} + +/* Gradient borders */ +.gradient-border-pink-purple { + position: relative; + background: #090909; + background-clip: padding-box; + border: 1px solid transparent; +} +.gradient-border-pink-purple::before { + content: ''; + position: absolute; + top: 0; right: 0; bottom: 0; left: 0; + z-index: -1; + margin: -1px; + border-radius: inherit; + background: linear-gradient(135deg, #FF2E88, #8B3DFF); +} + +.gradient-border-yellow-lime { + position: relative; + background: #090909; + background-clip: padding-box; + border: 1px solid transparent; +} +.gradient-border-yellow-lime::before { + content: ''; + position: absolute; + top: 0; right: 0; bottom: 0; left: 0; + z-index: -1; + margin: -1px; + border-radius: inherit; + background: linear-gradient(135deg, #FFC72C, #C8FF37); +} + +/* Neobrutalist helpers (funky Gen Z theme) */ +.nb { + border: 3px solid #0a0a0a; + box-shadow: 6px 6px 0 0 #0a0a0a; +} +.nb-sm { + border: 2px solid #0a0a0a; + box-shadow: 3px 3px 0 0 #0a0a0a; +} +.nb-lg { + border: 4px solid #0a0a0a; + box-shadow: 10px 10px 0 0 #0a0a0a; +} +.nb-hover { + transition: transform 0.15s ease, box-shadow 0.15s ease; +} +.nb-hover:hover { + transform: translate(-2px, -2px); + box-shadow: 9px 9px 0 0 #0a0a0a; +} +.strike-pink { + position: relative; + display: inline-block; +} +.strike-pink::after { + content: ""; + position: absolute; + left: -2%; + right: -2%; + top: 52%; + height: 6px; + background: #FF2E88; + transform: rotate(-2deg); + border-radius: 4px; +} +.bg-cream { + background-color: #FBF3DD; +} +.bg-deep { + background: radial-gradient(ellipse at 60% 30%, #7d1fa8 0%, #3a1160 45%, #1a0b2e 100%); +} +.bg-night { + background-color: #120a1f; + background-image: radial-gradient(rgba(255, 255, 255, 0.06) 1px, transparent 1px); + background-size: 26px 26px; +} +.text-stroke-dark { + -webkit-text-stroke: 2px #0a0a0a; +} + +/* Custom Swiper Styles */ +.swiper-pagination-bullet { + background: rgba(255, 255, 255, 0.3) !important; + opacity: 1 !important; +} +.swiper-pagination-bullet-active { + background: #FFC72C !important; + width: 24px !important; + border-radius: 4px !important; +} + +/* Mobile view media queries for The Vision and For You sections */ +@media (max-width: 640px) { + #the-vision, + #for-you { + padding-left: 1.25rem !important; + padding-right: 1.25rem !important; + padding-top: 4rem !important; + padding-bottom: 4rem !important; + } + + #the-vision h2, + #for-you h2 { + font-size: 2.5rem !important; + line-height: 1.05 !important; + letter-spacing: -0.02em !important; + word-break: break-word !important; + overflow-wrap: break-word !important; + } + + /* Optimize card spacing and shadow sizes for mobile neobrutalism style */ + #the-vision .nb, + #for-you .nb { + padding: 1rem !important; + font-size: 0.95rem !important; + box-shadow: 4px 4px 0 0 #0a0a0a !important; + border-width: 2px !important; + } + + /* Decrease vertical spacing inside grid for better layout flow on mobile */ + #the-vision .grid, + #for-you .grid { + gap: 1.25rem !important; + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 976eb90..f2fecd6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,20 +1,42 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import type { Metadata, Viewport } from "next"; +import { Sora, Inter } from "next/font/google"; import "./globals.css"; +import Providers from "@/components/common/Providers"; -const geistSans = Geist({ - variable: "--font-geist-sans", +const sora = Sora({ + variable: "--font-sora", subsets: ["latin"], + weight: ["300", "400", "500", "600", "700", "800"], + display: "swap", }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const inter = Inter({ + variable: "--font-inter", subsets: ["latin"], + weight: ["300", "400", "500", "600", "700", "800"], + display: "swap", }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "LytsUp | Earn. Spend. Live More. - Gen Z Social Currency Platform", + description: + "LytsUp is a gamified social currency platform where Gen Z earns LYTs through real-world activities like walking, visiting stores, referrals, playing games, and redeems them for vouchers from premium partner brands.", + keywords: [ + "LytsUp", + "Social Currency", + "Walk to Earn", + "Gen Z rewards", + "Loyalty points", + "Gamified rewards", + "Earn LYTs", + "Play to Earn", + ], + authors: [{ name: "LytsUp Team" }], +}; + +export const viewport: Viewport = { + width: "device-width", + initialScale: 1.0, }; export default function RootLayout({ @@ -25,9 +47,14 @@ export default function RootLayout({ return ( - {children} + + + {children} + + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 3f36f7c..92c24c7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,65 +1,67 @@ -import Image from "next/image"; +import SideNav, { NavSection } from "@/components/layout/SideNav"; +import { + Intro, + TheWhy, + TheProblem, + TheShift, + MeetLyts, + OneDay, + Feelings, + Difference, + TheLoop, +} from "@/components/story/SectionsA"; +import { + ForYou, + ForBusiness, + WhyNow, + HowItWorks, + OldVsNew, + TheVision, + OurBelief, + ThePromise, +} from "@/components/story/SectionsB"; + +const sections: NavSection[] = [ + { id: "intro", label: "Intro" }, + { id: "the-why", label: "The Why" }, + { id: "the-problem", label: "The Problem" }, + { id: "the-shift", label: "The Shift" }, + { id: "lyts", label: "Meet LYTs" }, + { id: "one-day", label: "One Day" }, + { id: "feelings", label: "Feelings" }, + { id: "difference", label: "Difference" }, + { id: "the-loop", label: "The Loop" }, + { id: "for-you", label: "For You" }, + { id: "for-business", label: "For Business" }, + { id: "why-now", label: "Why Now" }, + { id: "how-it-works", label: "How It Works" }, + { id: "old-vs-new", label: "Old vs New" }, + { id: "the-vision", label: "The Vision" }, + { id: "our-belief", label: "Our Belief" }, + { id: "the-promise", label: "The Promise" }, +]; export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
- - Vercel logomark - Deploy Now - - - Documentation - -
-
-
+
+ + + + + + + + + + + + + + + + + + +
); } diff --git a/src/components/animations/Hero3D.tsx b/src/components/animations/Hero3D.tsx new file mode 100644 index 0000000..4cd27d7 --- /dev/null +++ b/src/components/animations/Hero3D.tsx @@ -0,0 +1,227 @@ +"use client"; + +import { useEffect, useRef } from "react"; +import * as THREE from "three"; + +export default function Hero3D() { + const containerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) return; + + const container = containerRef.current; + const width = container.clientWidth; + const height = container.clientHeight; + + // Scene setup + const scene = new THREE.Scene(); + + // Camera setup + const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100); + camera.position.z = 8; + + // Renderer setup + const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); + renderer.setSize(width, height); + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); + container.appendChild(renderer.domElement); + + // Lights + const ambientLight = new THREE.AmbientLight(0xffffff, 0.4); + scene.add(ambientLight); + + const keyLight = new THREE.DirectionalLight(0xffffff, 1.5); + keyLight.position.set(5, 5, 5); + scene.add(keyLight); + + const pinkLight = new THREE.PointLight(0xff2e88, 5, 15); + pinkLight.position.set(-4, -2, 2); + scene.add(pinkLight); + + const goldLight = new THREE.PointLight(0xffc72c, 8, 15); + goldLight.position.set(4, 3, 2); + scene.add(goldLight); + + // Creating the LYT Coin (an Octahedron + Cylinder compound shape for a stylized look) + const coinGroup = new THREE.Group(); + + // Core Crystal (Diamond/Octahedron) + const crystalGeom = new THREE.OctahedronGeometry(1.6, 0); + const crystalMat = new THREE.MeshStandardMaterial({ + color: 0xffc72c, + roughness: 0.1, + metalness: 0.9, + emissive: 0xffa500, + emissiveIntensity: 0.3, + flatShading: true, + }); + const crystal = new THREE.Mesh(crystalGeom, crystalMat); + coinGroup.add(crystal); + + // Outer Ring + const ringGeom = new THREE.TorusGeometry(2.3, 0.12, 16, 100); + const ringMat = new THREE.MeshStandardMaterial({ + color: 0xff2e88, + roughness: 0.2, + metalness: 0.8, + emissive: 0x8b3dff, + emissiveIntensity: 0.2, + }); + const ring = new THREE.Mesh(ringGeom, ringMat); + ring.rotation.x = Math.PI / 2; + coinGroup.add(ring); + + // Floating minor energy crystals + const smallCrystals: THREE.Mesh[] = []; + const smallCrystalGeom = new THREE.OctahedronGeometry(0.3, 0); + const smallCrystalMat = new THREE.MeshStandardMaterial({ + color: 0xc8ff37, + roughness: 0.1, + metalness: 0.8, + flatShading: true, + }); + + for (let i = 0; i < 4; i++) { + const smallCrys = new THREE.Mesh(smallCrystalGeom, smallCrystalMat); + const angle = (i / 4) * Math.PI * 2; + smallCrys.position.set(Math.cos(angle) * 3.2, Math.sin(angle) * 3.2, 0); + coinGroup.add(smallCrys); + smallCrystals.push(smallCrys); + } + + scene.add(coinGroup); + + // Floating particles (energy dust) + const particleCount = 120; + const particleGeometry = new THREE.BufferGeometry(); + const particlePositions = new Float32Array(particleCount * 3); + + for (let i = 0; i < particleCount * 3; i += 3) { + // Circle layout with random spreads + const radius = 2 + Math.random() * 8; + const theta = Math.random() * Math.PI * 2; + const phi = Math.acos(2 * Math.random() - 1); + + particlePositions[i] = radius * Math.sin(phi) * Math.cos(theta); + particlePositions[i + 1] = radius * Math.sin(phi) * Math.sin(theta); + particlePositions[i + 2] = radius * Math.cos(phi); + } + + particleGeometry.setAttribute( + "position", + new THREE.BufferAttribute(particlePositions, 3) + ); + + // Dynamic glowing points + const pMaterial = new THREE.PointsMaterial({ + color: 0xffffff, + size: 0.05, + transparent: true, + opacity: 0.8, + blending: THREE.AdditiveBlending, + }); + + const particleSystem = new THREE.Points(particleGeometry, pMaterial); + scene.add(particleSystem); + + // Mouse interaction states + let mouseX = 0; + let mouseY = 0; + let targetX = 0; + let targetY = 0; + + const handleMouseMove = (event: MouseEvent) => { + const rect = container.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + + mouseX = (x / width) * 2 - 1; + mouseY = -(y / height) * 2 + 1; + }; + + window.addEventListener("mousemove", handleMouseMove); + + // Animation Loop + let animationFrameId: number; + let clock = new THREE.Clock(); + + const animate = () => { + const elapsedTime = clock.getElapsedTime(); + + // Slow passive rotation + coinGroup.rotation.y = elapsedTime * 0.4; + coinGroup.rotation.x = Math.sin(elapsedTime * 0.5) * 0.2; + + // Outer ring counter-rotation + ring.rotation.z = -elapsedTime * 0.6; + + // Small crystals float around + smallCrystals.forEach((crys, idx) => { + const offset = idx * (Math.PI / 2); + crys.position.y = Math.sin(elapsedTime * 1.5 + offset) * 3.4; + crys.position.x = Math.cos(elapsedTime * 1.5 + offset) * 3.4; + crys.rotation.x += 0.01; + crys.rotation.y += 0.02; + }); + + // Drifting particles + particleSystem.rotation.y = elapsedTime * 0.05; + particleSystem.rotation.x = Math.sin(elapsedTime * 0.02) * 0.1; + + // Smooth mouse follow (LERP) + targetX += (mouseX - targetX) * 0.05; + targetY += (mouseY - targetY) * 0.05; + + coinGroup.position.x = targetX * 1.5; + coinGroup.position.y = targetY * 1.5; + + // Dynamic lighting follow + goldLight.position.x = 4 + targetX * 2; + goldLight.position.y = 3 + targetY * 2; + + renderer.render(scene, camera); + animationFrameId = requestAnimationFrame(animate); + }; + + animate(); + + // Resize Handler + const handleResize = () => { + if (!container) return; + const newWidth = container.clientWidth; + const newHeight = container.clientHeight; + + camera.aspect = newWidth / newHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(newWidth, newHeight); + }; + + window.addEventListener("resize", handleResize); + + // Cleanup + return () => { + cancelAnimationFrame(animationFrameId); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("resize", handleResize); + container.removeChild(renderer.domElement); + + // Dispose materials/geometries to prevent memory leaks + crystalGeom.dispose(); + crystalMat.dispose(); + ringGeom.dispose(); + ringMat.dispose(); + smallCrystalGeom.dispose(); + smallCrystalMat.dispose(); + particleGeometry.dispose(); + pMaterial.dispose(); + renderer.dispose(); + }; + }, []); + + return ( +
+
+
+ ); +} diff --git a/src/components/common/Providers.tsx b/src/components/common/Providers.tsx new file mode 100644 index 0000000..83ac9ce --- /dev/null +++ b/src/components/common/Providers.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { useEffect } from "react"; +import { ThemeProvider } from "next-themes"; +import Lenis from "lenis"; +import { UserProvider } from "@/lib/context"; + +export default function Providers({ children }: { children: React.ReactNode }) { + useEffect(() => { + // Initialize Lenis smooth scroll (client-side only) + const lenis = new Lenis({ + duration: 1.2, + easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), + orientation: "vertical", + gestureOrientation: "vertical", + smoothWheel: true, + wheelMultiplier: 1, + touchMultiplier: 2, + }); + + function raf(time: number) { + lenis.raf(time); + requestAnimationFrame(raf); + } + + requestAnimationFrame(raf); + + // Expose instance so components can trigger programmatic smooth scroll + (window as unknown as { lenis?: Lenis }).lenis = lenis; + + return () => { + lenis.destroy(); + delete (window as unknown as { lenis?: Lenis }).lenis; + }; + }, []); + + return ( + + + {children} + + + ); +} diff --git a/src/components/games/CoinCatcher.tsx b/src/components/games/CoinCatcher.tsx new file mode 100644 index 0000000..690a7f3 --- /dev/null +++ b/src/components/games/CoinCatcher.tsx @@ -0,0 +1,391 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { useUser } from "@/lib/context"; +import { Play, RotateCcw, Coins, ShieldAlert, Award, Heart } from "lucide-react"; + +interface Particle { + x: number; + y: number; + vx: number; + vy: number; + color: string; + alpha: number; + size: number; +} + +interface GameObject { + x: number; + y: number; + size: number; + speed: number; + type: "coin" | "obstacle"; +} + +export default function CoinCatcher() { + const { addLyts } = useUser(); + const canvasRef = useRef(null); + + // Game states + const [gameState, setGameState] = useState<"idle" | "playing" | "gameover">("idle"); + const [score, setScore] = useState(0); + const [lives, setLives] = useState(3); + const [claimed, setClaimed] = useState(false); + + // References for mutable game loop variables + const requestRef = useRef(0); + const objectsRef = useRef([]); + const particlesRef = useRef([]); + const playerXRef = useRef(200); + const livesRef = useRef(3); + const scoreRef = useRef(0); + const spawnTimerRef = useRef(0); + + // Start the game + const startGame = () => { + setGameState("playing"); + setScore(0); + setLives(3); + setClaimed(false); + livesRef.current = 3; + scoreRef.current = 0; + objectsRef.current = []; + particlesRef.current = []; + playerXRef.current = 200; + }; + + // Claim score as LYTs + const claimEarnings = () => { + if (score > 0 && !claimed) { + addLyts(score); + setClaimed(true); + } + }; + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + // Track mouse coordinates to control player position + const handleMouseMove = (e: MouseEvent) => { + const rect = canvas.getBoundingClientRect(); + const relativeX = e.clientX - rect.left; + // Clamp player position within canvas bounds + playerXRef.current = Math.max(30, Math.min(canvas.width - 30, relativeX)); + }; + + // Track touch events for mobile support + const handleTouchMove = (e: TouchEvent) => { + if (e.touches.length === 0) return; + const rect = canvas.getBoundingClientRect(); + const relativeX = e.touches[0].clientX - rect.left; + playerXRef.current = Math.max(30, Math.min(canvas.width - 30, relativeX)); + }; + + canvas.addEventListener("mousemove", handleMouseMove); + canvas.addEventListener("touchmove", handleTouchMove); + + // Particle emitter for explosion effects + const createExplosion = (x: number, y: number, color: string) => { + for (let i = 0; i < 15; i++) { + particlesRef.current.push({ + x, + y, + vx: (Math.random() - 0.5) * 6, + vy: (Math.random() - 0.5) * 6 - 2, + color, + alpha: 1, + size: Math.random() * 3 + 2, + }); + } + }; + + // Main physics and rendering game loop + const gameLoop = () => { + if (gameState !== "playing") return; + + // Clear Canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // 1. Draw Catcher Bucket (Player) + const pX = playerXRef.current; + const pY = canvas.height - 35; + + // Outer glow for player + ctx.shadowBlur = 15; + ctx.shadowColor = "#ffc72c"; + + ctx.fillStyle = "#ffc72c"; + ctx.beginPath(); + // Draw a sleek curved basket shape + ctx.roundRect(pX - 35, pY, 70, 15, [4, 4, 10, 10]); + ctx.fill(); + + // Inside mesh glow + ctx.shadowBlur = 5; + ctx.shadowColor = "#ff2e88"; + ctx.fillStyle = "#ff2e88"; + ctx.fillRect(pX - 25, pY + 4, 50, 2); + ctx.shadowBlur = 0; // Reset shadow + + // 2. Spawn Items + spawnTimerRef.current += 1; + if (spawnTimerRef.current > 40) { + spawnTimerRef.current = 0; + const type = Math.random() > 0.35 ? "coin" : "obstacle"; + objectsRef.current.push({ + x: Math.random() * (canvas.width - 40) + 20, + y: -20, + size: type === "coin" ? 12 : 14, + speed: Math.random() * 2.5 + 3 + scoreRef.current * 0.05, // Speed increases as score goes up + type, + }); + } + + // 3. Update and Draw Falling Items + const activeObjects: GameObject[] = []; + objectsRef.current.forEach((obj) => { + obj.y += obj.speed; + + // Collision Check (AABB / Circle collision against basket) + const hitPlayer = + obj.y + obj.size >= pY && + obj.y - obj.size <= pY + 15 && + obj.x >= pX - 45 && + obj.x <= pX + 45; + + if (hitPlayer) { + if (obj.type === "coin") { + scoreRef.current += 1; + setScore(scoreRef.current); + createExplosion(obj.x, obj.y, "#ffc72c"); + } else { + livesRef.current -= 1; + setLives(livesRef.current); + createExplosion(obj.x, obj.y, "#ff2e88"); + + // Screen shake effect on bomb hit + ctx.translate((Math.random() - 0.5) * 15, (Math.random() - 0.5) * 15); + + if (livesRef.current <= 0) { + setGameState("gameover"); + } + } + } else if (obj.y < canvas.height + 20) { + activeObjects.push(obj); + } + }); + objectsRef.current = activeObjects; + + // Draw active falling objects + objectsRef.current.forEach((obj) => { + ctx.shadowBlur = 12; + if (obj.type === "coin") { + // Yellow LYT Coin + ctx.shadowColor = "#ffc72c"; + ctx.fillStyle = "#ffc72c"; + ctx.beginPath(); + ctx.arc(obj.x, obj.y, obj.size, 0, Math.PI * 2); + ctx.fill(); + + // Outer yellow shine ring + ctx.strokeStyle = "#ffffff"; + ctx.lineWidth = 1.5; + ctx.stroke(); + + // Core details + ctx.fillStyle = "#ffffff"; + ctx.font = "bold 9px Arial"; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText("L", obj.x, obj.y); + } else { + // Purple Obstacle Spike + ctx.shadowColor = "#8b3dff"; + ctx.fillStyle = "#8b3dff"; + + ctx.beginPath(); + // Draw simple triangle obstacle + ctx.moveTo(obj.x, obj.y - obj.size); + ctx.lineTo(obj.x + obj.size, obj.y + obj.size); + ctx.lineTo(obj.x - obj.size, obj.y + obj.size); + ctx.closePath(); + ctx.fill(); + + // Spiky border + ctx.strokeStyle = "#ff2e88"; + ctx.lineWidth = 1; + ctx.stroke(); + } + }); + ctx.shadowBlur = 0; + + // 4. Update and Draw Particle Explosions + particlesRef.current.forEach((p, idx) => { + p.x += p.vx; + p.y += p.vy; + p.alpha -= 0.025; + + ctx.fillStyle = p.color; + ctx.globalAlpha = Math.max(0, p.alpha); + ctx.beginPath(); + ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); + ctx.fill(); + }); + ctx.globalAlpha = 1.0; + particlesRef.current = particlesRef.current.filter((p) => p.alpha > 0); + + // Loop + requestRef.current = requestAnimationFrame(gameLoop); + }; + + if (gameState === "playing") { + requestRef.current = requestAnimationFrame(gameLoop); + } + + return () => { + cancelAnimationFrame(requestRef.current); + canvas.removeEventListener("mousemove", handleMouseMove); + canvas.removeEventListener("touchmove", handleTouchMove); + }; + }, [gameState]); + + return ( +
+ {/* Background glow */} +
+ +
+ + {/* Header */} +
+ + Play & Earn + +

+ The LYT Catcher Mini-Game +

+

+ Use your mouse or touch screen to catch the falling yellow LYT coins. Avoid the purple hazard spikes! Points are credited straight to your wallet. +

+
+ + {/* Game Box Container */} +
+
+ + {/* Top Game UI Bar (While Playing) */} + {gameState === "playing" && ( +
+ {/* Score */} +
+ + Score: {score} +
+ + {/* Lives */} +
+ {Array.from({ length: 3 }).map((_, i) => ( + + ))} +
+
+ )} + + {/* Canvas */} + + + {/* Screens overlays */} + + {/* 1. Idle Overlay */} + {gameState === "idle" && ( +
+
+ +
+
+

Ready to Catch?

+

+ Move your mouse or finger horizontally to control the catcher basket. Grab coins, avoid spikes! +

+
+ +
+ )} + + {/* 2. Gameover Overlay */} + {gameState === "gameover" && ( +
+
+ +
+ +
+

Game Over

+

+ You caught {score} LYTs! +

+
+ + {/* Claim details */} +
+ + + +
+
+ )} + +
+
+ +
+
+ ); +} diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx new file mode 100644 index 0000000..15eb256 --- /dev/null +++ b/src/components/layout/Footer.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { useState } from "react"; +import { Send, Instagram, Twitter, MessageSquare, Heart } from "lucide-react"; +import Link from "next/link"; + +export default function Footer() { + const [email, setEmail] = useState(""); + const [subscribed, setSubscribed] = useState(false); + + const handleSubscribe = (e: React.FormEvent) => { + e.preventDefault(); + if (email) { + setSubscribed(true); + setEmail(""); + setTimeout(() => setSubscribed(false), 5000); + } + }; + + return ( +
+ {/* Ambient background light glows */} +
+
+ +
+
+ {/* Brand Info */} +
+ + + LytsUp + + +

+ The premier social currency app for Gen Z. Earn rewards for walking, playing, and living your best life. +

+

+ Earn. Spend. Live More. +

+
+ + {/* Site Navigation */} +
+

Navigate

+ +
+ + {/* Corporate Links */} +
+

Partnerships

+
    +
  • + Become a Partner +
  • +
  • + Brands Portal +
  • +
  • + API Integration +
  • +
  • + Support Hub +
  • +
+
+ + {/* Newsletter subscription */} +
+

Get Secret Drops

+

+ Subscribe to get notified about ultra-rare brand voucher drops. +

+
+ setEmail(e.target.value)} + required + className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2.5 text-sm text-white focus:outline-none focus:border-primary/50 transition-colors pr-10" + /> + +
+ {subscribed && ( +

+ You're in! Check your inbox for secret codes. +

+ )} +
+
+ +
+ + {/* Socials & Copyright */} +
+
+ © {new Date().getFullYear()} LytsUp.com. All rights reserved. + + + Made with for Gen Z + +
+ + {/* Social Icons */} + +
+
+
+ ); +} diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx new file mode 100644 index 0000000..a962962 --- /dev/null +++ b/src/components/layout/Navbar.tsx @@ -0,0 +1,146 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useUser } from "@/lib/context"; +import { Menu, X, Coins, Flame, Award, Smartphone, User, BarChart2 } from "lucide-react"; +import Link from "next/link"; + +export default function Navbar() { + const { profile } = useUser(); + const [isScrolled, setIsScrolled] = useState(false); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + + useEffect(() => { + const handleScroll = () => { + if (window.scrollY > 20) { + setIsScrolled(true); + } else { + setIsScrolled(false); + } + }; + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + const navLinks = [ + { name: "Earn", href: "#earn" }, + { name: "Spend", href: "#spend" }, + { name: "Play & Win", href: "#play" }, + { name: "Leaderboard", href: "#leaderboard" }, + { name: "Dashboard", href: "#dashboard" }, + ]; + + return ( + + ); +} diff --git a/src/components/layout/SideNav.tsx b/src/components/layout/SideNav.tsx new file mode 100644 index 0000000..bd46fb6 --- /dev/null +++ b/src/components/layout/SideNav.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import type Lenis from "lenis"; + +export interface NavSection { + id: string; + label: string; +} + +export default function SideNav({ sections }: { sections: NavSection[] }) { + const [active, setActive] = useState(sections[0]?.id ?? ""); + const [open, setOpen] = useState(false); + + // Track which section is in view + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) setActive(entry.target.id); + }); + }, + { rootMargin: "-45% 0px -45% 0px", threshold: 0 } + ); + + sections.forEach(({ id }) => { + const el = document.getElementById(id); + if (el) observer.observe(el); + }); + + return () => observer.disconnect(); + }, [sections]); + + const scrollTo = useCallback((id: string) => { + const el = document.getElementById(id); + if (!el) return; + const lenis = (window as unknown as { lenis?: Lenis }).lenis; + if (lenis) { + lenis.scrollTo(el, { offset: 0, duration: 1.2 }); + } else { + el.scrollIntoView({ behavior: "smooth", block: "start" }); + } + setOpen(false); + }, []); + + return ( + <> + {/* Mobile toggle */} + + + {/* Right-side navigation */} + + + ); +} diff --git a/src/components/sections/BrandMarquee.tsx b/src/components/sections/BrandMarquee.tsx new file mode 100644 index 0000000..81af8c9 --- /dev/null +++ b/src/components/sections/BrandMarquee.tsx @@ -0,0 +1,31 @@ +"use client"; + +import Marquee from "react-fast-marquee"; +import { Coffee, ShoppingBag, Music, Film, Dumbbell, Ticket, Sparkles } from "lucide-react"; + +export default function BrandMarquee() { + const brands = [ + { name: "Starbucks", icon: , color: "text-emerald-400" }, + { name: "Nike", icon: , color: "text-white" }, + { name: "Spotify", icon: , color: "text-green-400" }, + { name: "Netflix", icon: , color: "text-red-500" }, + { name: "Gymshark", icon: , color: "text-amber-500" }, + { name: "Ticketmaster", icon: , color: "text-sky-400" }, + ]; + + return ( +
+ + {brands.concat(brands).map((brand, idx) => ( +
+ + {brand.icon} + + {brand.name} + +
+ ))} +
+
+ ); +} diff --git a/src/components/sections/DashboardPreview.tsx b/src/components/sections/DashboardPreview.tsx new file mode 100644 index 0000000..29ea370 --- /dev/null +++ b/src/components/sections/DashboardPreview.tsx @@ -0,0 +1,313 @@ +"use client"; + +import { useUser } from "@/lib/context"; +import { User, Flame, Coins, CheckCircle, Smartphone, ArrowRight, Share2, Compass, ChevronRight, Activity, TrendingUp } from "lucide-react"; + +export default function DashboardPreview() { + const { profile } = useUser(); + + // Dynamic calculations based on user's steps + const caloriesBurned = Math.round(profile.stepsToday * 0.04); + const distanceKm = (profile.stepsToday * 0.00076).toFixed(2); + const activeMinutes = Math.round(profile.stepsToday * 0.008); + + const stepPercentage = Math.min(100, Math.round((profile.stepsToday / profile.stepGoal) * 100)); + + // Weekly steps dummy data + const weeklySteps = [ + { day: "Mon", steps: 8200 }, + { day: "Tue", steps: 9400 }, + { day: "Wed", steps: 6100 }, + { day: "Thu", steps: 7800 }, + { day: "Fri", steps: 11200 }, + { day: "Sat", steps: 9000 }, + { day: "Sun", steps: profile.stepsToday }, + ]; + + const maxWeeklySteps = Math.max(...weeklySteps.map((d) => d.steps)); + + // Transaction history dummy data + const transactions = [ + { id: 1, type: "Walk Reward", detail: "6,420 steps synced", amount: "+16 LYT", time: "2 hours ago", isEarn: true }, + { id: 2, type: "Redeemed Brand", detail: "Starbucks $10 Voucher", amount: "-150 LYT", time: "5 hours ago", isEarn: false }, + { id: 3, type: "Referral Bonus", detail: "Invited Marcus Lee", amount: "+50 LYT", time: "1 day ago", isEarn: true }, + { id: 4, type: "Game Catch", detail: "LYT Catcher Score: 45", amount: "+45 LYT", time: "1 day ago", isEarn: true }, + ]; + + return ( +
+ {/* Background visual glows */} +
+
+ +
+ + {/* Section Header */} +
+ + Platform Preview + +

+ Manage Assets on Your Dashboard +

+

+ Track walking logs, check voucher redemption codes, and review points transaction history in your unified portal. +

+
+ + {/* Contents Grid */} +
+ + {/* Left Block: Weekly Progress Charts (col-span-3) */} +
+
+
+ + + Activity Logs + + Weekly +
+ + {/* Steps Chart */} +
+ {weeklySteps.map((d, idx) => { + const pct = Math.max(15, Math.round((d.steps / maxWeeklySteps) * 100)); + const isToday = idx === weeklySteps.length - 1; + return ( +
+
+
+ {/* Tooltip */} +
+ {d.steps.toLocaleString()} +
+
+ + {d.day} + +
+ ); + })} +
+ +
+ 🎉 Your weekly walk step frequency has increased by 12.4% since last week. Keep it up! +
+
+
+ + {/* Center Block: Mobile iOS Phone Mockup (col-span-5) */} +
+ {/* Phone Shell */} +
+ + {/* Dynamic Island Notch */} +
+
+
+ + {/* Status Header */} +
+ 9:41 +
+ 5G + 100% +
+
+ + {/* App Internal Body (Scrollable mock container) */} +
+ + {/* User Header */} +
+
+
+
+ AS +
+
+
+ {profile.name} + Level {profile.level} Premium Member +
+
+ + {/* Streak tag */} +
+ + {profile.streak} Days +
+
+ + {/* Circular Step Tracker */} +
+ + {/* Gauge */} +
+ + {/* SVG circular track */} + + + + + + {/* Step values */} +
+ + {profile.stepsToday.toLocaleString()} + + + Steps Today + +
+
+ + + Goal: {profile.stepGoal.toLocaleString()} steps ({stepPercentage}%) + + + {/* Stats split */} +
+
+ {caloriesBurned} + Kcal +
+
+ {distanceKm} + Km +
+
+ {activeMinutes} + Mins +
+
+
+ + {/* Balance & Send Row */} +
+
+ Wallet Balance + + + {profile.lytBalance} LYTs + +
+ + +
+ + {/* Challenges Panel */} +
+

Daily Missions

+
+
+
+ + Log 5,000 steps today +
+ +10 LYT +
+ +
+
+ + Check in at Brew & Co +
+ +5 LYT +
+
+
+ +
+ + {/* Bottom Nav Bar */} +
+
+ + Track +
+
+ + Explore +
+
+ + Wallet +
+
+ +
+
+ + {/* Right Block: Transaction Log Feed (col-span-4) */} +
+
+
+ + + Wallet Transaction Ledger + + Real-Time +
+ + {/* Transaction list */} +
+ {transactions.map((tx) => ( +
+
+ {tx.type} + {tx.detail} +
+ +
+ + {tx.amount} + + {tx.time} +
+
+ ))} +
+ + +
+
+ +
+
+
+ ); +} diff --git a/src/components/sections/EarningFeatures.tsx b/src/components/sections/EarningFeatures.tsx new file mode 100644 index 0000000..63ae61b --- /dev/null +++ b/src/components/sections/EarningFeatures.tsx @@ -0,0 +1,417 @@ +"use client"; + +import { useState } from "react"; +import { useUser } from "@/lib/context"; +import { Footprints, ShoppingBag, MapPin, Share2, ClipboardCheck, Sparkles, Check, RefreshCw } from "lucide-react"; + +export default function EarningFeatures() { + const { profile, addLyts, incrementSteps } = useUser(); + + // Walk simulator state + const [simSteps, setSimSteps] = useState(1200); + const [walkClaimed, setWalkClaimed] = useState(false); + const walkReward = Math.floor(simSteps / 400); // 1 LYT per 400 steps for the simulator + + // Shop state + const [selectedItems, setSelectedItems] = useState(["coffee"]); + const [shopClaimed, setShopClaimed] = useState(false); + + const shopItems = [ + { id: "coffee", name: "Starbucks Iced Latte", cost: "$5.80", reward: 8 }, + { id: "sneakers", name: "Nike Air Max Essentials", cost: "$120.00", reward: 150 }, + { id: "gym", name: "Gymshark Fitness Tee", cost: "$35.00", reward: 40 }, + ]; + + const totalShopReward = shopItems + .filter((item) => selectedItems.includes(item.id)) + .reduce((sum, item) => sum + item.reward, 0); + + // Map state + const [visitedStores, setVisitedStores] = useState([]); + const [mapClaimed, setMapClaimed] = useState(null); + + const checkinLocations = [ + { id: "cafe", name: "Brew & Co Cafe", reward: 5, checked: false }, + { id: "apparel", name: "H&M flagship", reward: 12, checked: false }, + { id: "arena", name: "Downtown Arena", reward: 25, checked: false }, + ]; + + // Referral state + const [referralCopied, setReferralCopied] = useState(false); + const referralCode = `lytsup.com/invite/${profile.name.toLowerCase().replace(/\s+/g, "")}-77`; + + const handleSimulateWalk = () => { + incrementSteps(simSteps); + addLyts(walkReward); + setWalkClaimed(true); + setTimeout(() => { + setWalkClaimed(false); + setSimSteps(1200); + }, 4000); + }; + + const handleShopClaim = () => { + if (totalShopReward > 0) { + addLyts(totalShopReward); + setShopClaimed(true); + setTimeout(() => { + setShopClaimed(false); + setSelectedItems([]); + }, 4000); + } + }; + + const handleCheckin = (id: string, name: string, reward: number) => { + if (visitedStores.includes(id)) return; + setVisitedStores((prev) => [...prev, id]); + addLyts(reward); + setMapClaimed(name); + setTimeout(() => setMapClaimed(null), 3000); + }; + + const handleCopyReferral = () => { + navigator.clipboard.writeText(referralCode); + setReferralCopied(true); + addLyts(50); // Referral bonus! + setTimeout(() => setReferralCopied(false), 4000); + }; + + return ( +
+ {/* Background glow lines */} +
+ +
+ + {/* Section Header */} +
+ + How it works + +

+ Earn LYTs for Living Your Life +

+

+ Interact with our live simulations below to see how easy it is to rack up social currency. +

+
+ + {/* Feature Grid */} +
+ + {/* Card 1: Walk to Earn (Simulator) */} +
+
+
+
+ +
+ + 1 LYT / 400 Steps + +
+ +
+

Walk-to-Earn

+

+ Keep your phone in your pocket, walk to class, run errands, or jog. Watch your step count convert to LYTs automatically. +

+
+ + {/* Step Simulator Widget */} +
+
+
+ Steps to Simulate + + +{simSteps.toLocaleString()} Steps + +
+
+ Estimated Earn + + +{walkReward} LYTs + +
+
+ + {/* Range Slider */} + setSimSteps(parseInt(e.target.value))} + disabled={walkClaimed} + className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-primary" + /> + +
+ 400 Steps + 6,000 Steps + 12,000 Steps +
+
+
+ +
+ +
+
+ + {/* Card 2: Shop & Earn (Receipt Simulator) */} +
+
+
+
+ +
+ + 10% LYT Cashback + +
+ +
+

Shop-to-Earn

+

+ Shop from partner merchants (food, clothing, fitness centers) and scan your digital receipt to claim instant LYT cashback. +

+
+ + {/* Receipt Widget */} +
+
+ Pending Receipt +
+ +

Select items purchased:

+
+ {shopItems.map((item) => ( +
{ + if (shopClaimed) return; + setSelectedItems((prev) => + prev.includes(item.id) + ? prev.filter((id) => id !== item.id) + : [...prev, item.id] + ); + }} + className={`flex items-center justify-between p-3 rounded-xl border cursor-pointer transition-all ${ + selectedItems.includes(item.id) + ? "bg-accent-pink/5 border-accent-pink/30 text-white" + : "bg-white/3 border-white/5 text-white/60 hover:border-white/10" + }`} + > +
+
+ {selectedItems.includes(item.id) && } +
+
{item.name}
+
+
+
{item.cost}
+
+{item.reward} LYTs
+
+
+ ))} +
+
+
+ +
+ +
+
+ + {/* Card 3: Visit to Earn (Geofence Checkins) */} +
+
+
+
+ +
+ + Location Check-Ins + +
+ +
+

Visit-to-Earn

+

+ Explore your city! Check in at partner gyms, parks, retail shops, or cafes. Each location visit awards you instant points. +

+
+ + {/* Map Simulator Widget */} +
+ {mapClaimed && ( +
+
+
+ +
+

Checked in at {mapClaimed}!

+

LYTs Added directly to wallet

+
+
+ )} + +

Active nodes near you:

+
+ {checkinLocations.map((loc) => { + const visited = visitedStores.includes(loc.id); + return ( +
+
+ +
{loc.name}
+
+ +
+ ); + })} +
+
+
+ +
+ Glow tips: Allow location permissions in-app to auto check-in. +
+
+ + {/* Card 4: Refer to Earn (Social Loop) */} +
+
+
+
+ +
+ + 50 LYTs / Referral + +
+ +
+

Refer-to-Earn

+

+ Everything is better with friends. Send your personal link to your friends. Get 50 LYTs the second they verify their profile! +

+
+ + {/* Referral Widget */} +
+
+ +
+ +
+ +
+
+
+ +
+ 🔥 Bonus: Your friends also start with 50 LYTs in their wallet when they sign up! +
+
+
+ +
+ +
+
+ +
+
+
+ ); +} diff --git a/src/components/sections/FlashDrops.tsx b/src/components/sections/FlashDrops.tsx new file mode 100644 index 0000000..d0360ff --- /dev/null +++ b/src/components/sections/FlashDrops.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Zap, Clock, ShieldAlert, Award } from "lucide-react"; +import { motion } from "framer-motion"; + +export default function FlashDrops() { + const [timeLeft, setTimeLeft] = useState(7920); // 2 hours 12 minutes in seconds + + useEffect(() => { + const timer = setInterval(() => { + setTimeLeft((prev) => (prev > 0 ? prev - 1 : 7920)); + }, 1000); + return () => clearInterval(timer); + }, []); + + const formatTime = (secs: number) => { + const h = Math.floor(secs / 3600); + const m = Math.floor((secs % 3600) / 60); + const s = secs % 60; + return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; + }; + + const drops = [ + { + brand: "Starbucks Concert", + detail: "VIP Pass Ticket Drop", + cost: "800 LYTs", + locked: true, + time: "2h 12m", + }, + { + brand: "Nike Air Jordan", + detail: "$100 Gift Voucher Drop", + cost: "1200 LYTs", + locked: true, + time: "5h 40m", + }, + ]; + + return ( +
+
+ +
+
+
+ +
+ +
+ Section 12 • Flash Drops +

+ Ultra-Rare
+ Limited Vouchers +

+

+ Check back periodically. High-value partner rewards drop at set intervals. When the timer hits zero, vouchers unlock for instant claims. First come, first served. +

+
+
+ + {/* Time & Drops display */} +
+ + {/* Timer card */} +
+ Next Secret Drop Unlocks In: + + {formatTime(timeLeft)} + +
+ + Notify me when drops open +
+
+ + {/* Locked drops listing */} +
+ {drops.map((drop, idx) => ( +
+
+ {drop.brand} + {drop.detail} +
+ +
+ + Locked + + Cost: {drop.cost} +
+
+ ))} +
+ +
+
+ +
+
+ ); +} diff --git a/src/components/sections/FooterFAQ.tsx b/src/components/sections/FooterFAQ.tsx new file mode 100644 index 0000000..b56addf --- /dev/null +++ b/src/components/sections/FooterFAQ.tsx @@ -0,0 +1,223 @@ +"use client"; + +import { useState } from "react"; +import { ChevronDown, Send, Instagram, Twitter, MessageSquare, Heart, Sparkles } from "lucide-react"; +import Link from "next/link"; + +interface FaqItem { + question: string; + answer: string; +} + +export default function FooterFAQ() { + const [openFaq, setOpenFaq] = useState(null); + const [email, setEmail] = useState(""); + const [subscribed, setSubscribed] = useState(false); + + const faqItems: FaqItem[] = [ + { + question: "How do I sync my walk steps to earn LYTs?", + answer: "LytsUp requests standard health API permissions during app registration. It automatically reads your daily distance in the background and converts every 500 steps taken into 1 LYT coin. You don't need to keep the app open!", + }, + { + question: "Are there any fees or hidden charges?", + answer: "No, LytsUp is 100% free for users. Our partner brands sponsor the cashback rewards and vouchers to encourage physical activity, foot traffic, and community engagement.", + }, + { + question: "How do I redeem my vouchers at partner stores?", + answer: "When you click 'Redeem' on a voucher card in the marketplace, LytsUp validates your balance, deducts the points, and generates an active checkout code. Copy the code and apply it during checkout on the partner brand's site.", + }, + { + question: "Can I transfer LYT coins to my friends?", + answer: "Yes! The mobile app contains a peer-to-peer wallet system where you can send coins to friends to pool assets, complete squad challenges, or gift rewards.", + }, + ]; + + const handleSubscribe = (e: React.FormEvent) => { + e.preventDefault(); + if (email) { + setSubscribed(true); + setEmail(""); + setTimeout(() => setSubscribed(false), 5000); + } + }; + + const toggleFaq = (idx: number) => { + setOpenFaq((prev) => (prev === idx ? null : idx)); + }; + + return ( +
+ {/* Glow lights */} +
+
+ + {/* Part 1: FAQ Accordions */} +
+
+ + Support Hub + +

+ Frequently Asked Questions +

+
+ +
+ {faqItems.map((faq, idx) => { + const isOpen = openFaq === idx; + return ( +
+ + + {isOpen && ( +
+ {faq.answer} +
+ )} +
+ ); + })} +
+
+ + {/* Part 2: Footer layout */} +
+
+ + {/* Brand Info */} +
+ + LytsUp Logo + + LytsUp + + +

+ The premier social currency app for Gen Z. Earn rewards for walking, playing, and living your best life. +

+

+ Earn. Spend. Live More. +

+
+ + {/* Site Navigation */} +
+

Navigate

+ +
+ + {/* Corporate Links */} +
+

Partnerships

+
    +
  • + Become a Partner +
  • +
  • + Brands Portal +
  • +
  • + API Integration +
  • +
+
+ + {/* Newsletter subscription */} +
+

Get Secret Drops

+

+ Subscribe to get notified about ultra-rare brand voucher drops. +

+
+ setEmail(e.target.value)} + required + className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-2 text-xs text-white focus:outline-none focus:border-primary/50 transition-colors pr-10" + /> + +
+ {subscribed && ( +

+ You're in! Check your inbox for secret codes. +

+ )} +
+
+ +
+ + {/* Socials & Copyright */} +
+
+ © {new Date().getFullYear()} LytsUp.com. All rights reserved. + + + Made with for Gen Z + +
+ + {/* Social Icons */} + +
+
+
+ ); +} diff --git a/src/components/sections/Hero.tsx b/src/components/sections/Hero.tsx new file mode 100644 index 0000000..c4a315c --- /dev/null +++ b/src/components/sections/Hero.tsx @@ -0,0 +1,145 @@ +"use client"; + +import dynamic from "next/dynamic"; +import { ArrowRight, Sparkles, TrendingUp, Users, ShoppingBag } from "lucide-react"; +import CountUp from "react-countup"; +import Marquee from "react-fast-marquee"; + +// Dynamically import Three.js Hero3D to prevent SSR issues +const Hero3D = dynamic(() => import("../animations/Hero3D"), { + ssr: false, + loading: () => ( +
+
+
+
+
+
+ ), +}); + +export default function Hero() { + const highlights = [ + "WALK & EARN", + "PLAY & WIN", + "SHOP & REDEEM", + "SHARE WITH FRIENDS", + "DRINK FREE COFFEE", + "LEVEL UP", + "GEN Z SOCIAL CURRENCY", + ]; + + return ( +
+ {/* Background Gradients & Glows */} +
+
+ +
+
+ + {/* Left Column: Copy & Actions */} +
+ {/* Promo Tag */} +
+ + Next Gen Social Currency +
+ + {/* Main Headline */} +
+

+ Earn. Spend.
+ + Live More. + +

+

+ Turn your everyday activities into social capital. Walk, play games, and shop to earn LYTs. Spend them at the brands you love. +

+
+ + {/* CTAs */} + + + {/* Live Counters / Stats */} +
+
+
+ + K+ +
+
+ + Active Users +
+
+ +
+
+ + + +
+
+ + Brand Partners +
+
+ +
+
+ + M+ +
+
+ + Steps Tracked +
+
+
+
+ + {/* Right Column: Interactive 3D Canvas */} +
+ {/* Hologram Circle Rings behind 3D Object */} +
+
+
+ +
+ +
+
+ +
+
+ + {/* Highlights Marquee Ticker */} +
+ + {highlights.map((text, idx) => ( +
+ + {text} +
+ ))} +
+
+
+ ); +} diff --git a/src/components/sections/HowItWorks.tsx b/src/components/sections/HowItWorks.tsx new file mode 100644 index 0000000..4c9410f --- /dev/null +++ b/src/components/sections/HowItWorks.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { motion } from "framer-motion"; +import { Zap, Coins, Gift } from "lucide-react"; + +export default function HowItWorks() { + const steps = [ + { + num: "01", + title: "Walk, Play & Shop", + description: "Keep doing your daily activities. Walk to school, play browser games, check-in, or scan shopping receipts.", + icon: , + bg: "rgba(255, 46, 136, 0.05)", + border: "border-accent-pink/15", + }, + { + num: "02", + title: "Earn LYT Coins", + description: "Earn LYTs directly into your wallet. Watch your points and account experience points (XP) scale as you walk.", + icon: , + bg: "rgba(255, 199, 44, 0.05)", + border: "border-primary/15", + }, + { + num: "03", + title: "Redeem Brand Vouchers", + description: "Spend your earned LYTs on our checkout catalog for gift cards, subscription tokens, and exclusive brand discounts.", + icon: , + bg: "rgba(139, 61, 255, 0.05)", + border: "border-accent-purple/15", + }, + ]; + + return ( +
+
+ + {/* Section Header */} +
+ + Platform Workflow + +

+ The Social Currency Lifecycle +

+

+ A three-step gamified loop designed to reward you for living an active, social, and healthy lifestyle. +

+
+ + {/* Step Cards Grid */} +
+ {steps.map((step, idx) => ( + +
+
+
+ {step.icon} +
+ + {step.num} + +
+ +
+

{step.title}

+

{step.description}

+
+
+
+ ))} +
+ +
+
+ ); +} diff --git a/src/components/sections/Leaderboard.tsx b/src/components/sections/Leaderboard.tsx new file mode 100644 index 0000000..463a051 --- /dev/null +++ b/src/components/sections/Leaderboard.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { useUser } from "@/lib/context"; +import { Users, Flame, Trophy } from "lucide-react"; +import { motion } from "framer-motion"; + +export default function Leaderboard() { + const { profile } = useUser(); + + const leaderboardUsers = [ + { rank: 1, name: "Zara Cole", level: 6, steps: 14200, points: 285, avatar: "Z" }, + { rank: 2, name: "Marcus Lee", level: 5, steps: 12100, points: 240, avatar: "M" }, + { rank: 3, name: "Chloe Vane", level: 4, steps: 9400, points: 188, avatar: "C" }, + { rank: 4, name: "Alex Stark (You)", level: profile.level, steps: profile.stepsToday, points: Math.round(profile.stepsToday / 15), avatar: "A", highlight: true }, + { rank: 5, name: "Devon Shaw", level: 3, steps: 5900, points: 118, avatar: "D" }, + ]; + + return ( +
+
+ +
+
+
+ +
+ +
+ Section 14 • Leaderboard +

+ Daily Ranks &
+ Community Standings +

+

+ Check out who is logging the most activity today. Walk, play, refer, and climb to the top to unlock premium avatar banners and multiplier rewards. +

+
+
+ + {/* Leaderboard panel list */} +
+
+ User Roster +
+ Steps Logged + Est. LYTs +
+
+ +
+ {leaderboardUsers.map((user) => ( +
+
+ + #{user.rank} + + +
+
+ {user.avatar} +
+ + L{user.level} + +
+ +
+ {user.name} + {user.highlight && ( + + + {profile.streak} days active streak + + )} +
+
+ +
+ + {user.steps.toLocaleString()} + + + + +{user.points} + +
+
+ ))} +
+
+
+ +
+
+ ); +} diff --git a/src/components/sections/LeaderboardSection.tsx b/src/components/sections/LeaderboardSection.tsx new file mode 100644 index 0000000..d00038a --- /dev/null +++ b/src/components/sections/LeaderboardSection.tsx @@ -0,0 +1,270 @@ +"use client"; + +import { useState } from "react"; +import { useUser } from "@/lib/context"; +import { Users, Flame, Award, ArrowUpRight, Check, Trophy, Footprints, Landmark } from "lucide-react"; + +interface TeamChallenge { + id: string; + title: string; + description: string; + reward: number; + current: number; + goal: number; + joined: boolean; + icon: React.ReactNode; +} + +export default function LeaderboardSection() { + const { profile, addLyts } = useUser(); + const [challenges, setChallenges] = useState([ + { + id: "ch-steps", + title: "Weekend Step Sprint", + description: "Collectively log 50,000 steps with your team this weekend.", + reward: 100, + current: 38400, + goal: 50000, + joined: false, + icon: , + }, + { + id: "ch-stores", + title: "Starbucks Double Drop", + description: "Check in at any two Starbucks locations within 7 days.", + reward: 40, + current: 1, + goal: 2, + joined: true, + icon: , + }, + { + id: "ch-ref", + title: "Referral Rush", + description: "Invite 3 friends to join LytsUp. Earn bonus points.", + reward: 150, + current: 2, + goal: 3, + joined: false, + icon: , + }, + ]); + + const leaderboardUsers = [ + { rank: 1, name: "Zara Cole", level: 6, steps: 14200, points: 285, avatar: "Z" }, + { rank: 2, name: "Marcus Lee", level: 5, steps: 12100, points: 240, avatar: "M" }, + { rank: 3, name: "Chloe Vane", level: 4, steps: 9400, points: 188, avatar: "C" }, + { rank: 4, name: "Alex Stark (You)", level: profile.level, steps: profile.stepsToday, points: Math.round(profile.stepsToday / 15), avatar: "A", highlight: true }, + { rank: 5, name: "Devon Shaw", level: 3, steps: 5900, points: 118, avatar: "D" }, + ]; + + const handleJoinChallenge = (id: string, reward: number) => { + setChallenges((prev) => + prev.map((ch) => { + if (ch.id === id) { + // If already joined, complete it to give points for the simulation + if (ch.joined) { + addLyts(reward); + return { ...ch, current: ch.goal, completed: true }; + } + return { ...ch, joined: true }; + } + return ch; + }) + ); + }; + + return ( +
+ {/* Background ambient lighting */} +
+
+ +
+ + {/* Section Header */} +
+ + Community Hub + +

+ Play Together. Earn Together. +

+

+ Join squad goals to multiply your LYT earnings, or compete for the top spots on the daily leaderboard. +

+
+ + {/* Contents Grid */} +
+ + {/* Left Column: Team Challenges */} +
+

+ + Active Squad Challenges +

+ +
+ {challenges.map((ch) => { + const percentage = Math.min(100, Math.round((ch.current / ch.goal) * 100)); + const isCompleted = ch.current >= ch.goal; + + return ( +
+
+
+
+ {ch.icon} +
+
+

{ch.title}

+

{ch.description}

+
+
+ +
+ Reward + +{ch.reward} LYTs +
+
+ + {/* Progress Slider */} +
+
+ {ch.current.toLocaleString()} / {ch.goal.toLocaleString()} + {percentage}% +
+
+
+
+
+ + {/* Button */} +
+ +
+
+ ); + })} +
+
+ + {/* Right Column: Leaderboard list */} +
+

+ + Daily Leaderboard +

+ +
+
+ User +
+ Steps + Est. LYTs +
+
+ +
+ {leaderboardUsers.map((user) => ( +
+
+ {/* Rank badge */} + + #{user.rank} + + + {/* Avatar */} +
+
+ {user.avatar} +
+ + L{user.level} + +
+ + {/* Profile Details */} +
+ {user.name} + {user.highlight && ( + + + {profile.streak} days active streak + + )} +
+
+ +
+ {/* Steps */} + + {user.steps.toLocaleString()} + + + {/* Points */} + + +{user.points} + +
+
+ ))} +
+
+
+ +
+
+
+ ); +} diff --git a/src/components/sections/PlayToEarn.tsx b/src/components/sections/PlayToEarn.tsx new file mode 100644 index 0000000..172533c --- /dev/null +++ b/src/components/sections/PlayToEarn.tsx @@ -0,0 +1,12 @@ +"use client"; + +import CoinCatcher from "../games/CoinCatcher"; + +export default function PlayToEarn() { + return ( +
+ {/* Renders the fully functional canvas coin-catcher mini-game */} + +
+ ); +} diff --git a/src/components/sections/ReferToEarn.tsx b/src/components/sections/ReferToEarn.tsx new file mode 100644 index 0000000..ac10783 --- /dev/null +++ b/src/components/sections/ReferToEarn.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { useState } from "react"; +import { useUser } from "@/lib/context"; +import { Share2, ClipboardCheck, Sparkles } from "lucide-react"; + +export default function ReferToEarn() { + const { profile, addLyts } = useUser(); + const [referralCopied, setReferralCopied] = useState(false); + const referralCode = `lytsup.com/invite/${profile.name.toLowerCase().replace(/\s+/g, "")}-77`; + + const handleCopyReferral = () => { + navigator.clipboard.writeText(referralCode); + setReferralCopied(true); + addLyts(50); // Give user 50 LYTs bonus for checking/testing referral copy! + setTimeout(() => setReferralCopied(false), 3000); + }; + + return ( +
+
+ +
+
+ + {/* Referral Widget */} +
+
+ +
+ +
+ +
+
+
+ +
+ 🔥 Invite Code Bonus: Friends start with 50 LYTs and you get 50 LYTs immediately on activation! +
+ + +
+ + {/* Information */} +
+
+ +
+ +
+ Section 09 • Refer-to-Earn +

+ Squad Invites &
+ Growth Loops +

+

+ Earn bonuses by inviting your friends. Create shared earning pools, unlock group level multipliers, and dominate leaderboards together. +

+
+
+ +
+
+ +
+
+ ); +} diff --git a/src/components/sections/ShopToEarn.tsx b/src/components/sections/ShopToEarn.tsx new file mode 100644 index 0000000..87183e9 --- /dev/null +++ b/src/components/sections/ShopToEarn.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { useState } from "react"; +import { useUser } from "@/lib/context"; +import { ShoppingBag, Check } from "lucide-react"; + +export default function ShopToEarn() { + const { addLyts } = useUser(); + const [selectedItems, setSelectedItems] = useState(["coffee"]); + const [shopClaimed, setShopClaimed] = useState(false); + + const shopItems = [ + { id: "coffee", name: "Starbucks Iced Caramel Macchiato", cost: "$6.20", reward: 12 }, + { id: "sneakers", name: "Nike Pegasus Running Shoes", cost: "$130.00", reward: 260 }, + { id: "tee", name: "Gymshark Seamless Tee", cost: "$38.00", reward: 76 }, + ]; + + const totalReward = shopItems + .filter((item) => selectedItems.includes(item.id)) + .reduce((sum, item) => sum + item.reward, 0); + + const handleShopClaim = () => { + if (totalReward > 0) { + addLyts(totalReward); + setShopClaimed(true); + setTimeout(() => { + setShopClaimed(false); + setSelectedItems([]); + }, 3000); + } + }; + + return ( +
+
+ +
+
+ + {/* Simulator box */} +
+

Select items purchased:

+
+ {shopItems.map((item) => ( +
{ + if (shopClaimed) return; + setSelectedItems((prev) => + prev.includes(item.id) + ? prev.filter((id) => id !== item.id) + : [...prev, item.id] + ); + }} + className={`flex items-center justify-between p-3.5 rounded-xl border cursor-pointer transition-all ${ + selectedItems.includes(item.id) + ? "bg-accent-pink/5 border-accent-pink/30 text-white" + : "bg-white/3 border-white/5 text-white/60 hover:border-white/10" + }`} + > +
+
+ {selectedItems.includes(item.id) && } +
+ {item.name} +
+
+ {item.cost} + +{item.reward} LYTs +
+
+ ))} +
+ + +
+ + {/* Information */} +
+
+ +
+ +
+ Section 06 • Shop-to-Earn +

+ Cashback &
+ Instant Rebates +

+

+ Earn high cashback rates when shopping with retail partners. Submit digital checks or verify loyalty IDs to earn LYT social tokens. +

+
+
+ +
+
+ +
+
+ ); +} diff --git a/src/components/sections/SocialWall.tsx b/src/components/sections/SocialWall.tsx new file mode 100644 index 0000000..d3a942a --- /dev/null +++ b/src/components/sections/SocialWall.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { motion } from "framer-motion"; +import { Heart, MessageCircle, Share, Star } from "lucide-react"; + +export default function SocialWall() { + const posts = [ + { + user: "@charlie_walks", + content: "Just cleared my daily step goal and claimed a $10 Starbucks gift card. Literally paid to walk to class! ☕🚶‍♂️", + likes: 242, + comments: 18, + brand: "Starbucks", + bgGlow: "rgba(16, 185, 129, 0.08)", + }, + { + user: "@kicks_n_lyts", + content: "Redeemed my LYTs for a 20% discount code at Nike. Ripped these Air Max 90s for absolute dirt. Walk simulator goes crazy! 🔥👟", + likes: 512, + comments: 44, + brand: "Nike", + bgGlow: "rgba(255, 255, 255, 0.08)", + }, + { + user: "@gamer_earn", + content: "Hit score 75 on the Coin Catcher mini-game, converted it directly and unlocked Spotify Premium. This app is essential. 🎧✨", + likes: 189, + comments: 12, + brand: "Spotify", + bgGlow: "rgba(34, 197, 94, 0.08)", + }, + ]; + + return ( +
+
+ + {/* Header */} +
+ + Gen Z Social Wall + +

+ Hear it from the Squad +

+

+ See how real users are converting their daily distance and plays into social currency vouchers. +

+
+ + {/* Social Cards Grid */} +
+ {posts.map((post, idx) => ( + +
+
+ {post.user} + + Redeemed {post.brand} + +
+

+ "{post.content}" +

+
+ + {/* Feed Actions Mock */} +
+ + + +
+
+ ))} +
+ +
+
+ ); +} diff --git a/src/components/sections/SpendSection.tsx b/src/components/sections/SpendSection.tsx new file mode 100644 index 0000000..ed2f92c --- /dev/null +++ b/src/components/sections/SpendSection.tsx @@ -0,0 +1,268 @@ +"use client"; + +import { useState } from "react"; +import { useUser } from "@/lib/context"; +import { Coffee, ShoppingBag, Music, Film, Dumbbell, Ticket, Check, Coins, Copy, ArrowRight, ShieldCheck } from "lucide-react"; + +interface BrandVoucher { + id: string; + brand: string; + discount: string; + cost: number; + icon: React.ReactNode; + color: string; + bgGlow: string; +} + +export default function SpendSection() { + const { profile, claimVoucher, vouchers } = useUser(); + const [redeemedCode, setRedeemedCode] = useState<{ brand: string; code: string } | null>(null); + const [errorMessage, setErrorMessage] = useState(null); + const [copiedId, setCopiedId] = useState(null); + + const availableVouchers: BrandVoucher[] = [ + { + id: "v-sbux", + brand: "Starbucks", + discount: "$10 Gift Card", + cost: 150, + icon: , + color: "border-emerald-500/20 text-emerald-400 hover:border-emerald-500/50", + bgGlow: "rgba(16, 185, 129, 0.15)", + }, + { + id: "v-nike", + brand: "Nike", + discount: "20% Discount Code", + cost: 300, + icon: , + color: "border-white/10 text-white hover:border-white/30", + bgGlow: "rgba(255, 255, 255, 0.15)", + }, + { + id: "v-spotify", + brand: "Spotify", + discount: "1 Month Premium", + cost: 200, + icon: , + color: "border-green-500/20 text-green-400 hover:border-green-500/50", + bgGlow: "rgba(34, 197, 94, 0.15)", + }, + { + id: "v-netflix", + brand: "Netflix", + discount: "1 Month Free UltraHD", + cost: 450, + icon: , + color: "border-red-500/20 text-red-500 hover:border-red-500/50", + bgGlow: "rgba(239, 68, 68, 0.15)", + }, + { + id: "v-gymshark", + brand: "Gymshark", + discount: "30% Storewide Voucher", + cost: 250, + icon: , + color: "border-amber-500/20 text-amber-500 hover:border-amber-500/50", + bgGlow: "rgba(245, 158, 11, 0.15)", + }, + { + id: "v-tickets", + brand: "Ticketmaster", + discount: "$25 Concert Coupon", + cost: 400, + icon: , + color: "border-sky-500/20 text-sky-400 hover:border-sky-500/50", + bgGlow: "rgba(56, 189, 248, 0.15)", + }, + ]; + + const handleRedeem = (voucher: BrandVoucher) => { + if (profile.lytBalance < voucher.cost) { + setErrorMessage(`Insufficient LYT balance. You need ${voucher.cost - profile.lytBalance} more LYTs for this voucher.`); + setTimeout(() => setErrorMessage(null), 5000); + return; + } + + const res = claimVoucher(voucher.brand, voucher.discount, voucher.cost); + if (res.success && res.code) { + setRedeemedCode({ brand: voucher.brand, code: res.code }); + // Clear popup after a while + setTimeout(() => setRedeemedCode(null), 10000); + } + }; + + const copyToClipboard = (id: string, text: string) => { + navigator.clipboard.writeText(text); + setCopiedId(id); + setTimeout(() => setCopiedId(null), 3000); + }; + + return ( +
+ {/* Visual background glows */} +
+
+ +
+ + {/* Section Header */} +
+ + Spend Your LYTs + +

+ Claim Vouchers at Your Favorite Brands +

+

+ Redeem your accumulated LYT coins for gift cards, subscriptions, and discounts. Click redeem to generate a checkout code. +

+
+ + {/* Dynamic Warning Alert */} + {errorMessage && ( +
+ {errorMessage} + Earn LYTs fast +
+ )} + + {/* Success Modal Popup */} + {redeemedCode && ( +
+
+
+ +
+ +
+

Redemption Successful!

+

+ You successfully claimed your {redeemedCode.brand} voucher code. +

+
+ + {/* Code Shelf */} +
+ {redeemedCode.code} + +
+ +
+ + Code is also saved to your Active Vouchers panel below. +
+
+
+ )} + + {/* Voucher Grid */} +
+ {availableVouchers.map((voucher) => { + const isAffordable = profile.lytBalance >= voucher.cost; + return ( +
+
+ {/* Brand Header */} +
+
+ {voucher.icon} +
+
+ + {voucher.cost} LYTs +
+
+ + {/* Info */} +
+

{voucher.brand}

+

{voucher.discount}

+
+
+ + {/* Bottom action */} +
+ +
+
+ ); + })} +
+ + {/* Claimed Vouchers Display */} + {vouchers.length > 0 && ( +
+

+ Your Claimed Vouchers + + {vouchers.length} Total + +

+ +
+ {vouchers.map((v) => ( +
+
+ {v.brand} +

{v.discount}

+ Claimed on {v.claimedAt} +
+ +
+ + {v.code} + + +
+
+ ))} +
+
+ )} + +
+
+ ); +} diff --git a/src/components/sections/SpendShop.tsx b/src/components/sections/SpendShop.tsx new file mode 100644 index 0000000..55d644f --- /dev/null +++ b/src/components/sections/SpendShop.tsx @@ -0,0 +1,12 @@ +"use client"; + +import SpendSection from "./SpendSection"; + +export default function SpendShop() { + return ( +
+ {/* Renders the fully functional vouchers shop store catalog */} + +
+ ); +} diff --git a/src/components/sections/SquadChallenges.tsx b/src/components/sections/SquadChallenges.tsx new file mode 100644 index 0000000..5c4b1f8 --- /dev/null +++ b/src/components/sections/SquadChallenges.tsx @@ -0,0 +1,157 @@ +"use client"; + +import { useState } from "react"; +import { useUser } from "@/lib/context"; +import { Users, Footprints, Check, Zap, Landmark } from "lucide-react"; +import { motion } from "framer-motion"; + +interface TeamChallenge { + id: string; + title: string; + description: string; + reward: number; + current: number; + goal: number; + joined: boolean; + icon: React.ReactNode; +} + +export default function SquadChallenges() { + const { addLyts } = useUser(); + const [challenges, setChallenges] = useState([ + { + id: "ch-steps", + title: "Weekend Step Sprint", + description: "Collectively log 50,000 steps with your team this weekend.", + reward: 100, + current: 41200, + goal: 50000, + joined: false, + icon: , + }, + { + id: "ch-stores", + title: "Starbucks Double Drop", + description: "Check in at any two Starbucks locations within 7 days.", + reward: 40, + current: 1, + goal: 2, + joined: true, + icon: , + }, + ]); + + const handleJoinChallenge = (id: string, reward: number) => { + setChallenges((prev) => + prev.map((ch) => { + if (ch.id === id) { + if (ch.joined) { + addLyts(reward); + return { ...ch, current: ch.goal }; + } + return { ...ch, joined: true }; + } + return ch; + }) + ); + }; + + return ( +
+
+ +
+
+
+ +
+ +
+ Section 13 • Squad Goals +

+ Cooperative Team
+ Active Challenges +

+

+ Connect with friends or match with nearby community squads. Log steps, complete visits, and unlock bulk LYT currency bonuses together. +

+
+
+ + {/* Challenges listing */} +
+ {challenges.map((ch) => { + const percentage = Math.min(100, Math.round((ch.current / ch.goal) * 100)); + const isCompleted = ch.current >= ch.goal; + + return ( +
+
+
+
+ {ch.icon} +
+
+

{ch.title}

+

{ch.description}

+
+
+ +
+ Reward + +{ch.reward} LYTs +
+
+ +
+
+ {ch.current.toLocaleString()} / {ch.goal.toLocaleString()} + {percentage}% +
+
+
+
+
+ +
+ +
+
+ ); + })} +
+
+ +
+
+ ); +} diff --git a/src/components/sections/StorySection.tsx b/src/components/sections/StorySection.tsx new file mode 100644 index 0000000..c5d5c60 --- /dev/null +++ b/src/components/sections/StorySection.tsx @@ -0,0 +1,31 @@ +"use client"; + +import Image from "next/image"; + +interface StorySectionProps { + id: string; + src: string; + alt: string; + priority?: boolean; +} + +export default function StorySection({ id, src, alt, priority }: StorySectionProps) { + return ( +
+
+ {alt} +
+
+ ); +} diff --git a/src/components/sections/ValueProp.tsx b/src/components/sections/ValueProp.tsx new file mode 100644 index 0000000..2c189f8 --- /dev/null +++ b/src/components/sections/ValueProp.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { motion } from "framer-motion"; +import { ShieldCheck, Award, Zap, Heart } from "lucide-react"; + +export default function ValueProp() { + const points = [ + { + title: "Real Brands, Instant Codes", + desc: "No points conversions or delays. Claim digital voucher codes instantly at Starbucks, Nike, Netflix, Spotify.", + icon: , + }, + { + title: "100% Free & Transparent", + desc: "Zero fees, zero limits. Earn by living your active, healthy life. Your steps and plays are your only capital.", + icon: , + }, + { + title: "Gen Z Social Ecosystem", + desc: "Form squad step goals, battle in real-time catcher leaderboards, and trade vouchers with friends in our app.", + icon: , + }, + { + title: "Health & Well-being Loop", + desc: "Earn currency while prioritizing your physical health, mental focus, and social community connections.", + icon: , + }, + ]; + + return ( +
+
+ + {/* Header */} +
+ + Platform Benefits + +

+ Designed for the Connected Generation +

+

+ LytsUp shifts the loyalty points equation. Discover why thousands of users sync daily. +

+
+ + {/* Grid */} +
+ {points.map((item, idx) => ( + +
+ {item.icon} +
+
+

{item.title}

+

{item.desc}

+
+
+ ))} +
+ +
+
+ ); +} diff --git a/src/components/sections/VisitToEarn.tsx b/src/components/sections/VisitToEarn.tsx new file mode 100644 index 0000000..2783d00 --- /dev/null +++ b/src/components/sections/VisitToEarn.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { useState } from "react"; +import { useUser } from "@/lib/context"; +import { MapPin, Sparkles } from "lucide-react"; + +export default function VisitToEarn() { + const { addLyts } = useUser(); + const [visitedStores, setVisitedStores] = useState([]); + const [mapClaimed, setMapClaimed] = useState(null); + + const checkinLocations = [ + { id: "cafe", name: "Brew & Co Cafe", reward: 8 }, + { id: "hm", name: "H&M旗舰店 Retail Store", reward: 15 }, + { id: "gym", name: "Core Fitness Gym", reward: 20 }, + ]; + + const handleCheckin = (id: string, name: string, reward: number) => { + if (visitedStores.includes(id)) return; + setVisitedStores((prev) => [...prev, id]); + addLyts(reward); + setMapClaimed(name); + setTimeout(() => setMapClaimed(null), 3000); + }; + + return ( +
+
+ +
+
+ + {/* Information */} +
+
+ +
+ +
+ Section 08 • Visit-to-Earn +

+ Location Check-Ins &
+ GPS Rewards +

+

+ Earn points simply by visiting local spaces. Confirm your location status via GPS check-ins at restaurants, parks, concerts, and malls. +

+
+
+ + {/* Map Simulator */} +
+ {mapClaimed && ( +
+
+ +

Checked in at {mapClaimed}!

+

LYT reward added directly to wallet

+
+
+ )} + +

Active nodes near you:

+
+ {checkinLocations.map((loc) => { + const visited = visitedStores.includes(loc.id); + return ( +
+
+ + {loc.name} +
+ +
+ ); + })} +
+
+ +
+
+ +
+
+ ); +} diff --git a/src/components/sections/WalkToEarn.tsx b/src/components/sections/WalkToEarn.tsx new file mode 100644 index 0000000..5f25f17 --- /dev/null +++ b/src/components/sections/WalkToEarn.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { useState } from "react"; +import { useUser } from "@/lib/context"; +import { Footprints, Check, RefreshCw } from "lucide-react"; +import { motion } from "framer-motion"; + +export default function WalkToEarn() { + const { profile, addLyts, incrementSteps } = useUser(); + const [simSteps, setSimSteps] = useState(2500); + const [walkClaimed, setWalkClaimed] = useState(false); + const walkReward = Math.floor(simSteps / 500); // 1 LYT / 500 steps + + const handleSimulateWalk = () => { + incrementSteps(simSteps); + addLyts(walkReward); + setWalkClaimed(true); + setTimeout(() => { + setWalkClaimed(false); + setSimSteps(2500); + }, 3000); + }; + + return ( +
+
+ +
+
+ + {/* Left side: Information */} +
+
+ +
+ +
+ Section 05 • Walk-to-Earn +

+ Step Sync &
+ Points Generation +

+

+ Go about your day. Next.js integration syncs steps in the background from your phone's Health API. Convert them to tradeable LYTs. +

+
+
+ + {/* Right side: Step Simulator Panel */} +
+
+
+ Simulate Distance + + +{simSteps.toLocaleString()} Steps + +
+
+ Rewards Wallet + + +{walkReward} LYTs + +
+
+ + {/* Slider */} + setSimSteps(parseInt(e.target.value))} + disabled={walkClaimed} + className="w-full h-1.5 bg-white/10 rounded-lg appearance-none cursor-pointer accent-primary" + /> + +
+ 500 Steps + 5,000 Steps + 10,000 Steps +
+ + +
+ +
+
+ +
+
+ ); +} diff --git a/src/components/story/SectionsA.tsx b/src/components/story/SectionsA.tsx new file mode 100644 index 0000000..c6dbfde --- /dev/null +++ b/src/components/story/SectionsA.tsx @@ -0,0 +1,341 @@ +import { Section, Pill, Card, Chip, Heading, Logo, Group, Reveal } from "./ui"; +import { C } from "./palette"; + +/* 01 — INTRO / HERO */ +export function Intro() { + return ( +
+ {/* giant watermark */} + + LYTS + +
+ + + +
+

we built this for you.

+

+ social currency is value earned for participating in life. +

+

a LYTIAN believes every meaningful interaction has value.

+
+

+ social +
+ currency +
+ for real life. +

+
+ + "earn. spend. live more." + + a quiet belief that everyday moments matter. + + +
+
+
+
+
psst
+ make everyday count. +
+
+
+ ); +} + +/* 02 — THE WHY */ +export function TheWhy() { + return ( +
+
+ 💌 hey. quick story before we dive in +
+
+ +
30+ yrs
+
+ OF LOYALTY = SPEND · EARN POINTS · REDEEM REWARDS{" "} + (the same old loop) +
+
+ + loyalty began after you swiped. +
+ the world moved on. +
+
+ SPEND MONEY + + EARN POINTS + + REDEEM + + ⏳ POINTS EXPIRE +
+
+
+ +
🎬 DISCOVER ON TIKTOK
+
brand relationships start before purchase
+
+ +
👯 COMMUNITIES DECIDE
+
influence > paid advertising
+
+ +
✨ REWARD PARTICIPATION
+
recognition, not coupons
+
+
+
+
+
+ ); +} + +/* 03 — THE PROBLEM */ +export function TheProblem() { + const cards = [ + { n: "01", t: "starts too late.", bg: "#fff", tc: "#0a0a0a", items: ["Visits a store", "Discovers a brand", "Refers a friend", "Attends an event", "Joins a community"], foot: "= $0 value today" }, + { n: "02", t: "points = useless.", bg: C.pink, tc: "#fff", items: ["Expiring numbers", "Transactional incentives", "Isolated brand currencies", "Complex redemptions", "Zero emotional bond"], foot: "no flex · no vibe." }, + { n: "03", t: "businesses stuck.", bg: C.lime, tc: "#0a0a0a", items: ["CAC is thru the roof", "Engagement nosedives", "Retention is melting", "Communities = missing", "Zero pre-purchase insight"], foot: "spend $$$ · earn $0." }, + ]; + return ( +
+ ⚡ the problem + + traditional loyalty is broken. +
+ here's why. +
+ + {cards.map((c) => ( + +
{c.n}
+
{c.t}
+
    + {c.items.map((i) => ( +
  • ✗ {i}
  • + ))} +
+
{c.foot}
+
+ ))} +
+
+ ❗ customers create value before spending. then traditional loyalty assigns zero credit. +
+
+ ); +} + +/* 04 — THE SHIFT */ +export function TheShift() { + return ( +
+
+ ⚡ the shift + + from loyalty to engagement. + +
+
+
📺 old world
+
rewards spending only
+
+ {[["🛒 OFFLINE COMMERCE", "still rewards only purchases"], ["📺 TV ADS · BANNERS", "one-way broadcast · no loop"], ["💳 SPEND-BASED POINTS", "transaction, no participation"]].map(([t, s]) => ( + +
{t}
+
{s}
+
+ ))} +
+
+
+
✨ new world
+
rewards participation
+
+ {[["📱 SOCIAL MEDIA", "rewards engagement + time", C.purple], ["🎮 GAMING", "rewards achievements + grind", C.pink], ["🎬 CREATOR PLATFORMS", "rewards contribution + community", C.orange]].map(([t, s, bg]) => ( + +
{t}
+
{s}
+
+ ))} +
+
+
+
+ 🔥 biggest miss in consumer tech right now: offline commerce still rewards only spending. that changes with LYTs. +
+
+
+ ); +} + +/* 05 — MEET LYTs */ +export function MeetLyts() { + return ( +
+ ✦ introducing +

LYTs.

+ +

+ a LYT is a unit of digital social currency you earn by participating in real life. one LYT = one moment of showing up. +

+
+
+ walk 🚶 + visit 📍 + friend 🤝 + shop 🛍️ + event 🎉 + + your LYTs balance + + spend + + live more +
+
+ ); +} + +/* 06 — ONE ORDINARY DAY */ +export function OneDay() { + const rows = [ + ["🚶", "you walked", "10,000 steps.", C.lime], + ["☕", "met a friend", "for coffee.", C.yellow], + ["📍", "visited", "a new place.", C.pink], + ["🏪", "supported", "a local business.", C.orange], + ["🌱", "showed up", "for life.", C.purple], + ] as const; + return ( +
+ 💚 one ordinary day + + here is what one ordinary day with LYTs looks like. + +
+ {rows.map(([e, a, b, c], i) => ( + +
+ {e} + {a} + {b} +
+
+ ))} +
+ +
you earned LYTs
+
because your life has value. 🤍
+
+
+ ); +} + +/* 07 — FEELINGS */ +export function Feelings() { + const cards = [ + ["🏆", "Recognition", "be seen, be credited", C.pink, "#fff"], + ["🌴", "Experiences", "real life, not receipts", C.yellow, "#0a0a0a"], + ["🫂", "Belonging", "community = currency", C.lime, "#0a0a0a"], + ["🪞", "Identity", "who you are matters", C.orange, "#fff"], + ["💬", "Community", "tribes win", "#0a0a0a", C.lime], + ] as const; + return ( +
+ 💗 why social currency + + people don't want points. they want feelings. + + + {cards.map(([e, t, s, bg, tc]) => ( + +
{e}
+
{t}
+
{s}
+
+ ))} +
+ +
0/5
+
+
TRADITIONAL LOYALTY SCORES
+
On every emotional need above, old-school points-based loyalty = 0%. LYTs is designed to reward human participation itself.
+
+
+
+ ); +} + +/* 08 — THE DIFFERENCE */ +export function Difference() { + return ( +
+ ⚡ the fundamental difference + + loyalty starts at spend. +
+ LYTs starts at engage. +
+
+
+ 💻 OLD WORLD +
TRADITIONAL LOYALTY
+
+
PURCHASE ↓
+
EARN POINTS ↓
+
REDEEM ↓
+
⏳ POINTS EXPIRE
+
+
🔥 not a better coupon. a whole new currency. 🌱
+
+
+ ✨ LYTS WAY +
LYTS SOCIAL CURRENCY
+
+ {[["DISCOVER", C.lime, "#0a0a0a"], ["ENGAGE", C.yellow, "#0a0a0a"], ["EARN LYTS", C.orange, "#fff"], ["SPEND", C.pink, "#fff"], ["LIVE MORE", "#fff", "#0a0a0a"], ["💞 LYTIAN", "#0a0a0a", C.lime]].map(([t, bg, tc]) => ( +
{t} {t !== "💞 LYTIAN" && "↓"}
+ ))} +
+
+
+
+ ); +} + +/* 09 — THE LOOP */ +export function TheLoop() { + const cards = [ + ["01", "DISCOVER", "brands, places, people worth meeting.", C.pink, "#fff"], + ["02", "ENGAGE", "visit, share, take the small challenge.", C.yellow, "#0a0a0a"], + ["03", "EARN LYTS", "every interaction turns into value.", C.lime, "#0a0a0a"], + ["04", "SPEND LYTS", "moments, access, experiences that matter.", C.orange, "#fff"], + ["05", "LIVE MORE", "explore, connect, feel alive.", C.purple, "#fff"], + ["06", "BECOME A LYTIAN", "the community you always wanted. 💞", "#0a0a0a", C.lime], + ] as const; + return ( +
+ 🚀 your 6-step loop + + six small moments. +
+ one big life that keeps going. +
+ + {cards.map(([n, t, s, bg, tc]) => ( + +
{n}
+
{t}
+
{s}
+
+ ))} +
+
♻️ every interaction creates value. every LYTIAN invites another.
+
+ ); +} diff --git a/src/components/story/SectionsB.tsx b/src/components/story/SectionsB.tsx new file mode 100644 index 0000000..88f467a --- /dev/null +++ b/src/components/story/SectionsB.tsx @@ -0,0 +1,294 @@ +import { Section, Pill, Card, Chip, Heading, Logo, Group, Reveal } from "./ui"; +import { C } from "./palette"; + +/* 10 — FOR YOU */ +export function ForYou() { + const cards = [ + ["🚶", "earn before spending", "value starts before purchase. discovery, visits, sharing.", C.pink, "#fff"], + ["🌅", "everyday life = value", "walking. exploring. visiting. sharing. everything matters.", C.yellow, "#0a0a0a"], + ["🌐", "one connected currency", "one LYTs balance across experiences and brands. no silos.", C.lime, "#0a0a0a"], + ["📢", "real recognition", "rewarded for contribution and engagement, not just transactions.", C.purple, "#fff"], + ] as const; + return ( +
+
+
+ 🙋 for you +

+ your life +
+ is worth +
+ something. +

+

every step. every visit. every selfie. it's all worth it here. 💗

+
+
+ +
value begins before purchase. 🌱
+
+ +
walk in. win more.
+
+ 10k steps? earn. coffee with a friend?{" "} + earn. sharing a vibe?{" "} + earn. +
+
+
+
+ + {cards.map(([e, t, s, bg, tc]) => ( + +
{e}
+
{t}
+
{s}
+
+ ))} +
+
+ ); +} + +/* 11 — FOR BUSINESS */ +export function ForBusiness() { + const cards = [ + ["1", "🤝 ACQUIRE", "CUSTOMERS EARLIER", "Engage them BEFORE the transaction.", C.pink, "#fff"], + ["2", "👟 FOOTFALL", "INCREASE VISITS", "Reward the showing up.", C.lime, "#0a0a0a"], + ["3", "📁 CAC", "SLASH ACQUISITION COSTS", "Organic loops replace paid ads.", C.yellow, "#0a0a0a"], + ["4", "🔁 KEEP", "STICKIER CUSTOMERS", "Continuous engagement = love.", C.orange, "#fff"], + ["5", "👥 TRIBE", "BUILD ADVOCATES", "Customers turn into LYTIANS.", C.purple, "#fff"], + ["6", "🧠 INSIGHT", "BETTER INTENT SIGNALS", "See them before the receipt.", "#fff", "#0a0a0a"], + ] as const; + return ( +
+ 🤝 for businesses + + acquire earlier. +
+ retain forever. +
+

meet people before they buy. keep them warm after. 💞

+ + {cards.map(([n, tag, t, s, bg, tc]) => ( + + {n} +
{tag}
+
{t}
+
{s}
+
+ ))} +
+
+ ); +} + +/* 12 — WHY NOW */ +export function WhyNow() { + const cards = [ + ["🌴", "EXPERIENCE ECONOMY", "Experiences > possessions.", "↑↑", "#fff", "#0a0a0a"], + ["👯", "COMMUNITY COMMERCE", "Tribes > ads.", "↑", C.yellow, "#0a0a0a"], + ["🤲", "PARTICIPATION REWARDED", "Attention = the new spend.", "↑↑", C.lime, "#0a0a0a"], + ["🎮", "GAMIFIED LIFE", "Systems make it real.", "↑", C.orange, "#fff"], + ["📉", "OLD LOYALTY FADING", "No real bond anymore.", "↓↓", C.pink, "#fff"], + ] as const; + return ( +
+ ⏰ why this matters + + 5 currents +
+ are converging right now. +
+ + {cards.map(([e, t, s, a, bg, tc]) => ( + +
{e}
+
{t}
+
{s}
+
{a}
+
+ ))} +
+
+ ); +} + +/* 13 — HOW IT WORKS */ +export function HowItWorks() { + const cards = [ + ["🚶", "Walk", "turn every step into social currency", C.pink, "#fff"], + ["📍", "Visit", "explore local spots, earn for showing up", C.yellow, "#0a0a0a"], + ["🤳", "Selfie", "turn memories into rewards", C.lime, "#0a0a0a"], + ["🎡", "Spin", "surprise LYTs + experiences daily", C.orange, "#fff"], + ["🎟️", "Scratch", "instant wins, hidden surprises", C.purple, "#fff"], + ["🏆", "Challenge", "missions, goals, achievements", C.lime, "#0a0a0a"], + ["🤝", "Friend", "invite + grow community together", C.purple, "#fff"], + ["🛍️", "Shop", "purchases still matter · earn more", "#0a0a0a", C.lime], + ["🎉", "Event", "show up, get rewarded for the moment", C.pink, "#fff"], + ["🏷️", "Brand", "campaigns + unlock exclusive LYTs", C.orange, "#fff"], + ] as const; + return ( +
+ ⚡ how it works +

+ every action earns points{" "} + LYTs. +

+

+ no app-opening rituals. no complicated loyalty rules.{" "} + no waiting until you spend. just live your life, engage the world, and watch your LYTs grow. 🌱 +

+ + {cards.map(([e, t, s, bg, tc]) => ( + + +LYTs +
{e}
+
{t}
+
{s}
+
+ ))} +
+
+ ); +} + +/* 14 — OLD vs NEW */ +export function OldVsNew() { + return ( +
+ 💞 a new kind of loyalty +

traditional loyalty said one thing.

+ + LYTs says another. + +
+ + 💻 TRADITIONAL LOYALTY +
+ the old rule
spend first.
earn later. +
+
value is created only when you buy. participation = invisible. 🫥
+
+
+ VS +
+
+ ✨ THE LYTS WAY +
+ the new rule
live first.
earn everywhere. +
+
value is created when you participate. everyday life is the unlock. 🎉
+
+
+
+ + discover · engage · earn LYTs · spend LYTs · live more · become a LYTIAN. + +
+
+ ); +} + +/* 15 — THE VISION */ +export function TheVision() { + const items = [ + ["every discovery has value", "#fff", "#0a0a0a"], + ["every experience has value", C.yellow, "#0a0a0a"], + ["every interaction has value", C.lime, "#0a0a0a"], + ["every community has value", C.orange, "#fff"], + ["every person can live more", C.purple, "#fff"], + ] as const; + return ( +
+
+
+ 🌈 the vision +

+ world's largest social currency ecosystem. +

+
+
+ {items.map(([t, bg, tc], i) => ( + +
+ + {t} +
+
+ ))} +
+
+
+ every meaningful interaction has value. 🤝 +
+
+ ); +} + +/* 16 — OUR BELIEF */ +export function OurBelief() { + return ( +
+
+ + 🌱 our belief + +
+
+
+ 1 +

+ traditional loyalty begins when you SPEND. +

+
+
+ 2 +
+

+ LYTs begins when you ENGAGE. +

+
+
+
+
+ ); +} + +/* 17 — THE PROMISE */ +export function ThePromise() { + const chips = [ + ["✦ Discover.", C.pink, "#fff"], + ["🌐 Engage.", C.yellow, "#0a0a0a"], + ["💰 Earn LYTs.", C.lime, "#0a0a0a"], + ["🛍️ Spend LYTs.", C.orange, "#fff"], + ["🌱 Live More.", C.purple, "#fff"], + ["💞 LYTIAN.", "#0a0a0a", C.lime], + ] as const; + return ( +
+
+ ⚡ the promise + +
+

+ the social currency +
+ for real life. +

+
+ {chips.map(([t, bg, tc]) => ( + {t} + ))} +
+
+ + 👉 Earn. Spend. Live More. — the rest is just coupons. + + + join the LYTIANS 🤝 + +
+
+ ); +} diff --git a/src/components/story/palette.ts b/src/components/story/palette.ts new file mode 100644 index 0000000..a14ad4b --- /dev/null +++ b/src/components/story/palette.ts @@ -0,0 +1,12 @@ +/* Shared color palette — plain module (no "use client") so it can be + imported as real values by both Server and Client components. */ +export const C = { + pink: "#FF2E88", + hotpink: "#FF1B6D", + purple: "#A020F0", + lime: "#C8FF37", + yellow: "#FFD21E", + orange: "#FF6B1A", + black: "#0a0a0a", + cream: "#FBF3DD", +}; diff --git a/src/components/story/ui.tsx b/src/components/story/ui.tsx new file mode 100644 index 0000000..0cbd512 --- /dev/null +++ b/src/components/story/ui.tsx @@ -0,0 +1,228 @@ +"use client"; + +import { ReactNode } from "react"; +import Image from "next/image"; +import { motion, Variants } from "framer-motion"; +import { C } from "./palette"; + +export { C }; + +const viewport = { once: true, amount: 0.25 } as const; + +/* stagger container variants */ +export const stagger: Variants = { + hidden: {}, + show: { transition: { staggerChildren: 0.09, delayChildren: 0.05 } }, +}; +export const riseItem: Variants = { + hidden: { opacity: 0, y: 30, scale: 0.96 }, + show: { + opacity: 1, + y: 0, + scale: 1, + transition: { type: "spring", stiffness: 130, damping: 16 }, + }, +}; + +/* ------- Section ------- */ +type SectionProps = { + id: string; + children: ReactNode; + variant?: "cream" | "deep" | "night" | "plain"; + className?: string; +}; + +export function Section({ id, children, variant = "deep", className = "" }: SectionProps) { + const bg = + variant === "cream" + ? "bg-cream text-[#0a0a0a]" + : variant === "night" + ? "bg-night text-white" + : variant === "plain" + ? "bg-[#120a1f] text-white" + : "bg-deep text-white"; + return ( +
+
{children}
+
+ ); +} + +/* ------- Reveal (fade/slide up on scroll) ------- */ +export function Reveal({ + children, + className = "", + delay = 0, + y = 26, +}: { + children: ReactNode; + className?: string; + delay?: number; + y?: number; +}) { + return ( + + {children} + + ); +} + +/* ------- Group (stagger children that use riseItem) ------- */ +export function Group({ + children, + className = "", +}: { + children: ReactNode; + className?: string; +}) { + return ( + + {children} + + ); +} + +/* ------- Pill ------- */ +export function Pill({ + children, + bg = C.pink, + text = "#fff", +}: { + children: ReactNode; + bg?: string; + text?: string; +}) { + return ( + + {children} + + ); +} + +/* ------- Card (scroll-reveal + neobrutalist hover) ------- */ +export function Card({ + children, + bg = "#fff", + text = "#0a0a0a", + className = "", + rounded = "rounded-2xl", + delay = 0, + group = false, +}: { + children: ReactNode; + bg?: string; + text?: string; + className?: string; + rounded?: string; + delay?: number; + /** set true when rendered inside a so stagger drives the entrance */ + group?: boolean; +}) { + const entrance = group + ? { variants: riseItem } + : { + initial: { opacity: 0, y: 30, scale: 0.96 }, + whileInView: { opacity: 1, y: 0, scale: 1 }, + viewport, + transition: { type: "spring" as const, stiffness: 130, damping: 16, delay }, + }; + return ( + + {children} + + ); +} + +/* ------- Chip ------- */ +export function Chip({ + children, + bg = C.lime, + text = "#0a0a0a", + delay = 0, +}: { + children: ReactNode; + bg?: string; + text?: string; + delay?: number; +}) { + return ( + + {children} + + ); +} + +/* ------- Heading (self-animating) ------- */ +export function Heading({ + children, + className = "", +}: { + children: ReactNode; + className?: string; +}) { + return ( + + {children} + + ); +} + +/* LytsUp wordmark */ +export function Logo({ className = "" }: { className?: string }) { + return ( + LytsUp + ); +} diff --git a/src/lib/context.tsx b/src/lib/context.tsx new file mode 100644 index 0000000..c0e82f2 --- /dev/null +++ b/src/lib/context.tsx @@ -0,0 +1,160 @@ +"use client"; + +import React, { createContext, useContext, useState, useEffect } from "react"; + +interface UserProfile { + name: string; + level: number; + xp: number; + xpToNextLevel: number; + streak: number; + stepsToday: number; + stepGoal: number; + lytBalance: number; +} + +interface Voucher { + id: string; + brand: string; + logo: string; + discount: string; + lytCost: number; + code: string; + claimedAt: string; +} + +interface UserContextType { + profile: UserProfile; + vouchers: Voucher[]; + addLyts: (amount: number) => void; + spendLyts: (amount: number) => boolean; + incrementSteps: (amount: number) => void; + claimVoucher: (brand: string, discount: string, cost: number) => { success: boolean; code?: string }; +} + +const UserContext = createContext(undefined); + +export function UserProvider({ children }: { children: React.ReactNode }) { + const [profile, setProfile] = useState({ + name: "Alex Stark", + level: 4, + xp: 680, + xpToNextLevel: 1000, + streak: 5, + stepsToday: 6420, + stepGoal: 10000, + lytBalance: 450, + }); + + const [vouchers, setVouchers] = useState([]); + + // Function to add LYTs and award XP + const addLyts = (amount: number) => { + setProfile((prev) => { + const newBalance = prev.lytBalance + amount; + const addedXp = amount * 2; + let newXp = prev.xp + addedXp; + let newLevel = prev.level; + let newXpToNext = prev.xpToNextLevel; + + if (newXp >= newXpToNext) { + newLevel += 1; + newXp = newXp - newXpToNext; + newXpToNext = Math.round(newXpToNext * 1.5); + } + + return { + ...prev, + lytBalance: newBalance, + xp: newXp, + level: newLevel, + xpToNextLevel: newXpToNext, + }; + }); + }; + + // Function to spend LYTs + const spendLyts = (amount: number): boolean => { + let success = false; + setProfile((prev) => { + if (prev.lytBalance >= amount) { + success = true; + return { + ...prev, + lytBalance: prev.lytBalance - amount, + }; + } + return prev; + }); + return success; + }; + + // Function to update steps + const incrementSteps = (amount: number) => { + setProfile((prev) => { + const newSteps = Math.min(25000, prev.stepsToday + amount); + // Earn 1 LYT for every 500 steps taken + const stepDiff = newSteps - prev.stepsToday; + const lytReward = Math.floor(stepDiff / 500); + + return { + ...prev, + stepsToday: newSteps, + lytBalance: prev.lytBalance + (lytReward > 0 ? lytReward : 0), + }; + }); + }; + + // Function to claim brand vouchers + const claimVoucher = (brand: string, discount: string, cost: number) => { + if (profile.lytBalance < cost) { + return { success: false }; + } + + const claimSuccess = spendLyts(cost); + if (!claimSuccess) return { success: false }; + + // Generate a random alphanumeric code + const generatedCode = Math.random().toString(36).substring(2, 8).toUpperCase() + "-" + cost; + + const newVoucher: Voucher = { + id: Math.random().toString(36).substring(2, 9), + brand, + logo: `/images/logo-${brand.toLowerCase().replace(/\s+/g, "")}.png`, + discount, + lytCost: cost, + code: generatedCode, + claimedAt: new Date().toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + }), + }; + + setVouchers((prev) => [newVoucher, ...prev]); + return { success: true, code: generatedCode }; + }; + + return ( + + {children} + + ); +} + +export function useUser() { + const context = useContext(UserContext); + if (context === undefined) { + throw new Error("useUser must be used within a UserProvider"); + } + return context; +}