// components.jsx — shared UI for Heartbeat Kit pages

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

// ─────────────────────────────────────────────────────────────────────────────
// Wordmark / logo
function Logo({ size = 22 }) {
  return (
    <a href="index.html" style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
      <svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
        <circle cx="12" cy="12" r="10" stroke="#5ec4a8" strokeWidth="1.4" opacity="0.4" />
        <path d="M2 12 H7 L9 6 L12 18 L15 9 L17 12 H22"
              stroke="#5ec4a8" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" fill="none" />
      </svg>
      <span style={{
        fontFamily: 'var(--f-display)',
        fontWeight: 600, fontSize: 16, letterSpacing: '-0.01em', color: 'inherit'
      }}>
        Heartbeat Kit
      </span>
    </a>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Top nav
function Nav({ current = 'home' }) {
  const link = (id, href, label) => (
    <a href={href} style={{
      color: current === id ? 'var(--sand)' : 'var(--slate)',
      fontSize: 14, fontWeight: 500, padding: '8px 4px',
      borderBottom: current === id ? '1px solid var(--cyan)' : '1px solid transparent',
    }}>{label}</a>
  );
  return (
    <header style={{
      position: 'sticky', top: 0, zIndex: 50,
      background: 'rgba(10,11,16,0.78)',
      backdropFilter: 'blur(14px) saturate(140%)',
      WebkitBackdropFilter: 'blur(14px) saturate(140%)',
      borderBottom: '1px solid rgba(255,255,255,0.06)',
    }}>
      <div className="wrap" style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        height: 64,
      }}>
        <Logo />
        <nav style={{ display: 'flex', gap: 28, alignItems: 'center' }}>
          {link('docs', 'docs.html', 'Docs')}
          {link('pricing', 'pricing.html', 'Pricing')}
          <a href="#" style={{ color: 'var(--slate)', fontSize: 14, fontWeight: 500 }}>Changelog</a>
          <a href="#" style={{ color: 'var(--slate)', fontSize: 14, fontWeight: 500 }}>GitHub</a>
        </nav>
        <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
          <a className="btn btn-ghost" href="#">Sign in</a>
          <a className="btn btn-primary" href="#">Start free →</a>
        </div>
      </div>
    </header>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Footer
function Footer() {
  const col = (title, links) => (
    <div className="col gap-12">
      <div className="eyebrow">{title}</div>
      {links.map(l => (
        <a key={l} href="#" style={{ color: 'var(--slate)', fontSize: 14 }}>{l}</a>
      ))}
    </div>
  );
  return (
    <footer style={{ background: 'var(--black)', borderTop: '1px solid rgba(255,255,255,0.06)', padding: '64px 0 40px' }}>
      <div className="wrap">
        <div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr 1fr 1fr 1fr', gap: 48 }}>
          <div className="col gap-16">
            <Logo />
            <p className="muted" style={{ fontSize: 14, maxWidth: 280, margin: 0 }}>
              The accountability layer for autonomous systems. Built on Cloudflare's edge.
            </p>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--slate)' }}>
              <span className="dot active" />
              <span className="mono">api.heartbeatkit.com — operational</span>
            </div>
          </div>
          {col('Product', ['Heartbeats', 'Channels', 'Policies', 'AI Responder', 'MCP Server'])}
          {col('Solutions', ['AI agents', 'DevOps & SRE', 'IoT fleets', 'Safety & care', 'Compliance'])}
          {col('Developers', ['Docs', 'API reference', 'SDK', 'Status', 'Changelog'])}
          {col('Company', ['About', 'Customers', 'Careers', 'Security', 'Contact'])}
        </div>
        <div style={{
          marginTop: 56, paddingTop: 24,
          borderTop: '1px solid rgba(255,255,255,0.06)',
          display: 'flex', justifyContent: 'space-between', fontSize: 13, color: 'var(--slate)',
        }}>
          <span>© 2026 Heartbeat Kit, Inc.</span>
          <span className="mono">If it goes silent, we respond.</span>
        </div>
      </div>
    </footer>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Animated ECG pulse line — real oscilloscope-style decay trail.
function PulseLine({ height = 200, color = '#5ec4a8', state = 'active', strokeWidth = 2 }) {
  const canvasRef = useRef(null);
  const animRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const dpr = window.devicePixelRatio || 1;

    function resize() {
      const rect = canvas.parentElement.getBoundingClientRect();
      canvas.width = rect.width * dpr;
      canvas.height = height * dpr;
      canvas.style.width = rect.width + 'px';
      canvas.style.height = height + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    resize();
    window.addEventListener('resize', resize);

    const W = () => canvas.width / dpr;
    const mid = height / 2;
    const speed = state === 'firing' ? 2.4 : 1.5;
    let headX = 0;

    // Realistic EKG waveform: TP baseline → P wave → PR segment → QRS complex → ST segment → T wave → TP baseline
    function getY(x) {
      const period = 240;
      const p = ((x % period) + period) % period;
      const amp = height * 0.32;

      // TP segment (baseline) 0-60
      if (p < 60) return mid;

      // P wave (small rounded bump) 60-90
      if (p < 90) {
        const t = (p - 60) / 30;
        return mid - Math.sin(t * Math.PI) * (amp * 0.12);
      }

      // PR segment (flat) 90-108
      if (p < 108) return mid;

      // Q wave (small dip) 108-114
      if (p < 114) {
        const t = (p - 108) / 6;
        return mid + Math.sin(t * Math.PI) * (amp * 0.08);
      }

      // R wave (tall sharp spike up) 114-124
      if (p < 119) {
        const t = (p - 114) / 5;
        return mid - t * amp * 0.92;
      }

      // R peak to S (fast drop through baseline) 119-127
      if (p < 127) {
        const t = (p - 119) / 8;
        return mid - amp * 0.92 + t * (amp * 0.92 + amp * 0.22);
      }

      // S wave recovery 127-134
      if (p < 134) {
        const t = (p - 127) / 7;
        return mid + amp * 0.22 * (1 - t);
      }

      // ST segment (slightly elevated) 134-160
      if (p < 160) return mid - amp * 0.02;

      // T wave (broad rounded bump) 160-200
      if (p < 200) {
        const t = (p - 160) / 40;
        return mid - Math.sin(t * Math.PI) * (amp * 0.18);
      }

      // TP segment (back to baseline) 200-240
      return mid;
    }

    function draw() {
      const w = W();
      ctx.clearRect(0, 0, w, height);

      // Head is drawn at 60% of width (not far right)
      const headScreenX = w * 0.6;
      const tailLen = w * 0.5;

      // Draw trace with fading tail behind the head
      for (let screenX = 0; screenX < w; screenX++) {
        const distFromHead = headScreenX - screenX;

        // Only draw behind the head (and a tiny bit ahead for the glow)
        if (distFromHead < -2) continue;
        if (distFromHead > tailLen) continue;

        const dataX = headX - distFromHead;
        const y = getY(dataX);
        const prevY = getY(dataX - 1);

        // Fade: 1.0 at head, 0.0 at tail end
        let alpha = 1 - (distFromHead / tailLen);
        if (alpha < 0) alpha = 0;
        alpha = alpha * alpha * 0.8; // ease-in curve

        if (alpha > 0.005) {
          ctx.beginPath();
          ctx.moveTo(screenX - 1, prevY);
          ctx.lineTo(screenX, y);
          ctx.strokeStyle = color;
          ctx.globalAlpha = alpha;
          ctx.lineWidth = strokeWidth;
          ctx.lineCap = 'round';
          ctx.stroke();
        }
      }

      // Bright head dot
      const headY = getY(headX);
      ctx.globalAlpha = 1;
      ctx.beginPath();
      ctx.arc(headScreenX, headY, 3.5, 0, Math.PI * 2);
      ctx.fillStyle = color;
      ctx.shadowColor = color;
      ctx.shadowBlur = 14;
      ctx.fill();
      ctx.shadowBlur = 0;

      headX += speed;
      animRef.current = requestAnimationFrame(draw);
    }

    animRef.current = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(animRef.current);
      window.removeEventListener('resize', resize);
    };
  }, [color, height, state, strokeWidth]);

  return (
    <div style={{ position: 'relative', height }}>
      <canvas ref={canvasRef} style={{ display: 'block', width: '100%', height }} />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Terminal / streaming logs
function Terminal({ lines, height = 360, title = 'agent.log' }) {
  const [visible, setVisible] = useState(0);
  const ref = useRef(null);

  useEffect(() => {
    if (visible >= lines.length) {
      // restart after a beat
      const t = setTimeout(() => setVisible(0), 4500);
      return () => clearTimeout(t);
    }
    const t = setTimeout(() => setVisible(v => v + 1), lines[visible]?.delay ?? 380);
    return () => clearTimeout(t);
  }, [visible, lines]);

  useEffect(() => {
    if (ref.current) ref.current.scrollTop = ref.current.scrollHeight;
  }, [visible]);

  return (
    <div style={{
      background: '#06070b',
      border: '1px solid var(--border)',
      borderRadius: 12,
      overflow: 'hidden',
      fontFamily: 'var(--f-mono)',
      fontSize: 13.5,
      boxShadow: 'var(--shadow-soft)',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8,
        padding: '10px 14px',
        borderBottom: '1px solid var(--border)',
        background: 'linear-gradient(180deg, #0d0e15, #0a0b10)',
      }}>
        <span style={{ width: 10, height: 10, borderRadius: 5, background: '#3a3b48' }} />
        <span style={{ width: 10, height: 10, borderRadius: 5, background: '#3a3b48' }} />
        <span style={{ width: 10, height: 10, borderRadius: 5, background: '#3a3b48' }} />
        <span className="mono" style={{ color: 'var(--slate)', fontSize: 12, marginLeft: 12 }}>
          {title}
        </span>
        <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6,
                       color: 'var(--slate)', fontSize: 12 }}>
          <span className="dot active" /> live
        </span>
      </div>
      <div ref={ref} style={{
        height, overflow: 'auto', padding: '14px 16px',
        color: '#cdd0d8', lineHeight: 1.9,
        fontSize: 12.5,
      }}>
        {lines.slice(0, visible).map((ln, i) => (
          <div key={i} style={{ display: 'flex', gap: 10, opacity: 0, animation: 'fadein 220ms forwards', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
            <span style={{ color: '#5b6076', userSelect: 'none', minWidth: 42, flexShrink: 0 }}>{ln.t}</span>
            <span style={{ color: ln.color || '#cdd0d8', overflow: 'hidden', textOverflow: 'ellipsis' }}>
              {ln.tag && <span style={{
                fontSize: 10, padding: '2px 5px', borderRadius: 3,
                background: ln.tagBg || 'rgba(94,196,168,0.12)',
                color: ln.tagColor || 'var(--cyan)', marginRight: 7,
                fontWeight: 600, letterSpacing: '0.03em', textTransform: 'uppercase',
              }}>{ln.tag}</span>}
              {ln.text}
            </span>
          </div>
        ))}
        {visible < lines.length && (
          <div style={{ display: 'inline-block', width: 7, height: 14,
                        background: 'var(--cyan)', verticalAlign: 'text-bottom',
                        animation: 'blink 1s steps(2) infinite' }} />
        )}
      </div>
      <style>{`
        @keyframes fadein { to { opacity: 1; } }
        @keyframes blink { 50% { opacity: 0; } }
      `}</style>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Code block with syntax-class spans
function Code({ children, lang = 'ts', label }) {
  return (
    <div style={{
      borderRadius: 12, overflow: 'hidden',
      border: '1px solid var(--border)',
      background: '#06070b',
      boxShadow: 'var(--shadow-soft)',
    }}>
      {label && (
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          padding: '10px 16px',
          borderBottom: '1px solid var(--border)',
          background: 'linear-gradient(180deg, #0d0e15, #0a0b10)',
          fontFamily: 'var(--f-mono)', fontSize: 12, color: 'var(--slate)',
        }}>
          <span>{label}</span>
          <span style={{ fontSize: 11, padding: '2px 8px', borderRadius: 4,
                         background: 'rgba(255,255,255,0.05)', color: 'var(--slate)' }}>{lang}</span>
        </div>
      )}
      <pre className="code" style={{ margin: 0, border: 0, borderRadius: 0 }}>
        {children}
      </pre>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Live dashboard mock — animated table of heartbeats with status changes
function DashboardMock() {
  const [rows, setRows] = useState(() => initialRows());
  const [tick, setTick] = useState(0);

  useEffect(() => {
    const i = setInterval(() => setTick(t => t + 1), 1400);
    return () => clearInterval(i);
  }, []);

  useEffect(() => {
    setRows(prev => prev.map((r, i) => {
      // advance "last check-in" timer
      if (r.status === 'active') {
        let nx = r.lastSec + 1;
        if (nx >= r.interval + 4) {
          // simulate a missed check-in occasionally
          if (i === 3 && tick % 14 === 13) return { ...r, status: 'firing', lastSec: nx, firingFor: 0 };
          nx = 0;
        }
        return { ...r, lastSec: nx };
      }
      if (r.status === 'firing') {
        const f = r.firingFor + 1;
        if (f > 8) return { ...r, status: 'active', lastSec: 0, firingFor: 0 };
        return { ...r, firingFor: f };
      }
      return r;
    }));
  }, [tick]);

  return (
    <div style={{
      background: '#0c0d14',
      border: '1px solid var(--border)',
      borderRadius: 14,
      overflow: 'hidden',
      boxShadow: 'var(--shadow-soft)',
    }}>
      {/* App chrome */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 16,
        padding: '14px 20px',
        borderBottom: '1px solid var(--border)',
        background: 'linear-gradient(180deg, #11121b, #0c0d14)',
      }}>
        <div style={{ display: 'flex', gap: 6 }}>
          <span style={{ width: 10, height: 10, borderRadius: 5, background: '#ff5f57' }} />
          <span style={{ width: 10, height: 10, borderRadius: 5, background: '#febc2e' }} />
          <span style={{ width: 10, height: 10, borderRadius: 5, background: '#28c840' }} />
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, fontFamily: 'var(--f-mono)', fontSize: 12, color: 'var(--slate)' }}>
          <Logo size={16} />
          <span style={{ color: '#3a3b48' }}>/</span>
          <span>Fleet</span>
          <span style={{ color: '#3a3b48' }}>/</span>
          <span style={{ color: 'var(--sand)' }}>production</span>
        </div>
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 14 }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--slate)', fontFamily: 'var(--f-mono)' }}>
            <span className="dot active" /> WS connected
          </span>
        </div>
      </div>

      {/* Stat strip */}
      <div style={{
        display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)',
        borderBottom: '1px solid var(--border)',
      }}>
        <Stat label="Active" value={rows.filter(r => r.status === 'active').length} accent="var(--cyan)" />
        <Stat label="Firing" value={rows.filter(r => r.status === 'firing').length} accent="var(--coral)" />
        <Stat label="Paused" value={rows.filter(r => r.status === 'paused').length} accent="var(--slate)" />
        <Stat label="Check-ins / hr" value={"4,218"} accent="var(--sand)" />
      </div>

      {/* Toolbar */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 12,
        padding: '14px 20px',
        borderBottom: '1px solid var(--border)',
        fontSize: 13, color: 'var(--slate)',
      }}>
        <Pill active>All</Pill>
        <Pill><span className="dot active" style={{ marginRight: 6 }} /> Active</Pill>
        <Pill><span className="dot firing" style={{ marginRight: 6 }} /> Firing</Pill>
        <Pill>Paused</Pill>
        <span style={{ marginLeft: 'auto', fontFamily: 'var(--f-mono)', fontSize: 12 }}>
          ⌘K  search heartbeats
        </span>
      </div>

      {/* Table */}
      <div>
        <div style={{
          display: 'grid', gridTemplateColumns: '24px 2.4fr 1fr 1fr 1.4fr 1fr 80px',
          padding: '12px 20px',
          fontFamily: 'var(--f-mono)', fontSize: 11, letterSpacing: '0.06em',
          textTransform: 'uppercase', color: 'var(--slate)',
          borderBottom: '1px solid var(--border)',
        }}>
          <span></span>
          <span>Heartbeat</span>
          <span>Mode</span>
          <span>Last check-in</span>
          <span>Next deadline</span>
          <span>Namespace</span>
          <span style={{ textAlign: 'right' }}>State</span>
        </div>
        {rows.map((r, i) => (
          <Row key={r.name} row={r} />
        ))}
      </div>
    </div>
  );
}

