/* global React, ReactDOM */
// BlueSci demo — animated CLI walkthrough, first interaction → final report

const { useState, useEffect, useRef, useMemo } = React;

// ──────────────────────────────────────────────────────────────────
// Timeline — each step is one line in the terminal.
// Types: prompt, sys, ok, dim, sev, banner, sec, big, ask, done, code, blast
// `delay` is ms BEFORE the line appears (after previous).
// ──────────────────────────────────────────────────────────────────
const BANNER = String.raw`
   ▄▄▄▄    █     █  ▄▄▄▄  ▄▄▄▄  ▄▄▄▄  █
   █   █   █     █ █     █     █      █
   █▄▄▄▀   █     █ █▄▄▄  █▄▄▄  █      █
   █   █   █     █ █        █  █      █
   █▄▄▄▀   █▄▄▄  █  ▄▄▄▄  ▄▄▄▄  ▄▄▄▄  █▄▄▄▄
`;

const TIMELINE = [
  // ── INSTALL & FIRST RUN ─────────────────────────────────
  { t: "prompt", text: "npx @bluesci/cli init ./mlpipe", delay: 400 },
  { t: "dim",    text: "↳ resolving @bluesci/cli@0.8.3", delay: 700 },
  { t: "dim",    text: "↳ verified signature (sigstore)  ed25519:4f3a…c20e", delay: 500 },
  { t: "dim",    text: "↳ installed 14 packages in 2.1s", delay: 600 },
  { t: "banner", text: BANNER, delay: 500 },
  { t: "dim",    text: "BlueSci  ·  autonomous red-team for source code   v0.8.3", delay: 200 },
  { t: "blank",  delay: 400 },

  // ── REPO DETECTION ──────────────────────────────────────
  { t: "sec",    text: "WORKSPACE",      delay: 300 },
  { t: "ok",     text: "detected repo       github.com/acme-labs/mlpipe", delay: 350 },
  { t: "ok",     text: "language profile    python 3.11 · 84% · 41,208 LOC", delay: 300 },
  { t: "ok",     text: "entrypoints         12 detected (api/, jobs/, scripts/)", delay: 300 },
  { t: "ok",     text: "dep graph           312 packages · 41 direct", delay: 300 },
  { t: "ok",     text: "ci pipelines        github actions · 4 workflows", delay: 300 },
  { t: "blank",  delay: 200 },

  // ── PLAN ────────────────────────────────────────────────
  { t: "sec",    text: "ENGAGEMENT PLAN",   delay: 350, color: "violet" },
  { t: "plan",   text: "01  recon & surface mapping",        delay: 200, eta: "~2m" },
  { t: "plan",   text: "02  static + taint analysis",        delay: 200, eta: "~6m" },
  { t: "plan",   text: "03  supply-chain & secret hunt",     delay: 200, eta: "~3m" },
  { t: "plan",   text: "04  attack-chain reasoning",         delay: 200, eta: "~4m" },
  { t: "plan",   text: "05  PoC generation & sandbox",       delay: 200, eta: "~7m" },
  { t: "plan",   text: "06  report & remediation",           delay: 200, eta: "~1m" },
  { t: "blank",  delay: 200 },
  { t: "sec",    text: "SAFETY",            delay: 300 },
  { t: "dim",    text: "network sandboxed (gVisor)  ·  egress denied  ·  destructive ops denied", delay: 300 },
  { t: "blank",  delay: 300 },

  // ── CONFIRM ─────────────────────────────────────────────
  { t: "ask",    text: "proceed with autonomous red-team?  [y]es  [n]o  [e]dit", delay: 400 },
  { t: "prompt", text: "y", delay: 900, dim: true },
  { t: "blank",  delay: 200 },

  // ── PHASE 1 RECON ───────────────────────────────────────
  { t: "phase",  text: "01 · RECON",        delay: 400 },
  { t: "prog",   text: "ast build",         delay: 250, pct: 100, sub: "41,208 nodes" },
  { t: "prog",   text: "call graph",        delay: 350, pct: 100, sub: "84,103 edges" },
  { t: "prog",   text: "taint map",         delay: 400, pct: 100, sub: "18 sources · 47 sinks" },
  { t: "blank",  delay: 200 },

  // ── PHASE 2 DISCOVERY ───────────────────────────────────
  { t: "phase",  text: "02 · DISCOVERY",    delay: 350 },
  { t: "find",   ts: "01:08", sev: "CRIT", code: "BS-001", title: "Unsafe pickle deserialization", path: "api/models/loader.py:42", delay: 600 },
  { t: "find",   ts: "01:23", sev: "HIGH", code: "BS-014", title: "Hardcoded AWS credentials",     path: "jobs/etl/sync_s3.py:11",   delay: 500 },
  { t: "find",   ts: "01:40", sev: "MED",  code: "BS-031", title: "SQL string concatenation",      path: "api/db/query.py:88",       delay: 500 },
  { t: "find",   ts: "02:02", sev: "HIGH", code: "BS-022", title: "Server-side request forgery",   path: "api/webhooks/handler.py:54", delay: 500 },
  { t: "find",   ts: "02:18", sev: "CRIT", code: "BS-007", title: "yaml.load with full Loader",    path: "config/loader.py:19",      delay: 500 },
  { t: "find",   ts: "02:34", sev: "LOW",  code: "BS-082", title: "Weak hash (md5) in cache",      path: "utils/cache.py:7",         delay: 450 },
  { t: "find",   ts: "02:51", sev: "HIGH", code: "BS-018", title: "Path traversal on artifact",    path: "api/artifacts/serve.py:30", delay: 500 },
  { t: "find",   ts: "03:12", sev: "CRIT", code: "BS-003", title: "Code injection via eval()",     path: "ml/registry/register.py:64", delay: 500 },
  { t: "blank",  delay: 200 },

  // ── PHASE 3 SUPPLY CHAIN ────────────────────────────────
  { t: "phase",  text: "03 · SUPPLY CHAIN", delay: 400 },
  { t: "dim",    text: "resolved 312 transitive dependencies", delay: 350 },
  { t: "find",   ts: "04:01", sev: "HIGH", code: "BS-026", title: "Pinned-vulnerable: tensorflow 2.8.0 (CVE-2023-44982)", path: "requirements.txt:14", delay: 450 },
  { t: "find",   ts: "04:09", sev: "MED",  code: "BS-029", title: "Typosquat candidate: numpyy 1.0.0",                    path: "requirements.txt:23", delay: 400 },
  { t: "blank",  delay: 200 },

  // ── PHASE 4 ATTACK CHAIN ────────────────────────────────
  { t: "phase",  text: "04 · ATTACK-CHAIN REASONING", delay: 400 },
  { t: "dim",    text: "reasoning over 17 findings · evaluating 84 chain candidates", delay: 400 },
  { t: "dim",    text: "↳ pruning by feasibility · pruning by preconditions · scoring …", delay: 700 },
  { t: "blank",  delay: 200 },
  { t: "chain",  text: "CHAIN 01  untrusted YAML → SSRF → eval(model_def)", cvss: "9.4", color: "crit", delay: 700 },
  { t: "chain",  text: "CHAIN 02  hardcoded AWS key → S3 model swap → poisoned inference", cvss: "7.8", color: "high", delay: 500 },
  { t: "chain",  text: "CHAIN 03  path traversal → /etc/secrets exfil via artifact serve", cvss: "6.4", color: "high", delay: 500 },
  { t: "blank",  delay: 200 },

  // ── PHASE 5 POC SANDBOX ─────────────────────────────────
  { t: "phase",  text: "05 · POC · SANDBOX EXECUTION", delay: 400 },
  { t: "dim",    text: "generating PoC for chain 01 → poc_eval_via_yaml.py", delay: 500 },
  { t: "code",   delay: 600, lines: [
    "import requests, yaml",
    "TARGET = \"http://mlpipe.internal:8080\"",
    "payload = yaml.dump({",
    "    \"model_def\": \"__import__('os').system('id > /tmp/owned')\",",
    "    \"name\": \"benign-classifier-v2\",",
    "})",
    "r = requests.post(f\"{TARGET}/api/webhooks/handler\",",
    "    json={\"callback\": \"http://localhost:9001/registry/register\",",
    "          \"body\": payload})",
  ]},
  { t: "blank",  delay: 200 },
  { t: "dim",    text: "spinning ephemeral sandbox · gVisor · no egress · fs read-only", delay: 500 },
  { t: "dim",    text: "booted in 1.84s · target snapshot @ 8a3f04c", delay: 500 },
  { t: "ok",     text: "[+] payload accepted — eval scheduled", delay: 500 },
  { t: "ok",     text: "[+] worker picked up job j-9f3a-001", delay: 400 },
  { t: "ok",     text: "[+] reading /tmp/owned …", delay: 500 },
  { t: "blank",  delay: 200 },
  { t: "rce",    delay: 700 },
  { t: "blank",  delay: 300 },
  { t: "sec",    text: "BLAST RADIUS",      delay: 350 },
  { t: "blast",  k: "code execution",  v: "root in training worker", delay: 200 },
  { t: "blast",  k: "lateral",         v: "kube SA token reachable", delay: 200 },
  { t: "blast",  k: "data",            v: "2.3 TB training set readable", delay: 200 },
  { t: "blast",  k: "persistence",     v: "model weights writable", delay: 200 },
  { t: "blank",  delay: 300 },

  // ── PHASE 6 REPORT ──────────────────────────────────────
  { t: "phase",  text: "06 · REPORT",       delay: 400 },
  { t: "dim",    text: "rendering report.md  ·  signing evidence package  ·  attaching replay .pcap", delay: 600 },
  { t: "ok",     text: "→ report.md       (17 findings · 3 chains)", delay: 400 },
  { t: "ok",     text: "→ report.html     (white-labeled · Mandrake Security)", delay: 400 },
  { t: "ok",     text: "→ evidence.tgz    (signed · 84 MB)", delay: 400 },
  { t: "blank",  delay: 300 },

  // ── SUMMARY ─────────────────────────────────────────────
  { t: "big",    delay: 800 },
  { t: "blank",  delay: 200 },
  { t: "dim",    text: "engagement complete in 23m 41s · agent idle", delay: 400 },
  { t: "prompt", text: "_", delay: 600, cursor: true },
];

