README / app /forge /page.tsx
kaigiii's picture
Deploy Learn8 Demo Space
5c920e9
"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>
);
}