// Simulation driver — auto-progresses a phase through a scripted timeline // usage: const sim = useSim(steps, deps) — steps: [{at: ms, do: (set) => {...}}, ...] function useSim(steps, playing, resetKey) { const timers = React.useRef([]); React.useEffect(() => { timers.current.forEach(clearTimeout); timers.current = []; if (!playing) return; steps.forEach(s => { const t = setTimeout(() => s.do(), s.at); timers.current.push(t); }); return () => { timers.current.forEach(clearTimeout); timers.current = []; }; }, [playing, resetKey]); } // Typewriter — animates a string over duration ms function useTypewriter(target, startAt, playing, resetKey) { const [text, setText] = React.useState(""); React.useEffect(() => { setText(""); if (!playing) { setText(target); return; } if (!target) return; const duration = Math.min(Math.max(target.length * 18, 400), 2600); const chars = target.length; const timers = []; const begin = setTimeout(() => { for (let i = 1; i <= chars; i++) { const t = setTimeout(() => setText(target.slice(0, i)), (i / chars) * duration); timers.push(t); } }, startAt); timers.push(begin); return () => timers.forEach(clearTimeout); }, [target, playing, resetKey]); return text; } // Simulation controls — big play/reset bar at top of a simulated phase function SimBar({ playing, onPlay, onPause, onReset, done, label }) { return (
sim ·
{label}
{!done ? ( playing ? ( ) : ( ) ) : ( sim complete )}
); } Object.assign(window, { useSim, useTypewriter, SimBar });