function Stat({ label, value, accent }) {
  return (
    <div style={{ padding: '18px 20px', borderRight: '1px solid var(--border)' }}>
      <div className="eyebrow" style={{ fontSize: 11 }}>{label}</div>
      <div style={{
        fontFamily: 'var(--f-display)', fontSize: 32, fontWeight: 600,
        letterSpacing: '-0.02em', color: accent, marginTop: 4,
        fontVariantNumeric: 'tabular-nums',
      }}>
        {value}
      </div>
    </div>
  );
}

function Pill({ children, active }) {
  return (
    <span style={{
      padding: '6px 12px', borderRadius: 999,
      border: `1px solid ${active ? 'var(--cyan)' : 'var(--border)'}`,
      color: active ? 'var(--cyan)' : 'var(--slate)',
      background: active ? 'rgba(94,196,168,0.08)' : 'transparent',
      fontSize: 13, display: 'inline-flex', alignItems: 'center',
    }}>{children}</span>
  );
}

function Row({ row }) {
  const stateLabel = row.status === 'active' ? 'OK'
                   : row.status === 'firing' ? 'FIRING'
                   : 'PAUSED';
  const stateColor = row.status === 'active' ? 'var(--cyan)'
                   : row.status === 'firing' ? 'var(--coral)'
                   : 'var(--slate)';
  return (
    <div style={{
      display: 'grid', gridTemplateColumns: '24px 2.4fr 1fr 1fr 1.4fr 1fr 80px',
      padding: '14px 20px',
      borderBottom: '1px solid rgba(255,255,255,0.04)',
      alignItems: 'center', fontSize: 14,
      background: row.status === 'firing' ? 'rgba(255,111,97,0.04)' : 'transparent',
      borderLeft: row.status === 'firing' ? '2px solid var(--coral)'
                  : row.status === 'active' ? '2px solid transparent'
                  : '2px solid transparent',
      transition: 'background 240ms ease',
    }}>
      <span className={`dot ${row.status}`} />
      <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
        <span style={{ fontFamily: 'var(--f-mono)', fontSize: 13.5, color: 'var(--sand)' }}>{row.name}</span>
        <span style={{ fontSize: 12, color: 'var(--slate)' }}>{row.desc}</span>
      </div>
      <span style={{ fontSize: 12.5, color: 'var(--slate)', fontFamily: 'var(--f-mono)' }}>{row.mode}</span>
      <span style={{ fontSize: 13, fontFamily: 'var(--f-mono)', color: row.status === 'firing' ? 'var(--coral)' : 'var(--sand)' }}>
        {row.status === 'firing' ? `${row.firingFor + row.lastSec - row.interval}s overdue` : `${row.lastSec}s ago`}
      </span>
      <span style={{ fontSize: 13, fontFamily: 'var(--f-mono)', color: 'var(--slate)' }}>
        {row.status === 'firing' ? '— escalating' : `in ${Math.max(0, row.interval - row.lastSec)}s`}
      </span>
      <span style={{ fontSize: 12.5, color: 'var(--slate)', fontFamily: 'var(--f-mono)' }}>{row.ns}</span>
      <span style={{ textAlign: 'right' }}>
        <span style={{
          fontFamily: 'var(--f-mono)', fontSize: 11, fontWeight: 600,
          padding: '3px 8px', borderRadius: 4,
          background: row.status === 'firing' ? 'rgba(255,111,97,0.15)'
                    : row.status === 'active' ? 'rgba(94,196,168,0.12)'
                    : 'rgba(163,166,171,0.12)',
          color: stateColor, letterSpacing: '0.06em',
        }}>{stateLabel}</span>
      </span>
    </div>
  );
}

