function Thread() { const [progress, setProgress] = React.useState(0); const [docH, setDocH] = React.useState(0); const [vh, setVh] = React.useState(900); const [headPt, setHeadPt] = React.useState({ x: 80, y: 60 }); const pathRef = React.useRef(null); // Path drawn through the major scenes — viewBox 1440 x 7000. // Height stretches to docH so anchors map proportionally to scenes. const pathD = [ "M 60 240", "C 220 360, 520 540, 1180 700", // hero → cube (starts mid-hero, visible immediately) "S 1320 1080, 880 1300", // cube → marquee "S 180 1720, 560 2000", // marquee → promotion "S 1300 2400, 1100 2700", // promotion poster (lit center) "C 1320 2900, 1340 3060, 1320 3200", // dive down along right edge of LLM card "C 1280 3290, 1100 3320, 1000 3350", // sweep left through CREATIVE card "C 860 3390, 720 3410, 620 3430", // hook into AI mark — end with spark "S 1300 3700, 720 4000", // ai → clients (sweep) "S 180 4400, 1100 4720", // clients diagonal "S 580 5120, 1120 5400", // → cases (focused frame) "S 220 5760, 800 6020", // cases → cta "C 1320 6200, 1280 6480, 720 6520", // cta swirl approach "C 600 6560, 880 6620, 720 6800", // tiny curl into footer © ].join(" "); // Anchors in viewBox coords — moments where the thread "touches" main objects const anchors = React.useMemo(() => [ { x: 1180, y: 700, k: "cube", r: 56 }, { x: 880, y: 1300, k: "marquee", r: 38 }, { x: 1100, y: 2700, k: "poster", r: 64 }, { x: 620, y: 3430, k: "ai", r: 80 }, { x: 1100, y: 5400, k: "case", r: 44 }, { x: 720, y: 6500, k: "cta", r: 70 }, ], []); React.useEffect(() => { const measure = () => { setDocH(document.documentElement.scrollHeight); setVh(window.innerHeight); }; const onScroll = () => { const h = document.documentElement.scrollHeight - window.innerHeight; setProgress(Math.min(1, Math.max(0, window.scrollY / Math.max(1, h)))); }; measure(); onScroll(); // Re-measure after fonts/layout settle const t1 = setTimeout(measure, 600); const t2 = setTimeout(measure, 1800); window.addEventListener("scroll", onScroll, { passive: true }); window.addEventListener("resize", measure); return () => { clearTimeout(t1); clearTimeout(t2); window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", measure); }; }, []); // Update the traveling head along the path React.useEffect(() => { if (!pathRef.current) return; try { const len = pathRef.current.getTotalLength(); if (!len) return; const pt = pathRef.current.getPointAtLength(progress * len); setHeadPt({ x: pt.x, y: pt.y }); } catch (e) {} }, [progress, docH]); const scrollPx = progress * Math.max(1, docH - vh); return ( <> {/* BACK layer — sits behind
; weaves "under" cards */}
{/* FRONT layer — sparks + traveling head pop OVER main content */}
{anchors.map((a, i) => { const anchorPx = (a.y / 7000) * docH; const active = scrollPx + vh * 0.45 > anchorPx; return (
{a.k}
); })} {/* Traveling head — follows current scroll position along the path */}
0.01 && progress < 0.99 ? 1 : 0, }} >
); } window.Thread = Thread;