Spaces:
Running
Running
BRIDGE Platform Agent
feat(platform): BRIDGE Modules hub with /modules/e and /modules/s routed workspaces
1955a0f Download src/modules/ModuleLaunchAnimation.tsx from sitcod3/BRIDGE_Modul-S_prod: direct link, hf CLI and curl.
- Browser
- Download file 15.8 kB
-
https://huggingface.co/spaces/sitcod3/BRIDGE_Modul-S_prod/resolve/main/src/modules/ModuleLaunchAnimation.tsx
- Command line
-
hf download hf://spaces/sitcod3/BRIDGE_Modul-S_prod/src/modules/ModuleLaunchAnimation.tsx
-
curl -L -o ModuleLaunchAnimation.tsx https://huggingface.co/spaces/sitcod3/BRIDGE_Modul-S_prod/resolve/main/src/modules/ModuleLaunchAnimation.tsx
15.8 kB
| 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<Set<number>>(new Set()); | |
| const doneRef = useRef(false); | |
| const watchdogRef = useRef<number | null>(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 ( | |
| <div | |
| className="absolute inset-0 z-30 flex items-center justify-center overflow-hidden p-4 md:p-8 pointer-events-none" | |
| aria-hidden="true" | |
| data-testid="module-launch-animation" | |
| > | |
| <div className="grid grid-cols-6 auto-rows-min gap-3 md:gap-4 w-full max-w-5xl"> | |
| {specs.map((spec, i) => { | |
| const initial = | |
| isEnter && !prefersReducedMotion | |
| ? { x: spec.from.x, y: spec.from.y, rotate: spec.from.rotate, scale: spec.from.scale, opacity: 0 } | |
| : isEnter | |
| ? { x: 0, y: 0, rotate: 0, scale: 1, opacity: 1 } | |
| : { x: 0, y: 0, rotate: 0, scale: 1, opacity: 1 }; | |
| const animate = isEnter | |
| ? { x: 0, y: 0, rotate: 0, scale: 1, opacity: 1 } | |
| : prefersReducedMotion | |
| ? { x: 0, y: 0, opacity: 0 } | |
| : { | |
| x: spec.from.x, | |
| y: spec.from.y, | |
| rotate: spec.from.rotate, | |
| scale: spec.from.scale, | |
| opacity: 0, | |
| }; | |
| return ( | |
| <motion.div | |
| key={`${spec.key}-${seed}`} | |
| className={cn("module-launch-piece min-w-0", spec.span, settled.has(i) && "module-launch-settled")} | |
| initial={initial} | |
| animate={play ? animate : initial} | |
| transition={ | |
| prefersReducedMotion | |
| ? { duration: 0 } | |
| : { | |
| delay: (isEnter ? spec.from.delay : i * 24) / 1000, | |
| duration: isEnter ? spec.from.duration : 0.85, | |
| ease: ASSEMBLY_EASE, | |
| } | |
| } | |
| onAnimationComplete={() => { | |
| if (play) markSettled(i); | |
| }} | |
| style={{ transformOrigin: "center center" }} | |
| > | |
| <Piece | |
| index={i} | |
| module={module} | |
| accent={accent} | |
| accentSoft={accentSoft} | |
| StatCardVariant={StatCardVariant} | |
| Frame={Frame} | |
| frameProps={frameProps} | |
| /> | |
| </motion.div> | |
| ); | |
| })} | |
| </div> | |
| {/* Engine strip — reassembles last, identifies the module being launched */} | |
| <motion.div | |
| className="absolute bottom-6 left-1/2 -translate-x-1/2 flex items-center gap-2 px-3 py-1.5 rounded-full border border-white/10 bg-black/50 backdrop-blur-md" | |
| initial={{ opacity: 0, y: 14 }} | |
| animate={play ? { opacity: 1, y: 0 } : { opacity: 0, y: 14 }} | |
| transition={prefersReducedMotion ? { duration: 0 } : { delay: 0.35, duration: 0.7, ease: ASSEMBLY_EASE }} | |
| > | |
| <span className="w-1.5 h-1.5 rounded-full animate-pulse" style={{ backgroundColor: accent, boxShadow: `0 0 10px ${accent}` }} /> | |
| <span className="text-[10px] font-mono uppercase tracking-[0.2em] text-zinc-400"> | |
| {isEnter ? "Assembling" : "Releasing"} {module === "s" ? "ModuleS" : "ModuleE"} workspace | |
| </span> | |
| <span className="text-[10px] font-mono" style={{ color: accentSoft }}> | |
| {Math.round(MIN_DURATION * 10) / 10}–{MAX_DURATION}s | |
| </span> | |
| </motion.div> | |
| </div> | |
| ); | |
| } | |
| /* ------------------------------------------------------------------ */ | |
| /* 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<string, unknown>; | |
| } | |
| function Piece({ index, module, accent, accentSoft, StatCardVariant, Frame, frameProps }: PieceProps) { | |
| switch (index) { | |
| case 0: | |
| return ( | |
| <StatCardVariant | |
| title="Total Leads" | |
| value="12,847" | |
| icon={Users} | |
| iconColor="indigo" | |
| change={{ value: "+12.4%", isPositive: true, period: "vs last week" }} | |
| /> | |
| ); | |
| case 1: | |
| return ( | |
| <StatCardVariant | |
| title="Active Campaigns" | |
| value="24" | |
| icon={Megaphone} | |
| iconColor="emerald" | |
| change={{ value: "3 launching", isPositive: null }} | |
| /> | |
| ); | |
| case 2: | |
| return ( | |
| <StatCardVariant | |
| title="Reply Rate" | |
| value="8.2%" | |
| icon={Mail} | |
| iconColor="purple" | |
| change={{ value: "+0.9%", isPositive: true, period: "7-day avg" }} | |
| /> | |
| ); | |
| case 3: | |
| return ( | |
| <StatCardVariant | |
| title="Deliverability" | |
| value="98.2%" | |
| icon={ShieldCheck} | |
| iconColor="teal" | |
| subtitle="Reputation shield active" | |
| progress={98} | |
| /> | |
| ); | |
| case 4: | |
| return ( | |
| <Frame {...frameProps}> | |
| <div className="flex items-center justify-between mb-2"> | |
| <div className="flex items-center gap-2 min-w-0"> | |
| <TrendingUp className="w-3.5 h-3.5 shrink-0" style={{ color: accent }} /> | |
| <span className="text-[11px] font-mono uppercase tracking-wider text-zinc-400 truncate">Sequence telemetry</span> | |
| </div> | |
| <span className="text-[10px] font-mono px-1.5 py-0.5 rounded border" style={{ color: accentSoft, borderColor: accentSoft + "55", backgroundColor: accentSoft + "14" }}> | |
| LIVE | |
| </span> | |
| </div> | |
| <div className="h-28 -ml-2"> | |
| <ResponsiveContainer width="100%" height="100%"> | |
| <AreaChart data={CHART_DATA} margin={{ top: 4, right: 4, bottom: 0, left: 4 }}> | |
| <defs> | |
| <linearGradient id={`launch-grad-${module}`} x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="0%" stopColor={accent} stopOpacity={0.45} /> | |
| <stop offset="100%" stopColor={accent} stopOpacity={0.02} /> | |
| </linearGradient> | |
| </defs> | |
| <XAxis dataKey="d" hide /> | |
| <YAxis hide /> | |
| <Tooltip cursor={{ stroke: accent + "55" }} contentStyle={{ background: "#0c0d10", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 8, fontSize: 10 }} /> | |
| <Area type="monotone" dataKey="b" stroke="transparent" fill={`url(#launch-grad-${module})`} isAnimationActive={false} /> | |
| <Area type="monotone" dataKey="v" stroke={accent} strokeWidth={1.5} fill="transparent" isAnimationActive={false} dot={false} /> | |
| </AreaChart> | |
| </ResponsiveContainer> | |
| </div> | |
| </Frame> | |
| ); | |
| case 5: | |
| return ( | |
| <Frame {...frameProps}> | |
| <div className="flex items-center gap-2 mb-2"> | |
| <Activity className="w-3.5 h-3.5 shrink-0" style={{ color: accent }} /> | |
| <span className="text-[11px] font-mono uppercase tracking-wider text-zinc-400 truncate">Live feed</span> | |
| </div> | |
| <div className="space-y-2"> | |
| {LIST_ROWS.map((row) => ( | |
| <div key={row.email} className="flex items-center justify-between gap-2"> | |
| <div className="flex items-center gap-2 min-w-0"> | |
| <span className="w-1.5 h-1.5 rounded-full shrink-0" style={{ backgroundColor: accent }} /> | |
| <span className="text-[11px] text-zinc-300 truncate">{row.email}</span> | |
| </div> | |
| <span className={cn("text-[8px] font-bold tracking-wider px-1.5 py-0.5 rounded border shrink-0", row.tone)}>{row.badge}</span> | |
| </div> | |
| ))} | |
| </div> | |
| </Frame> | |
| ); | |
| case 6: | |
| return ( | |
| <div className="flex items-center gap-2 flex-wrap"> | |
| {["SEQUENCES", "WARMUP", "UNIBOX"].map((tag) => ( | |
| <span key={tag} className="text-[9px] font-bold tracking-[0.18em] px-2 py-1 rounded-md border border-white/10 bg-white/5 text-zinc-300"> | |
| {tag} | |
| </span> | |
| ))} | |
| <span className="text-[9px] font-bold tracking-[0.18em] px-2 py-1 rounded-md border" style={{ color: accentSoft, borderColor: accentSoft + "55", backgroundColor: accentSoft + "14" }}> | |
| DELIVERABILITY | |
| </span> | |
| </div> | |
| ); | |
| case 7: | |
| return ( | |
| <StatCardVariant title="Open Rate" value="64.7%" icon={Mail} iconColor="blue" change={{ value: "+2.1%", isPositive: true }} /> | |
| ); | |
| case 8: | |
| return <StatCardVariant title="Bounce Rate" value="0.4%" icon={ArrowUpRight} iconColor="rose" change={{ value: "-0.2%", isPositive: true }} />; | |
| case 9: | |
| return ( | |
| <StatCardVariant title="Mailboxes Rotating" value="38" icon={Users} iconColor="amber" subtitle="Multi-mailbox rotation on" /> | |
| ); | |
| default: | |
| return ( | |
| <div className="space-y-2.5"> | |
| <Skeleton className="h-3 w-1/3" /> | |
| <Skeleton className="h-3 w-2/3" /> | |
| <Skeleton className="h-3 w-1/2" /> | |
| </div> | |
| ); | |
| } | |
| } | |