function initialRows() {
  return [
    { name: 'order-processor', desc: 'Stripe order webhook consumer', mode: 'recurring · 60s', interval: 60, lastSec: 12, ns: 'commerce', status: 'active', firingFor: 0 },
    { name: 'nightly-backup',  desc: 'Postgres → R2 snapshot',       mode: 'scheduled · 03:00', interval: 90, lastSec: 41, ns: 'infra',   status: 'active', firingFor: 0 },
    { name: 'agent-research-7', desc: 'Long-running research agent',  mode: 'recurring · 30s', interval: 30, lastSec: 18, ns: 'agents',  status: 'active', firingFor: 0 },
    { name: 'embed-worker',    desc: 'Vector embedding pipeline',     mode: 'recurring · 45s', interval: 45, lastSec: 50, ns: 'agents',  status: 'firing', firingFor: 2 },
    { name: 'deploy-canary',   desc: 'Post-deploy health verification', mode: 'oneshot · 600s', interval: 80, lastSec: 30, ns: 'deploys', status: 'active', firingFor: 0 },
    { name: 'iot-fleet-eu-west', desc: '2,418 devices reporting',      mode: 'recurring · 120s', interval: 120, lastSec: 67, ns: 'iot',    status: 'active', firingFor: 0 },
    { name: 'lone-worker-tx',  desc: 'Field crew check-in',           mode: 'scheduled · hourly', interval: 100, lastSec: 22, ns: 'safety', status: 'paused', firingFor: 0 },
  ];
}