const COLOR = {
  crit: "var(--bs-crit-500)", high: "var(--bs-high-500)", med: "var(--bs-med-500)", low: "var(--bs-low-500)",
  violet: "var(--bs-violet-400)", cyan: "var(--bs-cyan-500)", ok: "var(--bs-ok-500)",
};

// ──────────────────────────────────────────────────────────────────
// Renderers per line type
// ──────────────────────────────────────────────────────────────────
function LineRow({ step }) {
  const base = { fontFamily: "var(--bs-font-mono)", fontSize: 13.5, lineHeight: 1.6, color: "var(--bs-fog-100)" };

  if (step.t === "blank") return <div style={{ height: 8 }} />;

  if (step.t === "prompt") {
    return (
      <div style={base}>
        <span style={{ color: "var(--bs-cyan-500)" }}>›</span>{" "}
        <span style={{ color: "var(--bs-fog-300)" }}>bluesci</span>{" "}
        <span style={{ color: step.dim ? "var(--bs-fog-300)" : "var(--bs-fog-50)" }}>
          {step.text}
          {step.cursor && <span className="bs-cursor" />}
        </span>
      </div>
    );
  }

  if (step.t === "dim") return <div style={{ ...base, color: "var(--bs-fog-400)" }}>{step.text}</div>;

  if (step.t === "ok") return (
    <div style={base}>
      <span style={{ color: "var(--bs-ok-500)" }}>✓</span>{" "}
      <span style={{ color: "var(--bs-fog-100)" }}>{step.text}</span>
    </div>
  );

  if (step.t === "banner") return (
    <pre style={{ margin: 0, color: "var(--bs-cyan-500)", fontSize: 10, lineHeight: 1.15,
      fontFamily: "var(--bs-font-mono)", textShadow: "0 0 12px rgba(20,217,201,0.5)" }}>{step.text}</pre>
  );

  if (step.t === "sec") return (
    <div style={{ marginTop: 6, marginBottom: 2, fontFamily: "var(--bs-font-mono)", fontSize: 10.5,
      letterSpacing: "0.22em", textTransform: "uppercase", fontWeight: 600,
      color: step.color === "violet" ? COLOR.violet : "var(--bs-cyan-500)" }}>
      ── {step.text} ──
    </div>
  );

  if (step.t === "phase") return (
    <div style={{
      marginTop: 14, marginBottom: 6,
      display: "inline-block",
      padding: "5px 14px",
      background: "rgba(20,217,201,0.10)",
      border: "1px solid rgba(20,217,201,0.4)",
      borderLeft: "3px solid var(--bs-cyan-500)",
      borderRadius: "var(--bs-r-1)",
      fontFamily: "var(--bs-font-mono)", fontSize: 11.5, fontWeight: 600,
      color: "var(--bs-cyan-300)", letterSpacing: "0.16em",
      boxShadow: "0 0 18px rgba(20,217,201,0.18)",
    }}>{step.text}</div>
  );

  if (step.t === "plan") return (
    <div style={{ ...base, color: "var(--bs-fog-100)", display: "flex", gap: 12, paddingLeft: 12 }}>
      <span style={{ color: COLOR.violet, width: 24 }}>{step.text.slice(0, 2)}</span>
      <span>{step.text.slice(2)}</span>
      <span style={{ marginLeft: "auto", color: "var(--bs-fog-400)" }}>{step.eta}</span>
    </div>
  );

  if (step.t === "ask") return (
    <div style={{
      marginTop: 6, padding: "10px 14px",
      border: "1px solid var(--bs-cyan-500)",
      borderRadius: "var(--bs-r-3)",
      background: "rgba(20,217,201,0.06)",
      boxShadow: "0 0 24px rgba(20,217,201,0.18)",
      fontFamily: "var(--bs-font-mono)", fontSize: 13,
      color: "var(--bs-fog-50)",
    }}>
      <span style={{ color: "var(--bs-cyan-500)" }}>›</span> {step.text}
    </div>
  );

  if (step.t === "prog") {
    return (
      <div style={{ ...base, display: "grid", gridTemplateColumns: "140px 1fr 60px 160px", gap: 12, alignItems: "center" }}>
        <span style={{ color: "var(--bs-fog-300)" }}>{step.text}</span>
        <span style={{ display: "block", height: 4, background: "var(--bs-ink-800)", borderRadius: 2, overflow: "hidden" }}>
          <span style={{ display: "block", height: "100%", width: `${step.pct}%`, background: "var(--bs-cyan-500)", boxShadow: "0 0 8px var(--bs-cyan-500)" }} />
        </span>
        <span style={{ color: "var(--bs-cyan-400)", textAlign: "right" }}>{step.pct}%</span>
        <span style={{ color: "var(--bs-fog-400)" }}>{step.sub}</span>
      </div>
    );
  }

  if (step.t === "find") {
    const sevColor = COLOR[step.sev.toLowerCase()] || "var(--bs-fog-100)";
    const sevBg = `rgba(${step.sev === "CRIT" ? "255,61,92" : step.sev === "HIGH" ? "255,138,61" : step.sev === "MED" ? "242,201,76" : "94,190,250"}, 0.10)`;
    return (
      <div style={{ ...base, display: "grid", gridTemplateColumns: "84px 64px 1fr auto", gap: 14, alignItems: "center", padding: "3px 0" }}>
        <span style={{ color: "var(--bs-fog-500)" }}>[{step.ts}]</span>
        <span style={{
          padding: "2px 7px", borderRadius: "var(--bs-r-1)",
          fontSize: 10, fontWeight: 600, letterSpacing: "0.14em",
          color: sevColor, background: sevBg,
          border: `1px solid ${sevColor}55`,
          display: "inline-flex", alignItems: "center", gap: 6, justifyContent: "center",
        }}>
          <span style={{ width: 6, height: 6, borderRadius: "50%", background: sevColor, boxShadow: `0 0 6px ${sevColor}` }} />
          {step.sev}
        </span>
        <span>
          <span style={{ color: "var(--bs-fog-400)" }}>{step.code}</span>{" "}
          <span style={{ color: "var(--bs-fog-50)" }}>{step.title}</span>
        </span>
        <span style={{ color: "var(--bs-cyan-400)", fontSize: 12 }}>{step.path}</span>
      </div>
    );
  }

  if (step.t === "chain") {
    const color = COLOR[step.color];
    return (
      <div style={{ ...base, display: "flex", gap: 14, padding: "6px 12px",
        background: "rgba(139,92,246,0.05)", border: "1px solid rgba(139,92,246,0.25)",
        borderLeft: `3px solid ${color}`, borderRadius: "var(--bs-r-2)", alignItems: "center" }}>
        <span style={{ color: COLOR.violet, fontWeight: 600 }}>{step.text.slice(0, 8)}</span>
        <span style={{ color: "var(--bs-fog-50)" }}>{step.text.slice(8)}</span>
        <span style={{ marginLeft: "auto", color: "var(--bs-fog-300)" }}>
          CVSS <span style={{ color, fontWeight: 600 }}>{step.cvss}</span>
        </span>
      </div>
    );
  }

  if (step.t === "code") return (
    <div style={{ background: "var(--bs-ink-950)", border: "1px solid var(--bs-border-1)",
      borderRadius: "var(--bs-r-2)", padding: "10px 14px", fontFamily: "var(--bs-font-mono)", fontSize: 12, lineHeight: 1.7 }}>
      {step.lines.map((ln, i) => (
        <div key={i} style={{ display: "grid", gridTemplateColumns: "32px 1fr" }}>
          <span style={{ color: "var(--bs-fog-500)", textAlign: "right", paddingRight: 10 }}>{i + 1}</span>
          <span style={{ color: ln.startsWith("import") ? COLOR.violet : ln.includes("\"") ? "var(--bs-fog-100)" : "var(--bs-fog-100)" }}>{ln}</span>
        </div>
      ))}
    </div>
  );

  if (step.t === "rce") return (
    <div style={{
      padding: "12px 16px",
      background: "rgba(255,61,92,0.10)",
      border: "1px solid rgba(255,61,92,0.5)",
      borderLeft: "4px solid var(--bs-crit-500)",
      borderRadius: "var(--bs-r-2)",
      boxShadow: "0 0 28px rgba(255,61,92,0.25)",
      fontFamily: "var(--bs-font-mono)", fontSize: 13, lineHeight: 1.6,
    }}>
      <div style={{ color: "var(--bs-crit-500)", fontWeight: 700, letterSpacing: "0.16em" }}>RCE CONFIRMED</div>
      <div style={{ color: "var(--bs-fog-100)", marginTop: 6 }}>
        uid=0(root) gid=0(root) groups=0(root)<br />
        hostname: mlpipe-worker-0
      </div>
    </div>
  );

  if (step.t === "blast") return (
    <div style={{ ...base, display: "grid", gridTemplateColumns: "180px 1fr", paddingLeft: 12 }}>
      <span style={{ color: "var(--bs-fog-300)" }}>{step.k}</span>
      <span style={{ color: "var(--bs-crit-500)" }}>{step.v}</span>
    </div>
  );

  if (step.t === "big") return (
    <div style={{
      marginTop: 8, padding: "20px 24px",
      background: "var(--bs-ink-950)",
      border: "1px solid var(--bs-border-2)",
      borderRadius: "var(--bs-r-3)",
    }}>
      <div style={{ fontFamily: "var(--bs-font-mono)", fontSize: 10.5, letterSpacing: "0.22em", color: "var(--bs-fog-400)" }}>
        ENGAGEMENT COMPLETE · acme-labs / mlpipe
      </div>
      <div style={{ fontFamily: "var(--bs-font-display)", fontSize: 30, color: "var(--bs-fog-50)", letterSpacing: "-0.02em", marginTop: 8 }}>
        17 findings · 3 chains · 1 sandbox-verified RCE
      </div>
      <div style={{ marginTop: 18, display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 20 }}>
        <BigKV k="MAX CVSS" v="9.4" color={COLOR.crit} />
        <BigKV k="DURATION" v="23m 41s" />
        <BigKV k="COVERAGE" v="98.6%" />
        <BigKV k="FIX EFFORT" v="3.2h" sub="mean" />
      </div>
    </div>
  );

  return null;
}

