/* ── Stairwell Hero ─ animated service-request timeline.
   Auto-plays the lifecycle of a single ticket ("Stairwell light issue")
   on a looping 13-beat state machine. Every visual is a pure function
   of the current beat. Designed to scale into the hero-right column
   via transform: scale() on a fixed 1440×1040 design canvas. ── */

(() => {
  const { useState, useEffect, useRef } = React;

  // ── Inline icon library — tight Lucide-style SVGs at 24×24. ──
  const SI = (size, sw, children) => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">{children}</svg>
  );
  const I = {
    panel:    () => SI(20, 1.8, <><rect x="3" y="4" width="18" height="16" rx="2"/><line x1="9" y1="4" x2="9" y2="20"/></>),
    search:   () => SI(19, 2,   <><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.5" y2="16.5"/></>),
    bell:     () => SI(20, 1.9, <><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></>),
    moon:     () => SI(19, 1.9, <path d="M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8Z"/>),
    chevSwap: () => SI(15, 2,   <><polyline points="8 9 12 5 16 9"/><polyline points="8 15 12 19 16 15"/></>),
    chevR:    () => SI(16, 2,   <path d="M9 6l6 6-6 6"/>),
    activity: () => SI(18, 1.8, <path d="M22 12h-4l-3 9L9 3l-3 9H2"/>),
    inbox:    () => SI(18, 1.8, <><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.5 5h13l3.5 7v6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-6Z"/></>),
    building: () => SI(18, 1.8, <><rect x="4" y="3" width="11" height="18" rx="1"/><path d="M15 9h4a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-4"/><line x1="8" y1="7" x2="11" y2="7"/><line x1="8" y1="11" x2="11" y2="11"/><line x1="8" y1="15" x2="11" y2="15"/></>),
    users:    () => SI(18, 1.8, <><path d="M16 19v-1.5a3.5 3.5 0 0 0-3.5-3.5h-5A3.5 3.5 0 0 0 4 17.5V19"/><circle cx="10" cy="8" r="3.2"/></>),
    alert:    () => SI(18, 1.8, <><circle cx="12" cy="12" r="9"/><line x1="12" y1="8" x2="12" y2="13"/><circle cx="12" cy="16.5" r="0.6" fill="currentColor" stroke="none"/></>),
    shield:   () => SI(18, 1.8, <><path d="M12 3l7 3v5c0 4.5-3 7.8-7 9-4-1.2-7-4.5-7-9V6Z"/><path d="M9 12l2 2 4-4"/></>),
    cart:     () => SI(18, 1.8, <><circle cx="9" cy="20" r="1.4"/><circle cx="18" cy="20" r="1.4"/><path d="M2.5 3h2l2.3 12.2a1.5 1.5 0 0 0 1.5 1.2h8.4a1.5 1.5 0 0 0 1.5-1.2L21 7H6"/></>),
    billing:  () => SI(18, 1.8, <><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="2.5"/><path d="M6 12h.01"/><path d="M18 12h.01"/></>),
    chat:     () => SI(15, 1.9, <path d="M21 12a8 8 0 0 1-11.5 7.2L4 21l1.8-5.3A8 8 0 1 1 21 12Z"/>),
    box:      () => SI(15, 1.9, <><path d="M21 8v8l-9 5-9-5V8l9-5 9 5Z"/><path d="M3.3 8 12 13l8.7-5"/><path d="M12 13v8"/></>),
    cube:     () => SI(15, 1.9, <><path d="M12 3 4 7v10l8 4 8-4V7Z"/><path d="M4 7l8 4 8-4M12 11v10"/></>),
    receipt:  () => SI(15, 1.9, <><path d="M5 3h14v18l-2.3-1.4L14.4 21l-2.4-1.4L9.6 21l-2.3-1.4L5 21Z"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="9" y1="12" x2="15" y2="12"/></>),
    navigate: () => SI(15, 1.9, <path d="M3 11l18-8-8 18-2-8-8-2Z"/>),
    person:   () => SI(15, 1.9, <><circle cx="12" cy="7.5" r="3.4"/><path d="M5.5 20a6.5 6.5 0 0 1 13 0"/></>),
    check:    () => SI(16, 2.4, <path d="M5 12.5l4.5 4.5L19 7"/>),
    checkSm:  () => SI(12, 2.6, <path d="M5 12.5l4.5 4.5L19 7"/>),
  };
  // AI sparkle — 4-point star + spark
  const Sparkle = ({ size = 14 }) => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
      <path d="M12 2c.4 3.4 1.2 4.2 4.6 4.6C13.2 7 12.4 7.8 12 11.2 11.6 7.8 10.8 7 7.4 6.6 10.8 6.2 11.6 5.4 12 2Z"/>
      <path d="M18.5 13c.25 1.9.7 2.35 2.6 2.6-1.9.25-2.35.7-2.6 2.6-.25-1.9-.7-2.35-2.6-2.6 1.9-.25 2.35-.7 2.6-2.6Z"/>
    </svg>
  );
  const Avatar = ({ initials, grad, size = 34 }) => (
    <div className="st-av" style={{ width: size, height: size, background: grad, fontSize: size * 0.4 }}>{initials}</div>
  );

  // ── Beat clock data ──
  // dwell time (ms) before advancing from each beat
  const DUR = [2000, 2600, 1500, 650, 650, 1800, 2100, 2000, 2300, 2600, 2800, 4600, 6000];

  // hero copy + icon + colour key per beat
  const HERO = [
    { k: 'neutral', i: 'chat',     h: 'New request incoming',     s: 'An occupier just messaged the building.' },
    { k: 'blue',    i: 'chat',     h: 'Request logged',           s: 'Raised from an occupier message · 14:23.' },
    { k: 'violet',  i: 'sparkle',  h: 'Running AI triage',        s: 'Classifying the issue automatically…' },
    { k: 'violet',  i: 'sparkle',  h: 'Triaging the request',    s: 'Reading the report and the asset register…' },
    { k: 'violet',  i: 'sparkle',  h: 'Triaging the request',    s: 'Electrical fault · narrowing the location…' },
    { k: 'violet',  i: 'sparkle',  h: 'Triaging the request',    s: 'Confirming priority and floor…' },
    { k: 'violet',  i: 'sparkle',  h: 'Triaged - Electrical, P2', s: 'Stairwell · Floor 4. Routing to the right fix.' },
    { k: 'blue',    i: 'cube',     h: 'Sourcing the fix',         s: 'Matching the part from the asset register.' },
    { k: 'blue',    i: 'box',      h: 'Parts secured',            s: 'G9 LED bulb auto-attached to the job.' },
    { k: 'blue',    i: 'receipt',  h: 'Quote approved for £96',   s: 'Auto-approved within policy. No action needed.' },
    { k: 'blue',    i: 'person',   h: 'Engineer allocated',       s: 'Roy Kendall, L2 Electrician, assigned by Reeve.' },
    { k: 'blue',    i: 'navigate', h: 'Roy is on the way',        s: 'ETA 14:31 · 8 minutes away.' },
    { k: 'green',   i: 'check',    h: 'Visit complete',           s: 'Resolved in 8 minutes, between 14:23 → 14:31.' },
  ];

  const STEPS = ['Logged', 'Triaged', 'Quoted', 'Allocated', 'On the way', 'Resolved'];
  // [doneCount, activeStage] per beat
  const PROG = [
    [0, 0], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1],
    [2, 2], [2, 2], [2, 2], [3, 3], [4, 4], [4, 4], [6, -1],
  ];

  const LAST_UPDATE = ['14:23','14:23','14:23','14:23','14:23','14:23','14:23','14:24','14:24','14:25','14:26','14:27','14:31'];

  // The 6 visible stages (STEPS) each span one or more beats. STAGE_BEATS is
  // the first beat of each stage — the arrows jump between these, while the
  // auto-timer still plays the in-between beats within a stage.
  const STAGE_BEATS = [0, 1, 6, 9, 10, 12];
  const stageOf = (b) => { const a = PROG[b][1]; return a === -1 ? STEPS.length - 1 : a; };

  const CHIPS = [
    { label: 'Electrical', key: true },
    { label: 'P2 priority' },
    { label: 'Stairwell' },
    { label: 'Floor 4' },
  ];
  const chipCount = (b) => (b < 3 ? 0 : Math.min(b - 2, 4));

  const statusOf = (b) => (b >= 12 ? 'resolved' : b >= 2 ? 'progress' : 'new');
  const STATUS_LBL = { new: 'New', progress: 'In progress', resolved: 'Resolved' };

  const AV_USER   = 'linear-gradient(135deg,#e8975a,#c46a32)';
  const AV_ROY    = 'linear-gradient(135deg,#ecae74,#c8773a)';

  // ── Event row ──
  function Event({ node, icon, time, children }) {
    const IconCmp = icon === 'sparkle' ? () => <Sparkle size={14} /> : I[icon];
    return (
      <div className="st-ev">
        <div className={'st-ev-node ' + node}><IconCmp /></div>
        <div className="st-ev-body">{children}</div>
        <div className="st-ev-time">{time}</div>
      </div>
    );
  }

  // ── Activity feed ──
  function Feed({ beat }) {
    const scrollRef = useRef(null);
    useEffect(() => {
      const el = scrollRef.current;
      if (el) el.scrollTop = el.scrollHeight;
    }, [beat]);

    const events = [];

    // 1 — inbound occupier message
    events.push(
      <Event key="msg" node="blue" icon="chat" time="14:23">
        <div className="st-ev-title">
          4th floor occupier
          <span className="st-chan"><I.chat />WhatsApp</span>
        </div>
        {beat < 1
          ? <div className="st-msg st-typing"><i className="st-typing-dot" /><i className="st-typing-dot" /><i className="st-typing-dot" /></div>
          : <div className="st-msg">Stairwell lights are out</div>}
      </Event>
    );

    // 2 — AI triage
    if (beat >= 2) {
      const done = beat >= 6;
      events.push(
        <Event key="triage" node="violet" icon="sparkle" time="14:23">
          <div className="st-ev-title">
            AI triage
            <span className="st-ai-flag"><Sparkle size={11} />AI</span>
            {done && <span className="st-tick"><I.checkSm /></span>}
          </div>
          <div className="st-ev-meta" style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
            {done
              ? <span>Electrical · P2 priority · Stairwell · Floor 4</span>
              : <><span className="st-spinner" /><span>Classifying - reading report &amp; asset register…</span></>}
          </div>
        </Event>
      );
    }

    if (beat >= 7) events.push(
      <Event key="bulb" node="violet" icon="cube" time="14:24">
        <div className="st-ev-title">G9 LED bulb · matched
          <span className="st-ai-flag"><Sparkle size={11} />AI</span>
          <span className="st-tick"><I.checkSm /></span>
        </div>
        <div className="st-ev-meta">Matched from the building's asset register.</div>
      </Event>
    );
    if (beat >= 8) events.push(
      <Event key="parts" node="blue" icon="box" time="14:24">
        <div className="st-ev-title">Parts ordered<span className="st-tick"><I.checkSm /></span></div>
        <div className="st-ev-meta">Auto-attached to the job.</div>
      </Event>
    );
    if (beat >= 9) events.push(
      <Event key="quote" node="blue" icon="receipt" time="14:25">
        <div className="st-ev-title">Quote approved<span className="st-tick"><I.checkSm /></span></div>
        <div className="st-ev-meta"><span className="st-cost">£96</span> · auto-approved within policy.</div>
      </Event>
    );
    if (beat >= 10) events.push(
      <Event key="alloc" node="violet" icon="person" time="14:26">
        <div className="st-ev-title">Roy Kendall allocated
          <span className="st-ai-flag"><Sparkle size={11} />AI</span>
          <span className="st-tick"><I.checkSm /></span>
        </div>
        <div className="st-ev-meta">L2 Electrician · nearest available operative.</div>
      </Event>
    );
    if (beat >= 11) events.push(
      <Event key="away" node="amber" icon="navigate" time="14:27">
        <div className="st-ev-title">
          Roy on the way
          {beat < 12 && <span className="st-live-dot" style={{ marginLeft: 2 }} />}
          {beat >= 12 && <span className="st-tick"><I.checkSm /></span>}
        </div>
        <div className="st-ev-meta">Reeve · L2 Electrician · ETA 14:31 · 8 minutes away.</div>
      </Event>
    );
    if (beat >= 12) events.push(
      <Event key="done" node="green" icon="check" time="14:31">
        <div className="st-ev-title">Visit complete<span className="st-tick"><I.checkSm /></span></div>
        <div className="st-ev-meta">Stairwell lights restored · resolved in 8 minutes.</div>
      </Event>
    );

    return (
      <div className="st-card">
        <div className="st-card-head">
          <I.activity />
          <span className="st-ttl">Activity</span>
          <span className="st-count">{events.length}</span>
        </div>
        <div className="st-feed" ref={scrollRef}>
          <div className="st-feed-track">{events}</div>
        </div>
      </div>
    );
  }

  // ── Hero status card ──
  function HeroCard({ beat }) {
    const hero = HERO[beat];
    const [doneCount, active] = PROG[beat];
    const fillIndex = active >= 0 ? active : 5;
    const fillPct = `calc(4% + ${(fillIndex / 5) * 92}%)`;
    const HeroIcon = hero.i === 'sparkle' ? () => <Sparkle size={26} /> : I[hero.i];

    return (
      <div className={'st-hero' + (beat >= 12 ? ' st-resolved' : '')}>
        <div className="st-hero-top">
          <div className={'st-hero-icon ' + hero.k}><HeroIcon /></div>
          <div className="st-hero-text st-hero-fade" key={beat}>
            <div className="st-hero-headline">{hero.h}</div>
            <div className="st-hero-sub">{hero.s}</div>
          </div>
        </div>

        <div className="st-stepper">
          <div className="st-step-track" />
          <div className="st-step-fill" style={{ width: fillPct }} />
          <div className="st-steps-row">
            {STEPS.map((label, i) => {
              const cls = i < doneCount ? 'done' : i === active ? 'active' : '';
              return (
                <div className={'st-step ' + cls} key={label}>
                  <div className="st-step-node"><I.check /></div>
                  <div className="st-step-label">{label}</div>
                </div>
              );
            })}
          </div>
        </div>

        {beat >= 11 && (
          <div className={'st-dispatch' + (beat >= 12 ? ' st-dispatch-done' : '')}>
            <Avatar initials="RK" grad={AV_ROY} size={42} />
            <div>
              <div className="st-dispatch-title">
                {beat >= 12 ? 'Visit complete' : 'Roy Kendall is on the way'}
                {beat < 12
                  ? <span className="st-live-dot" />
                  : <span className="st-tick" style={{ color: '#6fe3b0' }}><I.checkSm /></span>}
              </div>
              <div className="st-dispatch-sub">Reeve · L2 Electrician · {beat >= 12 ? 'on site 8 min' : '8 min away'}</div>
            </div>
            <div>
              <div className="st-dispatch-eta-lbl">{beat >= 12 ? 'Resolved' : 'ETA'}</div>
              <div className="st-dispatch-eta">14:31</div>
            </div>
          </div>
        )}
      </div>
    );
  }

  // ── Triage chip strip ──
  function TriageBar({ beat }) {
    const count = chipCount(beat);
    return (
      <div className="st-triagebar">
        {beat >= 2 && <span className="st-triage-tag"><Sparkle size={13} />AI TRIAGE</span>}
        <div className="st-chips">
          {CHIPS.slice(0, count).map((c) => (
            <span className={'st-chip' + (c.key ? ' st-chip-key' : '')} key={c.label}>
              {!c.key && <span className="st-cdot" />}{c.label}
            </span>
          ))}
        </div>
      </div>
    );
  }

  // ── Details panel ──
  function Details({ beat }) {
    const loc = beat >= 5;
    const pri = beat >= 4;
    const rep = beat >= 1;
    return (
      <div className="st-card st-dcard">
        <div className="st-ttl">Details</div>
        <div className="st-dfield"><div className="st-dlabel">Reference</div><div className="st-dval st-mono">mf-SR-04</div></div>
        <div className="st-dfield"><div className="st-dlabel">Raised</div><div className={'st-dval' + (rep ? '' : ' st-muted')}>{rep ? '11 Jun 2026, 14:23' : '—'}</div></div>
        <div className="st-dfield"><div className="st-dlabel">Last update</div><div className={'st-dval' + (rep ? '' : ' st-muted')}>{rep ? '11 Jun 2026, ' + LAST_UPDATE[beat] : '—'}</div></div>
        <div className="st-dfield"><div className="st-dlabel">Location</div><div className={'st-dval' + (loc ? '' : ' st-muted')}>{loc ? 'Stairwell - Floor 4' : (beat >= 2 ? 'Pending triage' : '—')}</div></div>
        <div className="st-dfield"><div className="st-dlabel">Priority</div><div className="st-dval">{pri ? <span className="st-pill st-pill-pri">P2 · Medium</span> : <span className="st-muted">—</span>}</div></div>
        <div className="st-dfield"><div className="st-dlabel">Reported by</div><div className={'st-dval' + (rep ? '' : ' st-muted')}>{rep ? '4th floor occupier' : '—'}</div></div>
      </div>
    );
  }

  // ── Service partner panel ──
  function Partner({ beat }) {
    return (
      <div className="st-card st-partner">
        <div className="st-dlabel">Service partner</div>
        <div className="st-prow">
          <img
            src="assets/img/brand/reeve-icon.svg"
            alt=""
            aria-hidden="true"
            className="st-pmark"
          />
          <div>
            <div className="st-pname">Reeve</div>
            <div className="st-phandle">Managed end-to-end by Reeve</div>
          </div>
        </div>
        {beat >= 10 && (
          <div className="st-eng-row">
            <Avatar initials="RK" grad={AV_ROY} size={34} />
            <div>
              <div className="st-eng-name">Roy Kendall</div>
              <div className="st-eng-role">L2 Electrician</div>
            </div>
            <span className="st-eng-tag st-ai-flag"><Sparkle size={11} />AI matched</span>
          </div>
        )}
      </div>
    );
  }

  // ── Left rail ──
  function Rail() {
    const items = [
      { g: 'Control Center' },
      { i: 'activity', l: 'Live Board' },
      { i: 'inbox', l: 'Actions', c: 3 },
      { g: 'Portfolio' },
      { i: 'building', l: 'Assets' },
      { i: 'users', l: 'Occupants' },
      { g: 'Maintenance' },
      { i: 'alert', l: 'Service Requests', active: true },
      { i: 'shield', l: 'Compliance' },
      { g: 'Services' },
      { i: 'cart', l: 'Everyday F&B' },
      { i: 'shield', l: 'Compliance products' },
      { g: 'Finance' },
      { i: 'billing', l: 'Billing' },
    ];
    return (
      <aside className="st-rail">
        <div className="st-brand">
          <div className="st-brand-mark">
            <svg width="30" height="22" viewBox="0 0 60 44" aria-hidden="true">
              <text x="30" y="31" textAnchor="middle" fontFamily="'Source Serif 4', Georgia, serif" fontSize="25" fontWeight="700" fill="#1b1b1a" letterSpacing="-0.5">M&amp;F</text>
            </svg>
          </div>
          <div>
            <div className="st-brand-name">Marlow &amp; Finch</div>
            <div className="st-brand-sub">marlowfinch.co</div>
          </div>
          <div className="st-brand-switch"><I.chevSwap /></div>
        </div>
        {items.map((it, idx) =>
          it.g
            ? <div className="st-rail-group" key={'g' + idx}>{it.g}</div>
            : (
              <div className={'st-rail-item' + (it.active ? ' st-rail-active' : '')} key={idx}>
                {I[it.i]()}
                <span>{it.l}</span>
                {it.c != null && <span className="st-rail-count">{it.c}</span>}
              </div>
            )
        )}
      </aside>
    );
  }

  // ── Top bar ──
  function Topbar() {
    return (
      <header className="st-topbar">
        <div className="st-collapse"><I.panel /></div>
        <div className="st-spacer" />
        <div className="st-tb-actions">
          <div className="st-tb-btn"><I.search /></div>
          <div className="st-tb-btn"><I.bell /><span className="st-tb-badge">2</span></div>
          <div className="st-tb-btn"><I.moon /></div>
          <div className="st-tb-avatar"><Avatar initials="AM" grad={AV_USER} size={34} /></div>
        </div>
      </header>
    );
  }

  // ── Main wrapper component ──
  function StairwellHero() {
    const [beat, setBeat] = useState(0);
    // Once the user drives with the arrows, auto-play stops for good so a demo
    // isn't yanked to the next stage mid-explanation. Manual-only from then on.
    const [manual, setManual] = useState(false);
    const stageRef = useRef(null);
    const canvasRef = useRef(null);

    // Reduced-motion → snap to the resolved state, no auto-play.
    useEffect(() => {
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) setBeat(DUR.length - 1);
    }, []);

    // Auto-advance the beat clock — but only until the user takes manual control.
    // Keying on `beat` gives each stage its own dwell; `manual` short-circuits it
    // so once the user steps with an arrow the auto-play stops permanently.
    useEffect(() => {
      if (manual) return;
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      const timer = setTimeout(() => setBeat((b) => (b + 1) % DUR.length), DUR[beat]);
      return () => clearTimeout(timer);
    }, [beat, manual]);

    // Manual step by STAGE (not beat). Jumps to the next/prev stage's first beat
    // and wraps around. Flips `manual` on, which disables auto-advance for good.
    const go = (dir) => {
      setManual(true);
      const next = (stageOf(beat) + dir + STAGE_BEATS.length) % STAGE_BEATS.length;
      setBeat(STAGE_BEATS[next]);
    };

    // Scale the 1440×1040 design canvas to fit the stage container.
    useEffect(() => {
      const stage = stageRef.current;
      const canvas = canvasRef.current;
      if (!stage || !canvas) return;
      const fit = () => {
        const w = stage.clientWidth;
        const h = stage.clientHeight;
        const s = Math.min(w / 1440, h / 1040);
        canvas.style.transform = `translate(-50%, -50%) scale(${s})`;
      };
      fit();
      const ro = new ResizeObserver(fit);
      ro.observe(stage);
      return () => ro.disconnect();
    }, []);

    const status = statusOf(beat);

    return (
      <div className="st-stage" ref={stageRef}>
        <div className="st-canvas" ref={canvasRef}>
          <div className="st-app">
            <Topbar />
            <Rail />
            <div className="st-main">
              <div className="st-col-main">
                <div className="st-crumb">
                  Service Requests <span className="st-sep"><I.chevR /></span>
                  <span className="st-here">Stairwell light issue</span>
                </div>
                <div className="st-titlewrap">
                  <div className="st-pills">
                    <span className={'st-pill st-pill-status st-pill-' + status}>{STATUS_LBL[status]}</span>
                    {beat >= 4 && <span className="st-pill st-pill-pri">P2</span>}
                    <span className="st-pill st-pill-ref">#mf-SR-04</span>
                  </div>
                  <div className="st-page-title">Stairwell light issue</div>
                </div>
                <TriageBar beat={beat} />
                <HeroCard beat={beat} />
                <Feed beat={beat} />
              </div>
              <div className="st-col-side">
                <Details beat={beat} />
                <Partner beat={beat} />
              </div>
            </div>
          </div>
        </div>
        <div className="st-controls" role="group" aria-label="Step through the demo">
          <button type="button" className="st-ctrl" onClick={() => go(-1)} aria-label="Previous stage">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 6l-6 6 6 6"/></svg>
          </button>
          <span className="st-ctrl-count">{stageOf(beat) + 1}/{STEPS.length} · {STEPS[stageOf(beat)]}</span>
          <button type="button" className="st-ctrl" onClick={() => go(1)} aria-label="Next stage">
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 6l6 6-6 6"/></svg>
          </button>
        </div>
        <style>{ST_CSS}</style>
      </div>
    );
  }

  // ── Scoped CSS (all selectors live under .st-stage) ──
  const ST_CSS = `
.st-stage {
  --st-bg:#fcfcfb; --st-panel:#fff; --st-rail:#f8f8f7; --st-inset:#f5f5f3; --st-inset-2:#efefec;
  --st-rule:#ececea; --st-rule-strong:#e0e0dd;
  --st-ink:#1b1b1a; --st-ink-2:#3e3e3c; --st-ink-3:#6d6d6a; --st-ink-4:#9a9a96;
  --st-blue:#2f6cf0; --st-blue-ink:#1d54cf; --st-blue-wash:#e9f0fd;
  --st-amber:#c98a12; --st-amber-ink:#a9740a; --st-amber-wash:#fbf0d6;
  --st-green:#1f8a5b; --st-green-ink:#156f48; --st-green-wash:#ddf2e7;
  --st-violet:#7a5bf0; --st-violet-ink:#5a3fcf; --st-violet-wash:#efeaff;
  --st-serif:'Source Serif 4','Iowan Old Style',Georgia,serif;
  --st-sans:'Inter',ui-sans-serif,system-ui,sans-serif;
  --st-mono:ui-monospace,'SF Mono','JetBrains Mono',Menlo,monospace;
  --st-ease:cubic-bezier(.2,.7,.2,1);
  position:relative; width:100%; aspect-ratio:1440/1040;
  background:var(--st-bg); border:1px solid var(--st-rule);
  border-radius:18px; overflow:hidden;
  box-shadow:0 24px 56px -30px rgb(30 40 80 / .28), 0 4px 14px -6px rgb(0 0 0 / .06);
  font-family:var(--st-sans); color:var(--st-ink); font-size:15px; line-height:1.5;
}
.st-canvas { position:absolute; left:50%; top:50%; width:1440px; height:1040px; transform-origin:center center; background:var(--st-bg); }

/* step-through controls (live outside the scaled canvas so they stay a fixed size) */
.st-controls { position:absolute; left:50%; bottom:16px; transform:translateX(-50%); z-index:20; display:flex; align-items:center; gap:4px; padding:5px 7px; border-radius:999px; background:color-mix(in oklch, var(--st-panel) 90%, transparent); border:1px solid var(--st-rule-strong); box-shadow:0 8px 22px -10px rgb(0 0 0 / .3); backdrop-filter:blur(8px); }
.st-ctrl { width:32px; height:32px; display:grid; place-items:center; border-radius:50%; border:none; background:transparent; color:var(--st-ink-2); cursor:pointer; transition:background 140ms var(--st-ease), color 140ms var(--st-ease); }
.st-ctrl:hover { background:var(--st-inset-2); color:var(--st-ink); }
.st-ctrl:focus-visible { outline:2px solid var(--st-blue); outline-offset:2px; }
.st-ctrl-count { font-family:var(--st-sans); font-size:12.5px; font-weight:600; color:var(--st-ink-2); min-width:118px; text-align:center; letter-spacing:-.01em; white-space:nowrap; }
.st-app { position:absolute; inset:0; display:grid; grid-template-columns:248px 1fr; grid-template-rows:56px 1fr; }

/* topbar */
.st-topbar { grid-column:1/-1; display:flex; align-items:center; padding:0 22px 0 18px; border-bottom:1px solid var(--st-rule); background:color-mix(in oklch, var(--st-bg) 88%, transparent); backdrop-filter:blur(10px); z-index:5; }
.st-collapse { color:var(--st-ink-3); display:grid; place-items:center; width:34px; height:34px; border-radius:9px; }
.st-spacer { flex:1; }
.st-tb-actions { display:flex; align-items:center; gap:6px; }
.st-tb-btn { position:relative; width:36px; height:36px; border-radius:50%; display:grid; place-items:center; color:var(--st-ink-2); }
.st-tb-badge { position:absolute; top:2px; right:1px; min-width:16px; height:16px; padding:0 4px; border-radius:999px; background:#e5484d; color:#fff; font-size:10px; font-weight:700; display:grid; place-items:center; border:2px solid var(--st-bg); line-height:1; }
.st-tb-avatar { width:34px; height:34px; border-radius:50%; margin-left:6px; overflow:hidden; flex-shrink:0; }
.st-av { border-radius:50%; color:#fff; display:grid; place-items:center; font-family:var(--st-sans); font-weight:600; letter-spacing:-.01em; flex-shrink:0; }

/* rail */
.st-rail { grid-row:2; grid-column:1; border-right:1px solid var(--st-rule); background:var(--st-rail); padding:14px 14px 18px; display:flex; flex-direction:column; }
.st-brand { display:flex; align-items:center; gap:11px; padding:6px 6px 4px; margin-bottom:10px; }
.st-brand-mark { width:42px; height:42px; border-radius:11px; background:#fff; border:1px solid var(--st-rule); display:grid; place-items:center; flex-shrink:0; box-shadow:0 1px 2px rgb(0 0 0 / .04); }
.st-brand-name { font-weight:600; font-size:14.5px; color:var(--st-ink); letter-spacing:-.01em; line-height:1.1; white-space:nowrap; }
.st-brand-sub { font-size:12px; color:var(--st-ink-3); }
.st-brand-switch { margin-left:auto; color:var(--st-ink-4); display:grid; place-items:center; }
.st-rail-group { font-size:11.5px; font-weight:600; color:var(--st-ink-4); padding:14px 10px 6px; }
.st-rail-item { display:flex; align-items:center; gap:12px; padding:8px 10px; border-radius:9px; font-size:14.5px; font-weight:500; color:var(--st-ink-2); }
.st-rail-item svg { color:var(--st-ink-3); flex-shrink:0; }
.st-rail-active { background:#e9e9e6; color:var(--st-ink); font-weight:600; }
.st-rail-active svg { color:var(--st-ink); }
.st-rail-count { margin-left:auto; font-size:11px; font-weight:600; color:#fff; background:var(--st-ink); border-radius:7px; padding:1px 7px; line-height:1.5; font-variant-numeric:tabular-nums; }

/* main */
.st-main { grid-row:2; grid-column:2; overflow:hidden; display:grid; grid-template-columns:minmax(0,1fr) 312px; gap:30px; padding:26px 34px 26px 38px; align-content:start; }
.st-col-main { min-width:0; display:flex; flex-direction:column; gap:16px; }
.st-col-side { display:flex; flex-direction:column; gap:16px; }
.st-crumb { display:flex; align-items:center; gap:9px; font-size:13.5px; color:var(--st-ink-3); }
.st-sep { color:var(--st-ink-4); }
.st-here { color:var(--st-ink-2); font-weight:500; }

/* title */
.st-titlewrap { display:flex; flex-direction:column; gap:9px; }
.st-pills { display:flex; align-items:center; gap:9px; }
.st-pill { font-size:12.5px; font-weight:600; padding:3px 11px; border-radius:999px; line-height:1.5; white-space:nowrap; transition:background 240ms var(--st-ease), color 240ms var(--st-ease); }
.st-pill-status.st-pill-new { background:var(--st-inset-2); color:var(--st-ink-3); }
.st-pill-status.st-pill-progress { background:var(--st-amber-wash); color:var(--st-amber-ink); }
.st-pill-status.st-pill-resolved { background:var(--st-green-wash); color:var(--st-green-ink); }
.st-pill-pri { background:var(--st-amber-wash); color:var(--st-amber-ink); }
.st-pill-ref { font-family:var(--st-mono); font-size:12.5px; color:var(--st-ink-4); font-weight:500; background:none; padding-left:2px; }
.st-page-title { font-family:var(--st-serif); font-weight:600; font-size:33px; line-height:1.12; letter-spacing:-.01em; color:var(--st-ink); }

/* triage chips */
.st-triagebar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; min-height:30px; }
.st-triage-tag { display:inline-flex; align-items:center; gap:6px; color:var(--st-violet-ink); font-size:12px; font-weight:700; letter-spacing:.02em; }
.st-chips { display:flex; gap:7px; flex-wrap:wrap; }
.st-chip { display:inline-flex; align-items:center; gap:7px; padding:4px 11px 4px 9px; border-radius:999px; background:#fff; border:1px solid var(--st-rule-strong); font-size:12.5px; font-weight:500; color:var(--st-ink); white-space:nowrap; box-shadow:0 1px 2px rgb(0 0 0 / .04); animation:stEvIn 340ms var(--st-ease) both; }
.st-cdot { width:6px; height:6px; border-radius:50%; background:var(--st-violet); flex-shrink:0; }
.st-chip-key { background:var(--st-violet-wash); border-color:color-mix(in oklch, var(--st-violet) 24%, transparent); color:var(--st-violet-ink); font-weight:600; }

/* cards */
.st-card { background:var(--st-panel); border:1px solid var(--st-rule); border-radius:14px; box-shadow:0 1px 2px rgb(0 0 0 / .05), 0 2px 5px rgb(0 0 0 / .03); }
.st-card-head { display:flex; align-items:center; gap:10px; padding:16px 20px; }
.st-card-head .st-ttl { font-family:var(--st-serif); font-weight:600; font-size:18px; color:var(--st-ink); }
.st-card-head svg { color:var(--st-ink-3); }
.st-count { font-size:12px; font-weight:600; color:var(--st-ink-3); background:var(--st-inset-2); border-radius:7px; padding:0 7px; line-height:1.55; margin-left:2px; }

/* hero status card */
.st-hero { border-radius:18px; padding:26px 28px 24px; position:relative; overflow:hidden; background:linear-gradient(118deg,#dce7fb,#e9f0fc 46%,#f2eefb); transition:background 600ms var(--st-ease); }
.st-hero.st-resolved { background:linear-gradient(120deg,#d7f3e6,#e6f3ec 50%,#eef4ec); }
.st-hero-top { display:flex; align-items:flex-start; gap:18px; }
.st-hero-icon { width:60px; height:60px; border-radius:15px; flex-shrink:0; display:grid; place-items:center; color:#fff; background:linear-gradient(155deg,#6296f5,#2f63e6); box-shadow:0 8px 20px -8px rgb(47 99 230 / .6); transition:background 500ms var(--st-ease), box-shadow 500ms var(--st-ease); }
.st-hero-icon.violet { background:linear-gradient(155deg,#9b82f7,#6d4ee8); box-shadow:0 8px 20px -8px rgb(109 78 232 / .55); }
.st-hero-icon.green { background:linear-gradient(155deg,#3fc88a,#1f8a5b); box-shadow:0 8px 20px -8px rgb(31 138 91 / .5); }
.st-hero-icon.neutral { background:linear-gradient(155deg,#b9b9b3,#8c8c86); box-shadow:0 8px 20px -8px rgb(0 0 0 / .25); }
.st-hero-icon svg { width:30px; height:30px; }
.st-hero-headline { font-family:var(--st-serif); font-weight:600; font-size:26px; line-height:1.12; letter-spacing:-.01em; color:var(--st-ink); }
.st-hero-sub { font-size:15px; color:var(--st-ink-2); margin-top:5px; }
.st-hero-text { padding-top:2px; flex:1; min-width:0; }
.st-hero-fade { animation:stHeroFade 460ms var(--st-ease) both; }
@keyframes stHeroFade { from { opacity:.35; transform:translateY(6px); } to { opacity:1; transform:none; } }

/* stepper */
.st-stepper { margin-top:26px; position:relative; padding:0 4px; }
.st-step-track { position:absolute; left:4%; right:4%; top:16px; height:3px; border-radius:2px; background:rgb(28 40 80 / .12); }
.st-step-fill { position:absolute; left:4%; top:16px; height:3px; border-radius:2px; background:var(--st-blue); transition:width 600ms var(--st-ease); }
.st-hero.st-resolved .st-step-fill { background:var(--st-green); }
.st-steps-row { position:relative; display:flex; justify-content:space-between; }
.st-step { display:flex; flex-direction:column; align-items:center; gap:9px; flex:1; }
.st-step-node { width:34px; height:34px; border-radius:50%; display:grid; place-items:center; background:#fff; border:2px solid rgb(28 40 80 / .18); color:transparent; z-index:1; transition:background 360ms var(--st-ease), border-color 360ms var(--st-ease), box-shadow 360ms var(--st-ease); }
.st-step-node svg { opacity:0; transition:opacity 240ms var(--st-ease); grid-area:1/1; }
.st-step.done .st-step-node { background:var(--st-blue); border-color:var(--st-blue); color:#fff; }
.st-step.done .st-step-node svg { opacity:1; }
.st-hero.st-resolved .st-step.done .st-step-node { background:var(--st-green); border-color:var(--st-green); }
.st-step.active .st-step-node { background:#fff; border-color:var(--st-blue); box-shadow:0 0 0 5px color-mix(in oklch, var(--st-blue) 16%, transparent); }
.st-step.active .st-step-node::after { content:''; grid-area:1/1; width:10px; height:10px; border-radius:50%; background:var(--st-blue); animation:stPulseDot 1.8s var(--st-ease) infinite; }
.st-hero.st-resolved .st-step.active .st-step-node { border-color:var(--st-green); box-shadow:0 0 0 5px color-mix(in oklch, var(--st-green) 16%, transparent); }
.st-hero.st-resolved .st-step.active .st-step-node::after { background:var(--st-green); }
@keyframes stPulseDot { 0%,100% { transform:scale(1); opacity:1; } 50% { transform:scale(.7); opacity:.55; } }
.st-step-label { font-size:13px; font-weight:500; color:var(--st-ink-4); white-space:nowrap; transition:color 360ms var(--st-ease); }
.st-step.done .st-step-label, .st-step.active .st-step-label { color:var(--st-ink); font-weight:600; }

/* dispatch confirmation */
.st-dispatch { margin-top:22px; display:grid; grid-template-columns:auto 1fr auto; gap:15px; align-items:center; padding:14px 16px; background:var(--st-ink); color:#fff; border-radius:14px; box-shadow:0 14px 30px -16px rgb(0 0 0 / .5); animation:stSlideUp 460ms var(--st-ease) both; }
@keyframes stSlideUp { from { transform:translateY(12px) scale(.99); } to { transform:none; } }
.st-dispatch-title { font-weight:600; font-size:15px; display:flex; align-items:center; gap:8px; }
.st-dispatch-sub { font-size:12.5px; color:rgb(255 255 255 / .62); margin-top:2px; }
.st-dispatch-eta-lbl { font-size:10.5px; text-transform:uppercase; letter-spacing:.07em; color:rgb(255 255 255 / .5); text-align:right; }
.st-dispatch-eta { font-family:var(--st-serif); font-weight:600; font-size:23px; letter-spacing:-.01em; color:#9fe6c4; font-variant-numeric:tabular-nums; text-align:right; line-height:1.1; }
.st-live-dot { width:7px; height:7px; border-radius:50%; background:#57d08f; box-shadow:0 0 0 0 rgb(87 208 143 / .6); animation:stLivePulse 2s ease-out infinite; }
@keyframes stLivePulse { 0% { box-shadow:0 0 0 0 rgb(87 208 143 / .55); } 70%,100% { box-shadow:0 0 0 6px rgb(87 208 143 / 0); } }

/* activity feed */
.st-feed { padding:4px 20px 14px; max-height:414px; overflow-y:auto; scrollbar-width:none; }
.st-feed::-webkit-scrollbar { display:none; }
.st-feed-track { position:relative; }
.st-feed-track::before { content:''; position:absolute; left:13px; top:14px; bottom:18px; width:2px; background:var(--st-rule); border-radius:2px; }
.st-ev { position:relative; display:grid; grid-template-columns:28px 1fr auto; gap:13px; padding:11px 0; align-items:start; animation:stEvIn 440ms var(--st-ease) both; }
@keyframes stEvIn { from { transform:translateY(9px); } to { transform:none; } }
.st-ev-node { width:28px; height:28px; border-radius:50%; display:grid; place-items:center; background:var(--st-inset); border:1px solid var(--st-rule-strong); color:var(--st-ink-3); z-index:1; flex-shrink:0; }
.st-ev-node.blue { background:var(--st-blue-wash); border-color:color-mix(in oklch, var(--st-blue) 26%, transparent); color:var(--st-blue-ink); }
.st-ev-node.violet { background:var(--st-violet-wash); border-color:color-mix(in oklch, var(--st-violet) 26%, transparent); color:var(--st-violet-ink); }
.st-ev-node.green { background:var(--st-green-wash); border-color:color-mix(in oklch, var(--st-green) 30%, transparent); color:var(--st-green-ink); }
.st-ev-node.amber { background:var(--st-amber-wash); border-color:color-mix(in oklch, var(--st-amber) 30%, transparent); color:var(--st-amber-ink); }
.st-ev-body { min-width:0; }
.st-ev-title { font-size:14.5px; font-weight:600; color:var(--st-ink); display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.st-ev-meta { font-size:13px; color:var(--st-ink-3); margin-top:2px; line-height:1.45; }
.st-ev-time { font-family:var(--st-mono); font-size:11.5px; color:var(--st-ink-4); white-space:nowrap; padding-top:2px; }
.st-ai-flag { display:inline-flex; align-items:center; gap:4px; font-size:10.5px; font-weight:700; letter-spacing:.03em; color:var(--st-violet-ink); background:var(--st-violet-wash); padding:1px 7px 1px 5px; border-radius:999px; white-space:nowrap; }
.st-tick { color:var(--st-green); display:inline-flex; }
.st-msg { background:var(--st-inset); border:1px solid var(--st-rule); border-radius:4px 12px 12px 12px; padding:9px 13px; margin-top:7px; font-size:13.5px; color:var(--st-ink); max-width:360px; }
.st-msg.st-typing { display:inline-flex; gap:4px; padding:12px 14px; }
.st-typing-dot { width:6px; height:6px; border-radius:50%; background:var(--st-ink-4); animation:stTyping 1.2s infinite; }
.st-typing-dot:nth-child(2) { animation-delay:.18s; }
.st-typing-dot:nth-child(3) { animation-delay:.36s; }
@keyframes stTyping { 0%,60%,100% { opacity:.3; transform:translateY(0); } 30% { opacity:1; transform:translateY(-3px); } }
.st-chan { display:inline-flex; align-items:center; gap:5px; font-size:11px; font-weight:600; color:var(--st-ink-3); background:var(--st-inset-2); border-radius:999px; padding:1px 8px; }
.st-spinner { width:13px; height:13px; border:2px solid color-mix(in oklch, var(--st-violet) 30%, transparent); border-top-color:var(--st-violet); border-radius:50%; animation:stSpin .7s linear infinite; }
@keyframes stSpin { to { transform:rotate(360deg); } }
.st-cost { font-family:var(--st-serif); font-weight:600; color:var(--st-ink); }

/* details */
.st-dcard { padding:18px 20px 20px; }
.st-dcard .st-ttl { font-family:var(--st-serif); font-weight:600; font-size:19px; color:var(--st-ink); margin-bottom:6px; }
.st-dfield { padding:13px 0; border-bottom:1px solid var(--st-rule); }
.st-dfield:last-child { border-bottom:0; padding-bottom:2px; }
.st-dlabel { font-size:11px; font-weight:600; letter-spacing:.07em; text-transform:uppercase; color:var(--st-ink-3); margin-bottom:6px; }
.st-dval { font-size:15px; color:var(--st-ink); transition:color 240ms var(--st-ease); }
.st-dval.st-mono { font-family:var(--st-mono); font-size:14px; }
.st-muted { color:var(--st-ink-4); }

/* partner */
.st-partner { padding:16px 20px 18px; }
.st-partner .st-dlabel { margin-bottom:12px; }
.st-prow { display:flex; align-items:center; gap:12px; }
.st-pmark { width:40px; height:40px; border-radius:10px; flex-shrink:0; display:block; box-shadow:0 1px 2px rgb(0 0 0 / .04); }
.st-pname { font-weight:600; font-size:15.5px; color:var(--st-ink); letter-spacing:-.005em; font-family:'Inter Tight','Inter',sans-serif; }
.st-phandle { font-size:12.5px; color:var(--st-ink-3); }
.st-eng-row { display:flex; align-items:center; gap:11px; margin-top:14px; padding-top:14px; border-top:1px solid var(--st-rule); animation:stEvIn 440ms var(--st-ease) both; }
.st-eng-name { font-weight:600; font-size:14px; color:var(--st-ink); }
.st-eng-role { font-size:12px; color:var(--st-ink-3); }
.st-eng-tag { margin-left:auto; }

@media (prefers-reduced-motion: reduce) {
  .st-stage *, .st-stage *::before, .st-stage *::after { animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important; }
}
`;

  window.StairwellHero = StairwellHero;
})();
