Spaces:
Runtime error
Runtime error
| "use client"; | |
| import React, { useState, useEffect } from "react"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { useRouter } from "next/navigation"; | |
| /* βββββββββββββββββββ Constants βββββββββββββββββββ */ | |
| const STEPS = [ | |
| "Scanning document...", | |
| "Extracting key concepts...", | |
| "Building knowledge graph...", | |
| "Forging interactive stages...", | |
| "Polishing your universe...", | |
| ]; | |
| const STEP_DURATION = 2000; // ms per step | |
| /* βββββββββββββββββββ Page βββββββββββββββββββ */ | |
| export default function ForgePage() { | |
| const router = useRouter(); | |
| const [stepIdx, setStepIdx] = useState(0); | |
| const [charIdx, setCharIdx] = useState(0); | |
| const [flash, setFlash] = useState(false); | |
| /* Typewriter per step */ | |
| useEffect(() => { | |
| if (charIdx < STEPS[stepIdx].length) { | |
| const t = setTimeout(() => setCharIdx((c) => c + 1), 38); | |
| return () => clearTimeout(t); | |
| } | |
| }, [charIdx, stepIdx]); | |
| /* Advance steps */ | |
| useEffect(() => { | |
| if (stepIdx >= STEPS.length) return; | |
| const t = setTimeout(() => { | |
| if (stepIdx < STEPS.length - 1) { | |
| setStepIdx((s) => s + 1); | |
| setCharIdx(0); | |
| } else { | |
| // Last step done β flash + navigate | |
| setFlash(true); | |
| setTimeout(() => router.push("/map/med-u1"), 800); | |
| } | |
| }, STEP_DURATION); | |
| return () => clearTimeout(t); | |
| }, [stepIdx, router]); | |
| const progress = ((stepIdx + 1) / STEPS.length) * 100; | |
| return ( | |
| <div className="relative min-h-screen flex flex-col items-center justify-center overflow-hidden bg-gradient-to-br from-[#edf7fb] via-[#c9e6f2] to-[#a3d5e8]"> | |
| {/* ββ Background particles ββ */} | |
| <ForgeBg /> | |
| {/* ββ Flash overlay ββ */} | |
| <AnimatePresence> | |
| {flash && ( | |
| <motion.div | |
| className="fixed inset-0 z-[200] bg-white" | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| transition={{ duration: 0.5 }} | |
| /> | |
| )} | |
| </AnimatePresence> | |
| {/* ββ Main content ββ */} | |
| <div className="relative z-10 flex flex-col items-center gap-8 px-4"> | |
| {/* Anvil animation */} | |
| <motion.div | |
| animate={{ | |
| scale: [1, 1.04, 1], | |
| }} | |
| transition={{ | |
| repeat: Infinity, | |
| duration: 2.5, | |
| ease: "easeInOut", | |
| }} | |
| > | |
| <ForgeAnvil progress={progress} /> | |
| </motion.div> | |
| {/* Typewriter text */} | |
| <div className="h-10 flex items-center"> | |
| <motion.p | |
| key={stepIdx} | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="font-heading text-xl md:text-2xl font-bold text-brand-gray-700 tracking-wide" | |
| > | |
| {STEPS[stepIdx].slice(0, charIdx)} | |
| <motion.span | |
| animate={{ opacity: [1, 0] }} | |
| transition={{ repeat: Infinity, duration: 0.6 }} | |
| className="inline-block w-0.5 h-5 bg-brand-teal ml-0.5 align-middle" | |
| /> | |
| </motion.p> | |
| </div> | |
| {/* Progress dots */} | |
| <div className="flex gap-2.5"> | |
| {STEPS.map((_, i) => ( | |
| <motion.div | |
| key={i} | |
| className={`h-2.5 rounded-full transition-all duration-500 ${ | |
| i <= stepIdx | |
| ? "bg-brand-teal w-8" | |
| : "bg-brand-gray-200 w-2.5" | |
| }`} | |
| layout | |
| /> | |
| ))} | |
| </div> | |
| {/* Subtle sub-text */} | |
| <p className="text-sm text-brand-gray-400 mt-2"> | |
| Forging your personalised learning universe⦠| |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| /* βββββββββββββββββββ Anvil SVG βββββββββββββββββββ */ | |
| function ForgeAnvil({ progress }: { progress: number }) { | |
| return ( | |
| <div className="relative w-72 h-72 md:w-80 md:h-80"> | |
| {/* Glow behind anvil */} | |
| <div | |
| className="absolute inset-0 rounded-full blur-3xl" | |
| style={{ | |
| background: `radial-gradient(circle, rgba(122,199,196,${0.12 + progress * 0.002}) 0%, rgba(212,169,106,${0.08 + progress * 0.002}) 50%, transparent 70%)`, | |
| }} | |
| /> | |
| <svg viewBox="0 0 400 400" className="w-full h-full" fill="none"> | |
| <defs> | |
| {/* Runic circle path */} | |
| <path | |
| id="forgeRune" | |
| d="M 200,200 m -155,0 a 155,155 0 1,1 310,0 a 155,155 0 1,1 -310,0" | |
| /> | |
| <radialGradient id="anvilGlow" cx="50%" cy="40%" r="50%"> | |
| <stop offset="0%" stopColor="#FFD5B0" stopOpacity="0.5" /> | |
| <stop offset="60%" stopColor="#F5C8A0" stopOpacity="0.2" /> | |
| <stop offset="100%" stopColor="transparent" /> | |
| </radialGradient> | |
| <linearGradient id="anvilBody" x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="0%" stopColor="#E8D5B7" /> | |
| <stop offset="100%" stopColor="#C4A882" /> | |
| </linearGradient> | |
| <linearGradient id="anvilTop" x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="0%" stopColor="#F0E0CC" /> | |
| <stop offset="100%" stopColor="#D4B896" /> | |
| </linearGradient> | |
| <linearGradient id="ringGrad" x1="0" y1="0" x2="1" y2="1"> | |
| <stop offset="0%" stopColor="#7AC7C4" /> | |
| <stop offset="100%" stopColor="#D4A96A" /> | |
| </linearGradient> | |
| </defs> | |
| {/* Outer runic ring */} | |
| <circle cx="200" cy="200" r="170" fill="none" stroke="#D4BC8B" strokeWidth="1" opacity="0.25" /> | |
| <circle cx="200" cy="200" r="160" fill="none" stroke="#D4BC8B" strokeWidth="0.5" opacity="0.2" /> | |
| {/* Runic text rotating */} | |
| <g opacity="0.3"> | |
| <animateTransform | |
| attributeName="transform" | |
| type="rotate" | |
| from="0 200 200" | |
| to="360 200 200" | |
| dur="40s" | |
| repeatCount="indefinite" | |
| /> | |
| <text fill="#C4A87A" fontSize="13" fontWeight="500" letterSpacing="5"> | |
| <textPath href="#forgeRune"> | |
| α α’α¦α¨α±α²α·αΉαΊαΎαααααααααααααααα α’α¦α¨α±α²α·αΉαΊαΎαααααααααααααααα α’α¦α¨α± | |
| </textPath> | |
| </text> | |
| </g> | |
| {/* Inner glow disc */} | |
| <circle cx="200" cy="200" r="120" fill="url(#anvilGlow)" /> | |
| {/* ββ The Anvil ββ */} | |
| {/* Base */} | |
| <rect x="155" y="280" width="90" height="35" rx="4" fill="url(#anvilBody)" /> | |
| <rect x="170" y="260" width="60" height="24" rx="3" fill="#D4B896" /> | |
| {/* Anvil body β the working surface */} | |
| <path | |
| d="M120 260 Q125 230 140 225 L155 220 L155 240 Q165 250 200 250 Q235 250 245 240 L245 220 L260 225 Q275 230 280 260 Z" | |
| fill="url(#anvilTop)" | |
| /> | |
| {/* Horn (left beak) */} | |
| <path | |
| d="M120 260 Q105 255 90 248 Q85 246 88 244 Q95 240 120 245 Z" | |
| fill="#D4B896" | |
| /> | |
| {/* Top face highlight */} | |
| <path | |
| d="M140 225 L155 220 L155 240 Q165 250 200 250 Q235 250 245 240 L245 220 L260 225 Q255 228 245 230 Q220 238 200 238 Q180 238 155 230 Q145 228 140 225 Z" | |
| fill="white" | |
| opacity="0.15" | |
| /> | |
| {/* ββ Wireframe / neural-net overlay on anvil ββ */} | |
| {/* Nodes */} | |
| {[ | |
| [170, 185], [200, 170], [230, 185], | |
| [155, 210], [200, 200], [245, 210], | |
| [175, 230], [225, 230], | |
| ].map(([cx, cy], i) => ( | |
| <g key={i}> | |
| <circle cx={cx} cy={cy} r="4" fill="#7AC7C4" opacity="0.5"> | |
| <animate | |
| attributeName="opacity" | |
| values="0.3;0.7;0.3" | |
| dur={`${1.5 + i * 0.3}s`} | |
| repeatCount="indefinite" | |
| /> | |
| </circle> | |
| <circle cx={cx} cy={cy} r="2" fill="white" opacity="0.6" /> | |
| </g> | |
| ))} | |
| {/* Edges */} | |
| {[ | |
| [170, 185, 200, 170], [200, 170, 230, 185], | |
| [155, 210, 200, 200], [200, 200, 245, 210], | |
| [170, 185, 155, 210], [230, 185, 245, 210], | |
| [170, 185, 200, 200], [200, 200, 230, 185], | |
| [155, 210, 175, 230], [245, 210, 225, 230], | |
| [175, 230, 200, 200], [225, 230, 200, 200], | |
| ].map(([x1, y1, x2, y2], i) => ( | |
| <line | |
| key={i} | |
| x1={x1} y1={y1} x2={x2} y2={y2} | |
| stroke="#7AC7C4" | |
| strokeWidth="1" | |
| opacity="0.25" | |
| /> | |
| ))} | |
| {/* ββ Forging rings (orbit) ββ */} | |
| <g> | |
| <animateTransform | |
| attributeName="transform" | |
| type="rotate" | |
| from="0 200 210" | |
| to="360 200 210" | |
| dur="6s" | |
| repeatCount="indefinite" | |
| /> | |
| <ellipse cx="200" cy="210" rx="90" ry="30" fill="none" stroke="url(#ringGrad)" strokeWidth="1.5" opacity="0.3" /> | |
| <circle cx="290" cy="210" r="3" fill="#7AC7C4" opacity="0.7"> | |
| <animate attributeName="opacity" values="0.4;1;0.4" dur="2s" repeatCount="indefinite" /> | |
| </circle> | |
| </g> | |
| <g> | |
| <animateTransform | |
| attributeName="transform" | |
| type="rotate" | |
| from="0 200 210" | |
| to="-360 200 210" | |
| dur="8s" | |
| repeatCount="indefinite" | |
| /> | |
| <ellipse cx="200" cy="210" rx="105" ry="22" fill="none" stroke="#D4A96A" strokeWidth="1" opacity="0.2" /> | |
| <circle cx="305" cy="210" r="2.5" fill="#D4A96A" opacity="0.6"> | |
| <animate attributeName="opacity" values="0.3;0.8;0.3" dur="2.5s" repeatCount="indefinite" /> | |
| </circle> | |
| </g> | |
| {/* ββ Center spark ββ */} | |
| <circle cx="200" cy="210" r="8" fill="#FFD5B0" opacity="0.3"> | |
| <animate attributeName="r" values="6;10;6" dur="2s" repeatCount="indefinite" /> | |
| <animate attributeName="opacity" values="0.2;0.5;0.2" dur="2s" repeatCount="indefinite" /> | |
| </circle> | |
| {/* Sparkles */} | |
| <g opacity="0.5"> | |
| <circle cx="145" cy="175" r="2" fill="#D4A96A"> | |
| <animate attributeName="opacity" values="0;0.7;0" dur="3s" repeatCount="indefinite" /> | |
| </circle> | |
| <circle cx="260" cy="185" r="1.5" fill="#7AC7C4"> | |
| <animate attributeName="opacity" values="0;0.8;0" dur="2.5s" begin="0.5s" repeatCount="indefinite" /> | |
| </circle> | |
| <circle cx="200" cy="155" r="2" fill="#D4A96A"> | |
| <animate attributeName="opacity" values="0;0.6;0" dur="3.5s" begin="1s" repeatCount="indefinite" /> | |
| </circle> | |
| <circle cx="165" cy="245" r="1.5" fill="#7AC7C4"> | |
| <animate attributeName="opacity" values="0;0.7;0" dur="2.8s" begin="0.3s" repeatCount="indefinite" /> | |
| </circle> | |
| <circle cx="240" cy="250" r="2" fill="#D4A96A"> | |
| <animate attributeName="opacity" values="0;0.6;0" dur="3.2s" begin="0.8s" repeatCount="indefinite" /> | |
| </circle> | |
| </g> | |
| </svg> | |
| </div> | |
| ); | |
| } | |
| /* βββββββββββββββββββ Background βββββββββββββββββββ */ | |
| function ForgeBg() { | |
| return ( | |
| <div className="pointer-events-none absolute inset-0 z-0 overflow-hidden" aria-hidden="true"> | |
| {/* Floating particles */} | |
| {[...Array(14)].map((_, i) => ( | |
| <div | |
| key={`p-${i}`} | |
| className="absolute rounded-full particle" | |
| style={{ | |
| width: `${2 + Math.random() * 4}px`, | |
| height: `${2 + Math.random() * 4}px`, | |
| left: `${5 + Math.random() * 90}%`, | |
| bottom: `${Math.random() * 15}%`, | |
| opacity: 0.3 + Math.random() * 0.3, | |
| background: i % 2 === 0 ? "#7AC7C4" : "#D4A96A", | |
| animationDelay: `${Math.random() * 6}s`, | |
| animationDuration: `${5 + Math.random() * 5}s`, | |
| }} | |
| /> | |
| ))} | |
| {/* Sparkles */} | |
| {[...Array(8)].map((_, i) => ( | |
| <div | |
| key={`s-${i}`} | |
| className="absolute sparkle" | |
| style={{ | |
| width: `${3 + Math.random() * 4}px`, | |
| height: `${3 + Math.random() * 4}px`, | |
| left: `${10 + Math.random() * 80}%`, | |
| top: `${10 + Math.random() * 80}%`, | |
| opacity: 0.2 + Math.random() * 0.3, | |
| background: i % 3 === 0 ? "#D4A96A" : "#7AC7C4", | |
| borderRadius: "50%", | |
| animationDelay: `${Math.random() * 4}s`, | |
| }} | |
| /> | |
| ))} | |
| {/* Soft radial glows */} | |
| <div className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full bg-brand-teal/5 blur-3xl" /> | |
| <div className="absolute bottom-1/4 right-1/4 w-80 h-80 rounded-full bg-[#D4A96A]/5 blur-3xl" /> | |
| </div> | |
| ); | |
| } | |