function BigKV({ k, v, color, sub }) {
  return (
    <div>
      <div style={{ fontFamily: "var(--bs-font-mono)", fontSize: 10, letterSpacing: "0.18em", color: "var(--bs-fog-400)" }}>{k}</div>
      <div style={{ fontFamily: "var(--bs-font-mono)", fontSize: 26, color: color || "var(--bs-fog-50)", marginTop: 4, fontWeight: 500 }}>{v}</div>
      {sub && <div style={{ fontFamily: "var(--bs-font-mono)", fontSize: 11, color: "var(--bs-fog-400)" }}>{sub}</div>}
    </div>
  );
}

// ──────────────────────────────────────────────────────────────────
// Demo controller
// ──────────────────────────────────────────────────────────────────
function Demo() {
  const [index, setIndex] = useState(0);
  const [playing, setPlaying] = useState(true);
  const [speed, setSpeed] = useState(1);
  const scrollerRef = useRef(null);
  const timerRef = useRef(null);

  // Advance timer
  useEffect(() => {
    if (!playing) return;
    if (index >= TIMELINE.length) return;
    const step = TIMELINE[index];
    timerRef.current = setTimeout(() => {
      setIndex(i => i + 1);
    }, Math.max(80, (step.delay || 200) / speed));
    return () => clearTimeout(timerRef.current);
  }, [playing, index, speed]);

  // Autoscroll
  useEffect(() => {
    const el = scrollerRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [index]);

  const restart = () => { setIndex(0); setPlaying(true); };
  const visible = TIMELINE.slice(0, index);
  const pct = Math.min(100, (index / TIMELINE.length) * 100);
  const elapsed = useMemo(() => {
    const ms = TIMELINE.slice(0, index).reduce((a, s) => a + (s.delay || 0), 0);
    const total = Math.floor(ms / 1000);
    const m = String(Math.floor(total / 60)).padStart(2, "0");
    const s = String(total % 60).padStart(2, "0");
    return `${m}:${s}`;
  }, [index]);

  return (
    <div className="bs-root" style={{
      width: "100vw", height: "100vh", background: "var(--bs-ink-1000)",
      display: "flex", flexDirection: "column", overflow: "hidden",
    }}>
      {/* Header */}
      <header style={{
        height: 56, padding: "0 24px",
        borderBottom: "1px solid var(--bs-border-1)",
        display: "flex", alignItems: "center", gap: 16,
        background: "rgba(8,12,24,0.7)", backdropFilter: "blur(14px)",
      }}>
        <a href="index.html" style={{ display: "flex", alignItems: "center", gap: 9, textDecoration: "none" }}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
            <path d="M3 6 L9 12 L3 18" stroke="var(--bs-cyan-500)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
            <circle cx="17" cy="12" r="3" fill="var(--bs-cyan-500)" />
            <circle cx="17" cy="12" r="6" stroke="var(--bs-cyan-500)" strokeOpacity="0.35" strokeWidth="1" />
          </svg>
          <span style={{ fontFamily: "var(--bs-font-display)", fontSize: 17, color: "var(--bs-fog-50)", letterSpacing: "-0.01em" }}>BlueSci</span>
        </a>
        <span style={{ color: "var(--bs-fog-500)" }}>/</span>
        <span style={{ fontSize: 13, color: "var(--bs-fog-200)" }}>Live demo · CLI engagement</span>

        <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 16, fontFamily: "var(--bs-font-mono)", fontSize: 11.5, color: "var(--bs-fog-300)" }}>
          <span><span style={{ color: playing ? "var(--bs-cyan-500)" : "var(--bs-fog-500)" }}>●</span> {playing ? "running" : "paused"}</span>
          <span>step {index} / {TIMELINE.length}</span>
          <span>elapsed {elapsed}</span>
        </div>
      </header>

      {/* Progress strip */}
      <div style={{ height: 2, background: "var(--bs-ink-800)" }}>
        <div style={{ height: "100%", width: `${pct}%`, background: "var(--bs-cyan-500)", boxShadow: "0 0 10px var(--bs-cyan-500)", transition: "width 200ms ease-out" }} />
      </div>

      {/* Terminal body */}
      <div style={{ flex: 1, padding: "24px 40px 16px", display: "flex", justifyContent: "center", overflow: "hidden" }}>
        <div style={{
          width: "100%", maxWidth: 1160, height: "100%",
          background: "var(--bs-ink-950)",
          border: "1px solid var(--bs-border-2)",
          borderRadius: "var(--bs-r-4)",
          boxShadow: "var(--bs-shadow-3)",
          display: "flex", flexDirection: "column",
          overflow: "hidden",
        }}>
          {/* Window chrome */}
          <div style={{
            height: 34, flex: "0 0 34px",
            background: "linear-gradient(180deg, #0E1528 0%, #080C18 100%)",
            borderBottom: "1px solid var(--bs-border-1)",
            display: "flex", alignItems: "center", padding: "0 14px", gap: 8,
          }}>
            <span style={{ width: 10, height: 10, borderRadius: "50%", background: "#2A3B66" }} />
            <span style={{ width: 10, height: 10, borderRadius: "50%", background: "#2A3B66" }} />
            <span style={{ width: 10, height: 10, borderRadius: "50%", background: "#2A3B66" }} />
            <span style={{ flex: 1, textAlign: "center", fontFamily: "var(--bs-font-mono)", fontSize: 11, color: "var(--bs-fog-300)" }}>
              ~/projects/mlpipe — bluesci
            </span>
          </div>
          {/* Scroller */}
          <div ref={scrollerRef} style={{ flex: 1, overflowY: "auto", padding: "18px 24px", position: "relative" }}>
            {/* faint scanlines */}
            <div style={{
              position: "absolute", inset: 0, pointerEvents: "none",
              backgroundImage: "linear-gradient(180deg, transparent 50%, rgba(255,255,255,0.012) 50%)",
              backgroundSize: "100% 3px",
            }} />
            <div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
              {visible.map((step, i) => <LineRow key={i} step={step} />)}
            </div>
          </div>
        </div>
      </div>

      {/* Controls */}
      <div style={{
        height: 56, padding: "0 24px",
        borderTop: "1px solid var(--bs-border-1)",
        display: "flex", alignItems: "center", gap: 12,
        background: "var(--bs-ink-950)",
      }}>
        <button onClick={() => setPlaying(p => !p)} style={ctrlBtnStyle(true)}>
          {playing ? "❚❚ Pause" : "▶ Play"}
        </button>
        <button onClick={restart} style={ctrlBtnStyle()}>↺ Restart</button>
        <button onClick={() => { setIndex(TIMELINE.length); setPlaying(false); }} style={ctrlBtnStyle()}>
          ⏭ Skip to end
        </button>

        <div style={{ marginLeft: 18, display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--bs-font-mono)", fontSize: 11.5, color: "var(--bs-fog-300)" }}>
          <span>speed</span>
          {[0.5, 1, 2, 4].map(s => (
            <button key={s} onClick={() => setSpeed(s)} style={{
              padding: "4px 10px", borderRadius: "var(--bs-r-2)",
              fontFamily: "var(--bs-font-mono)", fontSize: 11,
              border: `1px solid ${speed === s ? "var(--bs-cyan-500)" : "var(--bs-border-2)"}`,
              background: speed === s ? "rgba(20,217,201,0.10)" : "transparent",
              color: speed === s ? "var(--bs-cyan-300)" : "var(--bs-fog-200)",
              cursor: "pointer",
            }}>{s}×</button>
          ))}
        </div>

        {/* Scrubber */}
        <div style={{ flex: 1, marginLeft: 24, display: "flex", alignItems: "center", gap: 10 }}>
          <input
            type="range"
            min="0" max={TIMELINE.length} value={index}
            onChange={(e) => { setIndex(parseInt(e.target.value, 10)); setPlaying(false); }}
            style={{ flex: 1, accentColor: "var(--bs-cyan-500)" }}
          />
          <span style={{ fontFamily: "var(--bs-font-mono)", fontSize: 11, color: "var(--bs-fog-400)", minWidth: 80, textAlign: "right" }}>
            {Math.round(pct)}%
          </span>
        </div>
      </div>

      <style>{`
        .bs-cursor { display:inline-block; width:8px; height:14px; background:var(--bs-cyan-500); margin-left:2px; vertical-align:-2px; animation: bsblk 1s steps(2) infinite; }
        @keyframes bsblk { 0%,49%{opacity:1} 50%,100%{opacity:0} }
      `}</style>
    </div>
  );
}

function ctrlBtnStyle(primary) {
  return {
    padding: "7px 14px",
    borderRadius: "var(--bs-r-2)",
    fontFamily: "var(--bs-font-sans)",
    fontSize: 12.5,
    fontWeight: 500,
    border: primary ? "1px solid var(--bs-cyan-500)" : "1px solid var(--bs-border-2)",
    background: primary ? "var(--bs-cyan-500)" : "transparent",
    color: primary ? "var(--bs-ink-1000)" : "var(--bs-fog-100)",
    cursor: "pointer",
    boxShadow: primary ? "0 0 16px rgba(20,217,201,0.3)" : "none",
  };
}

ReactDOM.createRoot(document.getElementById("root")).render(<Demo />);