// ─────────────────────────────────────────────────────────────────────────────
// Escalation flow visualizer — animated walk through steps
function EscalationFlow() {
  const steps = [
    { t: '00:00', label: 'Heartbeat misses deadline',  ch: 'firing',   icon: '!' },
    { t: '00:00', label: 'Webhook → Slack #incidents', ch: 'webhook',  icon: '→' },
    { t: '05:00', label: 'Email → on-call rotation',   ch: 'email',    icon: '@' },
    { t: '15:00', label: 'AI agent investigates',      ch: 'agent',    icon: 'AI' },
    { t: '15:42', label: 'Agent acks — incident logged', ch: 'resolved', icon: '✓' },
  ];
  const [active, setActive] = useState(0);
  useEffect(() => {
    const i = setInterval(() => setActive(a => (a + 1) % (steps.length + 1)), 1800);
    return () => clearInterval(i);
  }, []);

  return (
    <div style={{
      background: '#0c0d14',
      border: '1px solid var(--border)',
      borderRadius: 14,
      padding: '32px 28px',
      boxShadow: 'var(--shadow-soft)',
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 24 }}>
        <span className="eyebrow">Policy: critical-response</span>
        <span className="mono" style={{ fontSize: 12, color: 'var(--slate)' }}>step {Math.min(active, steps.length)} / {steps.length}</span>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 0, position: 'relative' }}>
        {/* vertical line */}
        <div style={{
          position: 'absolute', left: 19, top: 18, bottom: 18,
          width: 1, background: 'var(--border)',
        }} />
        <div style={{
          position: 'absolute', left: 19, top: 18,
          width: 1, background: 'linear-gradient(180deg, var(--coral), var(--cyan))',
          height: `calc(${Math.max(0, Math.min(active, steps.length) - 1) * (100 / (steps.length - 1))}%)`,
          transition: 'height 600ms ease-out',
        }} />
        {steps.map((s, i) => {
          const reached = i < active;
          const current = i === active - 1;
          const colorMap = {
            firing:   'var(--coral)',
            webhook:  '#9ec3ff',
            email:    'var(--rose)',
            agent:    'var(--cyan)',
            resolved: 'var(--cyan)',
          };
          const c = colorMap[s.ch];
          return (
            <div key={i} style={{
              display: 'flex', gap: 18, alignItems: 'center',
              padding: '14px 0',
              opacity: reached ? 1 : 0.3,
              transition: 'opacity 320ms ease',
            }}>
              <div style={{
                width: 38, height: 38, borderRadius: '50%',
                background: reached ? c : 'transparent',
                color: reached ? '#0a0b10' : 'var(--slate)',
                border: `1px solid ${reached ? c : 'var(--border)'}`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontFamily: 'var(--f-mono)', fontSize: 12, fontWeight: 700,
                boxShadow: current ? `0 0 0 6px ${c}22` : 'none',
                transition: 'all 320ms ease',
                zIndex: 1,
              }}>{s.icon}</div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 15, color: reached ? 'var(--sand)' : 'var(--slate)', fontWeight: 500 }}>
                  {s.label}
                </div>
                <div className="mono" style={{ fontSize: 12, color: 'var(--slate)', marginTop: 2 }}>
                  T+{s.t} · channel {s.ch}
                </div>
              </div>
              {current && <span className="dot firing" />}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// Export to window
Object.assign(window, {
  Logo, Nav, Footer,
  PulseLine, Terminal, Code,
  DashboardMock, EscalationFlow,
});
