import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { motion, useReducedMotion } from "motion/react"; import { Activity, ArrowUpRight, Mail, Megaphone, ShieldCheck, TrendingUp, Users } from "lucide-react"; import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; import StatCard from "../components/StatCard"; import { Skeleton } from "../components/Skeleton"; import SStatCard from "./s/components/StatCard"; import SGlassCard from "./s/components/GlassCard"; import { cn } from "../utils/cn"; import { ModuleId } from "./registry"; /* ------------------------------------------------------------------ */ /* Deterministic seeded PRNG (mulberry32) — re-seeded on every launch. */ /* ------------------------------------------------------------------ */ export function mulberry32(seed: number) { let a = seed >>> 0; return () => { a |= 0; a = (a + 0x6d2b79f5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } export function hashModuleId(id: string): number { let h = 2166136261; for (let i = 0; i < id.length; i++) { h ^= id.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; } /* Slow-motion reassembly curve mandated by the brief. */ export const ASSEMBLY_EASE: [number, number, number, number] = [0.22, 1, 0.36, 1]; interface PieceTransform { x: number; y: number; rotate: number; scale: number; delay: number; duration: number; } interface PieceSpec { key: string; span: string; // grid column span classes from: PieceTransform; } const MIN_DISTANCE = 240; const MAX_DISTANCE = 720; const MIN_STAGGER_BASE = 40; const MAX_STAGGER_JITTER = 50; // stagger = i*55 + rand(40..90) per brief const MIN_DURATION = 2.4; const MAX_DURATION = 3.2; export function buildPieceSpecs(rand: () => number, count: number): PieceSpec[] { const spans = [ "sm:col-span-3 sm:row-span-1 col-span-6", "sm:col-span-3 sm:row-span-1 col-span-6", "sm:col-span-3 sm:row-span-1 col-span-6", "sm:col-span-3 sm:row-span-1 col-span-6", "sm:col-span-4 sm:row-span-2 col-span-6", "sm:col-span-2 sm:row-span-2 col-span-6", "col-span-6", "sm:col-span-2 col-span-6", "sm:col-span-2 col-span-6", "sm:col-span-2 col-span-6", "col-span-6", ]; return Array.from({ length: count }, (_, i) => { const angle = rand() * Math.PI * 2; const distance = MIN_DISTANCE + rand() * (MAX_DISTANCE - MIN_DISTANCE); const direction = i % 2 === 0 ? 1 : -1; // pieces fly in from both sides of the canvas return { key: `piece-${i}`, span: spans[i % spans.length], from: { x: Math.cos(angle) * distance * direction, y: Math.sin(angle) * distance, rotate: (8 + rand() * 37) * (rand() > 0.5 ? 1 : -1), scale: 0.55 + rand() * 0.8, delay: i * 55 + MIN_STAGGER_BASE + rand() * MAX_STAGGER_JITTER, duration: MIN_DURATION + rand() * (MAX_DURATION - MIN_DURATION), }, }; }); } /* ------------------------------------------------------------------ */ /* Real dashboard primitives rendered by the assembly grid */ /* ------------------------------------------------------------------ */ const CHART_DATA = [ { d: "Mon", v: 3200, b: 2100 }, { d: "Tue", v: 4100, b: 2600 }, { d: "Wed", v: 3800, b: 2400 }, { d: "Thu", v: 5200, b: 3100 }, { d: "Fri", v: 4800, b: 2900 }, { d: "Sat", v: 6100, b: 3600 }, { d: "Sun", v: 7400, b: 4200 }, ]; const LIST_ROWS = [ { email: "ava.reyes@northwind.io", badge: "DELIVERED" as const, tone: "text-emerald-300 bg-emerald-500/10 border-emerald-500/20" }, { email: "marcus.chen@brightlab.co", badge: "OPENED" as const, tone: "text-sky-300 bg-sky-500/10 border-sky-500/20" }, { email: "lena.hoffmann@vanta.dev", badge: "REPLIED" as const, tone: "text-violet-300 bg-violet-500/10 border-violet-500/20" }, { email: "j.torres@quanticloud.ai", badge: "ACTIVE" as const, tone: "text-emerald-300 bg-emerald-500/10 border-emerald-500/20" }, { email: "priya.nair@helioshift.com", badge: "QUEUED" as const, tone: "text-zinc-300 bg-white/5 border-white/10" }, ]; interface AnimationProps { module: ModuleId; mode: "enter" | "exit"; /** Re-seed source: Date.now() ^ hashModuleId(module) ^ launchSeq */ seed: number; onDone: () => void; /** Accent colors from the active module theme (engineLabel uses it too). */ accent: string; accentSoft: string; } export default function ModuleLaunchAnimation({ module, mode, seed, onDone, accent, accentSoft }: AnimationProps) { const prefersReducedMotion = useReducedMotion(); const [play, setPlay] = useState(false); const [settled, setSettled] = useState>(new Set()); const doneRef = useRef(false); const watchdogRef = useRef(null); const specs = useMemo( () => buildPieceSpecs(mulberry32(seed), 11), [seed] ); // Double-rAF: mount pieces in their hidden state first, then start the // timeline — guarantees the first paint of the overlay is cheap (no jank). useEffect(() => { if (prefersReducedMotion) { setPlay(true); setSettled(new Set(specs.map((_, i) => i))); return; } let raf2 = 0; const raf1 = requestAnimationFrame(() => { raf2 = requestAnimationFrame(() => setPlay(true)); }); return () => { cancelAnimationFrame(raf1); cancelAnimationFrame(raf2); }; }, [prefersReducedMotion, specs]); const finish = useCallback(() => { if (doneRef.current) return; doneRef.current = true; onDone(); }, [onDone]); // Watchdog is the single source of truth for completion (deterministic: // longest stagger + longest duration + margin). onAnimationComplete only // drives will-change cleanup. useEffect(() => { if (!play) return; const maxTime = Math.max(...specs.map((s) => s.from.delay + s.from.duration * 1000)); watchdogRef.current = window.setTimeout( finish, prefersReducedMotion ? 0 : (mode === "enter" ? maxTime + 120 : maxTime) + 80 ); return () => { if (watchdogRef.current) window.clearTimeout(watchdogRef.current); }; }, [play, specs, finish, mode, prefersReducedMotion]); const markSettled = useCallback((i: number) => { setSettled((prev) => { if (prev.has(i)) return prev; const next = new Set(prev); next.add(i); return next; }); }, []); const isEnter = mode === "enter"; const StatCardVariant = module === "s" ? SStatCard : StatCard; const Frame = module === "s" ? SGlassCard : React.Fragment; const frameProps = module === "s" ? { className: "p-4" } : {}; return ( ); } /* ------------------------------------------------------------------ */ /* Piece renderer — every slot is a REAL platform component */ /* ------------------------------------------------------------------ */ interface PieceProps { index: number; module: ModuleId; accent: string; accentSoft: string; StatCardVariant: typeof StatCard; Frame: React.ComponentType<{ children?: React.ReactNode; className?: string }> | typeof React.Fragment; frameProps: Record; } function Piece({ index, module, accent, accentSoft, StatCardVariant, Frame, frameProps }: PieceProps) { switch (index) { case 0: return ( ); case 1: return ( ); case 2: return ( ); case 3: return ( ); case 4: return (
Sequence telemetry
LIVE
); case 5: return (
Live feed
{LIST_ROWS.map((row) => (
{row.email}
{row.badge}
))}
); case 6: return (
{["SEQUENCES", "WARMUP", "UNIBOX"].map((tag) => ( {tag} ))} DELIVERABILITY
); case 7: return ( ); case 8: return ; case 9: return ( ); default: return (
); } }