// spndly — hero workflow deck: 3 distinct, use-case-specific flows on a rotating carousel
const { useRef: wfRef, useEffect: wfFx, useState: wfState, useLayoutEffect: wfLayout } = React;

const WF_GLYPH = {
  ingest: "M12 3v10M8 9l4 4 4-4M4 18h16",
  check:  "M4 12l5 5L20 6",
  branch: "M5 12h4M15 6h4M15 18h4M9 12c3 0 3-6 6-6M9 12c3 0 3 6 6 6",
  eye:    "M2.6 12C5 7.6 8.4 6 12 6s7 1.6 9.4 6c-2.4 4.4-5.8 6-9.4 6S5 16.4 2.6 12Z M12 9.3a2.7 2.7 0 1 0 0 5.4 2.7 2.7 0 0 0 0-5.4Z",
  filter: "M4 6h16l-6.2 7.2V19l-3.6-1.8v-4z",
  pen:    "M15.8 5.2l3 3L9 18l-4 1 1-4z",
  send:   "M3.5 11.2 20.5 3.5l-7.7 17-2.6-6.8z M10.2 13.6 20.5 3.5",
  clipboard: "M9 4h6v2.6H9z M6.6 5.5h10.8v14H6.6z M9.4 10.4h5.2M9.4 13.4h5.2M9.4 16.4h3.4",
  dollar: "M12 4.2v15.6M15.2 8c-.6-1.4-1.8-2-3.2-2-1.8 0-3.1.9-3.1 2.4 0 3.2 6.3 1.5 6.3 4.9 0 1.6-1.5 2.5-3.2 2.5-1.5 0-2.7-.6-3.3-2",
};

// Three real automations: an Excel-driven A/R chaser + two operations workflows.
const WF_FLOWS = [
  {
    // Excel-document automation: an A/R aging spreadsheet that chases overdue invoices.
    kind: 'excel',
    id: 'ar-aging.xlsx', name: 'Chase overdue invoices', stat: 'aging report → reminders, sent',
    nodes: [
      { id: 's0', t: 'logo', s: 'microsoft-excel', l: 'A/R aging', x: 58, y: 150 },
      { id: 's1', t: 'logo', s: 'stripe', l: 'Payments', x: 58, y: 250 },
      { id: 'f1', t: 'op', g: 'filter', l: 'find 14d+ overdue', x: 200, y: 200 },
      { id: 'f2', t: 'op', g: 'pen', l: 'draft reminder', x: 312, y: 200 },
      { id: 'f3', t: 'op', g: 'send', l: 'send', x: 420, y: 200 },
      { id: 'a0', t: 'logo', s: 'gmail', l: 'Email', x: 522, y: 96 },
      { id: 'a1', t: 'logo', s: 'twilio', l: 'Text', x: 522, y: 200 },
      { id: 'a2', t: 'logo', s: 'slack', l: 'Notify team', x: 522, y: 304 },
    ],
    edges: [['s0', 'f1'], ['s1', 'f1'], ['f1', 'f2'], ['f2', 'f3'], ['f3', 'a0'], ['f3', 'a1'], ['f3', 'a2']],
    routes: [['s0', 'f1', 'f2', 'f3', 'a0'], ['s1', 'f1', 'f2', 'f3', 'a1'], ['s0', 'f1', 'f2', 'f3', 'a2']],
  },
  {
    // Enterprise escalation workflow: CRM case → ticket → issue → docs → PR → notify.
    id: 'escalation.flow', name: 'Escalation → engineering fix', stat: 'case → ticket → issue → PR → notified',
    steps: [
      { logo: 'salesforce', title: 'Escalation flagged',     desc: 'High-priority case raised in the CRM' },
      { logo: 'servicenow', title: 'Open incident',          desc: 'ITSM ticket created & categorized' },
      { logo: 'jira',       title: 'Create engineering issue',desc: 'Bug filed with logs & repro steps' },
      { logo: 'sharepoint', title: 'Attach runbook',         desc: 'Pulled the runbook & account docs' },
      { logo: 'github',     title: 'Open fix PR',            desc: 'Draft pull request opened for the fix' },
      { logo: 'gmail',      title: 'Notify the customer',    desc: 'Status email sent to the account owner' },
    ],
  },
  {
    // Clinic IT workflow: EHR issue → ticket → issue → specs → PR → notify.
    id: 'clinic-it.flow', name: 'EHR issue → engineering fix', stat: 'EHR → ticket → issue → PR → notified',
    steps: [
      { logo: 'epic',       title: 'EHR issue reported',     desc: 'Order/result problem raised from the chart' },
      { logo: 'servicenow', title: 'Open incident',          desc: 'Help-desk ticket created & routed' },
      { logo: 'jira',       title: 'Create engineering issue',desc: 'Task filed for the integration team' },
      { logo: 'sharepoint', title: 'Attach interface spec',  desc: 'Pulled the SOP & HL7/FHIR specs' },
      { logo: 'github',     title: 'Open fix PR',            desc: 'Draft pull request for the interface fix' },
      { logo: 'gmail',      title: 'Notify the clinic',      desc: 'Update email sent to the practice' },
    ],
  },
  { kind: 'doc', id: 'claim-eob.pdf', name: 'Read the document' },
];

const WF_NR = 21;

// Orthogonal edge that exits/enters whichever side faces the target — supports
// left→right, right→left, and vertical "snake" bends, all with right angles.
function wfEdge(a, b) {
  const NR = WF_NR, r = 9;
  const dx = b.x - a.x, dy = b.y - a.y;
  if (Math.abs(dx) >= Math.abs(dy)) {
    const ax = a.x + (dx >= 0 ? NR : -NR), bx = b.x + (dx >= 0 ? -NR : NR);
    if (Math.abs(dy) < 0.6) return `M${ax} ${a.y} H${bx}`;
    const midX = (ax + bx) / 2, s = dy > 0 ? 1 : -1, hs = bx > ax ? 1 : -1, rr = Math.min(r, Math.abs(dy) / 2);
    return `M${ax} ${a.y} H${(midX - hs * r).toFixed(1)} Q${midX} ${a.y} ${midX} ${(a.y + s * rr).toFixed(1)} `
      + `V${(b.y - s * rr).toFixed(1)} Q${midX} ${b.y} ${(midX + hs * r).toFixed(1)} ${b.y} H${bx}`;
  }
  const ay = a.y + (dy >= 0 ? NR : -NR), by = b.y + (dy >= 0 ? -NR : NR);
  if (Math.abs(dx) < 0.6) return `M${a.x} ${ay} V${by}`;
  const midY = (ay + by) / 2, s = dx > 0 ? 1 : -1, vs = by > ay ? 1 : -1, rr = Math.min(r, Math.abs(dx) / 2);
  return `M${a.x} ${ay} V${(midY - vs * r).toFixed(1)} Q${a.x} ${midY} ${(a.x + s * rr).toFixed(1)} ${midY} `
    + `H${(b.x - s * rr).toFixed(1)} Q${b.x} ${midY} ${b.x} ${(midY + vs * r).toFixed(1)} V${by}`;
}

function WorkflowCanvas({ config, animate = true }) {
  const reduce = typeof window !== 'undefined' && window.matchMedia
    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const flow = animate && !reduce;

  const paths = wfRef({}), rings = wfRef({}), packetRef = wfRef(null);
  const setRef = (store, key) => (el) => { if (el) store.current[key] = el; };

  const N = {};
  config.nodes.forEach(n => { N[n.id] = n; });
  const edges = config.edges.map(([a, b]) => {
    const na = N[a], nb = N[b];
    return { id: a + '_' + b, d: wfEdge({ x: na.x, y: na.y }, { x: nb.x, y: nb.y }) };
  });
  const path = config.routes[0];
  const segIds = []; for (let j = 1; j < path.length; j++) segIds.push(path[j - 1] + '_' + path[j]);

  // -1 = none; otherwise an index into `path` for the step currently "executing"
  const [active, setActive] = wfState(-1);
  const [done, setDone] = wfState(() => new Set(flow ? [] : config.nodes.map(n => n.id)));

  wfFx(() => {
    if (!flow) { setActive(-1); setDone(new Set(config.nodes.map(n => n.id))); return; }
    setActive(0); setDone(new Set([path[0]]));
    const Dt = 600, Dd = 720, K = segIds.length, cycle = K * (Dt + Dd), hold = 1500, total = cycle + hold;
    let raf, running = true, t0 = null, lastArrived = 0, lastCycle = 0;
    const radiate = (id) => {
      const rg = rings.current[id];
      if (rg && rg.animate) rg.animate([{ opacity: .5, transform: 'scale(.42)' }, { opacity: 0, transform: 'scale(2.4)' }],
        { duration: 700, easing: 'cubic-bezier(.2,.7,.2,1)' });
    };
    const setHot = (eid) => edges.forEach(e => { const el = paths.current[e.id]; if (el) el.classList.toggle('is-hot', e.id === eid); });
    const place = (eid, lt) => {
      const pe = paths.current[eid], pk = packetRef.current;
      if (pe && pk) { const p = pe.getPointAtLength(pe.getTotalLength() * lt); pk.setAttribute('transform', `translate(${p.x.toFixed(2)},${p.y.toFixed(2)})`); }
    };
    const frame = (now) => {
      if (!running) return;
      if (t0 == null) t0 = now;
      const el = now - t0, tt = el % total, cyc = Math.floor(el / total);
      if (cyc !== lastCycle) { lastCycle = cyc; lastArrived = 0; setDone(new Set([path[0]])); setActive(0); }
      let arrived = -1;
      if (tt < cycle) {
        const beat = Math.floor(tt / (Dt + Dd)), lt = tt - beat * (Dt + Dd);
        if (lt < Dt) { const p = lt / Dt; place(segIds[beat], 1 - Math.pow(1 - p, 2)); setHot(segIds[beat]); }
        else { place(segIds[beat], 1); arrived = beat + 1; }
      } else { place(segIds[K - 1], 1); arrived = path.length - 1; setHot(''); }
      if (arrived !== -1 && arrived !== lastArrived) {
        lastArrived = arrived;
        const id = path[arrived];
        radiate(id); setActive(arrived);
        setDone(prev => { const s = new Set(prev); s.add(id); return s; });
      }
      raf = requestAnimationFrame(frame);
    };
    raf = requestAnimationFrame(frame);
    return () => { running = false; cancelAnimationFrame(raf); };
  }, [flow, config]);

  const activeId = active >= 0 ? path[active] : null;
  const renderNode = (n) => {
    const isLogo = n.t === 'logo';
    const chipCls = isLogo ? 'wf-chip wf-logo-chip' : n.t === 'decision' ? 'wf-chip wf-dec-chip' : 'wf-chip wf-op-chip';
    const isDone = done.has(n.id), isActive = n.id === activeId;
    return (
      <g key={n.id} className={'wf-node wf-' + n.t + (isActive ? ' is-active' : '') + (isDone ? ' is-done' : '')} transform={`translate(${n.x},${n.y})`}>
        <circle className="wf-ring" ref={setRef(rings, n.id)} r={WF_NR + 3} />
        <rect className={chipCls} x={-WF_NR} y={-WF_NR} width={WF_NR * 2} height={WF_NR * 2} rx={n.t === 'decision' ? 13 : 11} />
        {isLogo
          ? <image href={'/logos/' + n.s + '.svg'} x={-13} y={-13} width={26} height={26} />
          : <g transform="translate(-9,-9) scale(0.75)"><path className="wf-op-glyph" d={WF_GLYPH[n.g]} /></g>}
        <g className="wf-check" transform={`translate(${WF_NR - 3},${-WF_NR + 3})`}>
          <circle r="7.5" />
          <path d="M-3.2 0 L-0.8 2.6 L3.4 -2.6" fill="none" />
        </g>
        <text className={'wf-label' + (isLogo ? '' : ' wf-op-label')} y={WF_NR + 14} textAnchor="middle">{n.l}</text>
      </g>
    );
  };

  const aNode = activeId ? N[activeId] : null;
  return (
    <div className="wf">
      <div className="wf-bar">
        <div className="wf-bar-l"><span className="wf-dot" /><span className="wf-dot" /><span className="wf-dot" />
          <span className="wf-name">{config.name}</span><span className="wf-bar-id">{config.id}</span></div>
        <span className="wf-state"><span className="wf-pip" /> running</span>
      </div>
      <div className="wf-canvas">
        <svg className="wf-svg" viewBox="0 0 580 380" aria-hidden="true">
          <g className="wf-edges">
            {edges.map(e => <path key={e.id} className="wf-edge" d={e.d} ref={setRef(paths, e.id)} />)}
          </g>
          {config.nodes.map(renderNode)}
          {flow && <circle className="wf-packet" r={3.2} ref={packetRef} />}
        </svg>
        {aNode && aNode.m && (
          <div className="wf-callout" style={{
            left: (aNode.x / 580 * 100) + '%', top: (aNode.y / 380 * 100) + '%',
            transform: 'translate(' + (aNode.x < 175 ? '-14px' : aNode.x > 405 ? 'calc(-100% + 14px)' : '-50%') + ', '
              + (aNode.y > 190 ? 'calc(-100% - 28px)' : '46px') + ')',
          }}>
            <span className="wf-callout-step">{aNode.l}</span>{aNode.m}
          </div>
        )}
      </div>
      <div className="wf-statusbar">
        <span><span className="wf-pip sm" /> {done.size >= config.nodes.length ? 'run complete' : 'executing'}</span>
        <span className="wf-stat">{config.stat}</span>
      </div>
    </div>
  );
}

// ── Realistic Excel automation: scan the aging report, flag overdue, zoom in, send reminders ──
const XL_ROWS = [
  { pt: 'Maria Alvarez',  payer: 'Aetna',    mid: 'W2841-557', visit: 'May 31', elig: 'Active',   over: false },
  { pt: 'James Whitfield', payer: 'BCBS TX',  mid: 'XQF-99183', visit: 'May 31', elig: 'Inactive', over: true },
  { pt: 'Priya Nair',     payer: 'UnitedHC',  mid: '9920-31A',  visit: 'Jun 02', elig: 'Active',   over: false },
  { pt: 'Daniel Osei',    payer: 'Cigna',     mid: 'C-774120',  visit: 'Jun 02', elig: 'Termed',   over: true },
  { pt: 'Sofia Romano',   payer: 'Medicare',  mid: '1EG4-TE5',  visit: 'Jun 03', elig: 'Active',   over: false },
  { pt: 'Aaron Klein',    payer: 'Humana',    mid: 'H55-20817', visit: 'Jun 03', elig: 'Lapsed',   over: true },
];
const XL_COLS = ['A', 'B', 'C', 'D', 'E', 'F'];
const XL_HEAD = ['Patient', 'Payer', 'Member ID', 'Visit', 'Eligibility', 'Action'];
const XL_SEQ = [620, 240, 240, 240, 240, 240, 240, 680, 540, 540, 540, 540, 1000, 3000];

function ExcelAuto({ animate = true }) {
  const reduce = typeof window !== 'undefined' && window.matchMedia
    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const END = XL_SEQ.length - 1;
  const [step, setStep] = wfState((animate && !reduce) ? 0 : END);

  wfFx(() => {
    if (!animate || reduce) { setStep(END); return; }
    if (step >= END) { const t = setTimeout(() => setStep(0), XL_SEQ[END]); return () => clearTimeout(t); }
    const t = setTimeout(() => setStep(s => s + 1), XL_SEQ[step]);
    return () => clearTimeout(t);
  }, [step, animate, reduce]);

  const overdueOrder = XL_ROWS.map((r, i) => (r.over ? i : -1)).filter(i => i >= 0);
  const cursorRow = (step >= 1 && step <= 6) ? step - 1 : -1;
  const flagged = (i) => XL_ROWS[i].over && step - 1 >= i;
  const zoomIn = step >= 7 && step <= 11;
  const sentOf = (i) => { const k = overdueOrder.indexOf(i); return k >= 0 && step >= 8 + k; };
  const toast = step >= 12;
  const sentCount = overdueOrder.filter(i => sentOf(i)).length || overdueOrder.length;

  const zoomStyle = { transform: zoomIn ? 'scale(1.44)' : 'scale(1)', transformOrigin: '86% 50%' };

  return (
    <div className="wf wf-xl">
      <div className="wf-bar">
        <div className="wf-bar-l"><span className="wf-dot" /><span className="wf-dot" /><span className="wf-dot" />
          <span className="wf-name">Verify insurance eligibility</span><span className="wf-bar-id">eligibility.xlsx</span></div>
        <span className="wf-state"><span className="wf-pip" /> live</span>
      </div>
      <div className="xl-body">
        <div className="xl-formula">
          <span className="xl-fx">fx</span><span className="xl-ref">F2</span>
          <span className="xl-eq">{'=IF([@Eligibility]<>"Active", "flag front desk", "")'}</span>
        </div>
        <div className="xl-wrap">
          <div className={'xl-zoom' + (zoomIn ? ' zoomed' : '')} style={zoomStyle}>
            <table className="xl-table">
              <thead>
                <tr className="xl-colrow"><th className="xl-corner" />{XL_COLS.map(c => <th key={c} className="xl-colh">{c}</th>)}</tr>
                <tr className="xl-headrow"><td className="xl-rownum">1</td>{XL_HEAD.map(h => <td key={h} className="xl-hcell">{h}</td>)}</tr>
              </thead>
              <tbody>
                {XL_ROWS.map((r, i) => (
                  <tr key={r.mid} className={'xl-row' + (flagged(i) ? ' over' : '')}>
                    <td className="xl-rownum">{i + 2}</td>
                    <td className="xl-cell xl-inv">{r.pt}</td>
                    <td className="xl-cell">{r.payer}</td>
                    <td className="xl-cell xl-mono">{r.mid}</td>
                    <td className="xl-cell">{r.visit}</td>
                    <td className={'xl-cell xl-elig' + (cursorRow === i ? ' xl-sel' : '') + (flagged(i) ? ' xl-bad' : '')}>{r.elig}</td>
                    <td className="xl-cell xl-status">
                      {sentOf(i) ? <span className="xl-sent">Flagged ✓</span>
                        : (r.over && step > 6 ? <span className="xl-pending">checking…</span> : '')}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <div className={'xl-toast' + (toast ? ' in' : '')}>✓ {sentCount} coverage issues caught before visits</div>
        </div>
      </div>
      <div className="wf-statusbar">
        <span><span className="wf-pip sm" /> sync active</span>
        <span className="wf-stat">{overdueOrder.length} of {XL_ROWS.length} need review · front desk alerted</span>
      </div>
    </div>
  );
}

// ── Document intelligence: scan a claim/EOB, lift the facts into structured fields ──
const DI_OUT = [
  { k: 'Patient', v: 'Maria Alvarez' },
  { k: 'Member ID', v: 'W2841-557' },
  { k: 'Service date', v: '2026-05-22' },
  { k: 'Procedures', v: '99214, 99213' },
  { k: 'Billed', v: '$486.00' },
];
const DI_SEQ = [620, 560, 560, 560, 560, 560, 840, 3200];

function DocIntel({ animate = true }) {
  const reduce = typeof window !== 'undefined' && window.matchMedia
    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const END = DI_SEQ.length - 1;
  const [step, setStep] = wfState((animate && !reduce) ? 0 : END);

  wfFx(() => {
    if (!animate || reduce) { setStep(END); return; }
    if (step >= END) { const t = setTimeout(() => setStep(0), DI_SEQ[END]); return () => clearTimeout(t); }
    const t = setTimeout(() => setStep(s => s + 1), DI_SEQ[step]);
    return () => clearTimeout(t);
  }, [step, animate, reduce]);

  const cls = (f) => 'di-field' + (step - 1 === f ? ' active' : '') + (step - 1 >= f ? ' lit' : '');
  const shown = (i) => step >= i + 1;
  const scanPct = Math.min(step, 6) / 6 * 100;

  return (
    <div className="wf wf-di">
      <div className="wf-bar">
        <div className="wf-bar-l"><span className="wf-dot" /><span className="wf-dot" /><span className="wf-dot" />
          <span className="wf-name">Read the document</span><span className="wf-bar-id">claim-eob.pdf</span></div>
        <span className="wf-state"><span className="wf-pip" /> live</span>
      </div>
      <div className="di-body">
        <div className="di-doc">
          <div className="di-paper">
            <div className="di-doc-head"><span className="di-doc-title">EXPLANATION OF BENEFITS</span><span className="di-doc-meta">Aetna · CLM-7741208</span></div>
            <div className={cls(0)}><span className="di-fl">Patient</span><span className="di-fv">Maria Alvarez</span></div>
            <div className={cls(1)}><span className="di-fl">Member ID</span><span className="di-fv">W2841-557</span></div>
            <div className={cls(2)}><span className="di-fl">Date of service</span><span className="di-fv">2026-05-22</span></div>
            <div className={'di-lines' + (step - 1 === 3 ? ' active' : '') + (step - 1 >= 3 ? ' lit' : '')}>
              <div className="di-line di-line-h"><span>CPT</span><span>Description</span><span>Billed</span></div>
              <div className="di-line"><span className="di-cpt">99214</span><span>Office visit, est.</span><span className="di-amt">$286.00</span></div>
              <div className="di-line"><span className="di-cpt">99213</span><span>Office visit, est.</span><span className="di-amt">$200.00</span></div>
            </div>
            <div className={'di-total' + (step - 1 === 4 ? ' active' : '') + (step - 1 >= 4 ? ' lit' : '')}><span>Total billed</span><span className="di-amt">$486.00</span></div>
            <div className="di-shade" style={{ height: scanPct + '%' }} />
            <div className="di-scan" style={{ top: scanPct + '%' }} />
          </div>
        </div>
        <div className="di-out">
          <div className="di-out-head">// extracted</div>
          {DI_OUT.map((o, i) => (
            <div className={'di-card' + (shown(i) ? ' in' : '')} key={o.k}>
              <span className="di-ck">{o.k}</span><span className="di-cv">{o.v}</span><span className="di-tick">✓</span>
            </div>
          ))}
          <div className={'di-insight' + (step >= 6 ? ' in' : '')}>
            <span className="di-flag">⚑ denial risk</span>
            <span className="di-itext">Modifier 25 missing on 99214 — flagged for resubmit</span>
          </div>
        </div>
      </div>
      <div className="wf-statusbar">
        <span><span className="wf-pip sm" /> reading</span>
        <span className="wf-stat">5 fields read · 1 issue caught</span>
      </div>
    </div>
  );
}

// ── Workflow as a modern execution stepper (Vercel/Linear/Stripe-style run view) ──
function WorkflowRun({ config, animate = true }) {
  const reduce = typeof window !== 'undefined' && window.matchMedia
    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const steps = config.steps, N = steps.length;
  const [tick, setTick] = wfState((animate && !reduce) ? 0 : N);

  wfFx(() => {
    if (!animate || reduce) { setTick(N); return; }
    const dwell = tick >= N ? 2000 : 800;
    const t = setTimeout(() => setTick(x => (x >= N ? 0 : x + 1)), dwell);
    return () => clearTimeout(t);
  }, [tick, animate, reduce]);

  const allDone = tick >= N;
  const fillPct = N ? Math.min(tick, N - 0.0) / N * 100 : 0;

  return (
    <div className="wf wr">
      <div className="wf-bar">
        <div className="wf-bar-l"><span className="wf-dot" /><span className="wf-dot" /><span className="wf-dot" />
          <span className="wf-name">{config.name}</span><span className="wf-bar-id">{config.id}</span></div>
        <span className="wf-state"><span className={'wf-pip' + (allDone ? ' done' : '')} /> {allDone ? 'complete' : 'running'}</span>
      </div>
      <div className="wr-body" style={{ '--fill': fillPct + '%' }}>
        {steps.map((s, i) => {
          const st = i < tick ? 'done' : (i === tick && !allDone ? 'run' : 'wait');
          return (
            <div className={'wr-step is-' + st} key={i}>
              <div className="wr-node">
                {st === 'done'
                  ? <svg className="wr-check" viewBox="0 0 16 16"><path d="M3.5 8.4 6.4 11.3 12.5 4.9" /></svg>
                  : st === 'run' ? <span className="wr-spin" /> : <span className="wr-wait" />}
              </div>
              <div className={'wr-tile' + (s.glyph ? ' wr-tile-op' : '')}>
                {s.logo ? <img src={'/logos/' + s.logo + '.svg'} alt="" />
                  : <svg className="wr-glyph" viewBox="0 0 24 24"><path d={WF_GLYPH[s.glyph]} /></svg>}
              </div>
              <div className="wr-text">
                <div className="wr-title">{s.title}</div>
                <div className="wr-desc">{s.desc}</div>
              </div>
              <div className="wr-status">
                {st === 'done' ? 'done' : st === 'run' ? <span className="wr-run">running<i /><i /><i /></span> : 'queued'}
              </div>
            </div>
          );
        })}
      </div>
      <div className="wf-statusbar">
        <span><span className={'wf-pip sm' + (allDone ? ' done' : '')} /> {allDone ? 'run complete' : 'executing'}</span>
        <span className="wf-stat">{allDone ? N + ' steps · done' : 'step ' + Math.min(tick + 1, N) + ' of ' + N}</span>
      </div>
    </div>
  );
}

function WorkflowDeck({ animate = true }) {
  const M = WF_FLOWS.length;
  const [idx, setIdx] = wfState(0);
  const [h, setH] = wfState(0);
  const deckRef = wfRef(null), faces = wfRef([]);
  const acc = wfRef(0), cool = wfRef(false), drag = wfRef(null);

  const go = (dir) => setIdx(i => (i + dir + M) % M);

  // keep the 3D stage as tall as the tallest card (faces are absolutely positioned)
  wfLayout(() => {
    const measure = () => {
      const hs = faces.current.map(f => (f ? f.offsetHeight : 0));
      const mx = Math.max(0, ...hs);
      if (mx) setH(mx);
    };
    measure();
    window.addEventListener('resize', measure);
    return () => window.removeEventListener('resize', measure);
  }, []);

  wfFx(() => {
    const el = deckRef.current;
    if (!el) return;
    const onWheel = (e) => {
      if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;
      e.preventDefault();
      if (cool.current) return;
      acc.current += e.deltaX;
      if (Math.abs(acc.current) > 34) {
        go(acc.current > 0 ? 1 : -1);
        acc.current = 0; cool.current = true;
        setTimeout(() => { cool.current = false; }, 640);
      }
    };
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => el.removeEventListener('wheel', onWheel);
  }, []);

  const onPointerDown = (e) => { drag.current = e.clientX; };
  const onPointerUp = (e) => {
    if (drag.current == null) return;
    const dx = e.clientX - drag.current; drag.current = null;
    if (Math.abs(dx) > 48) go(dx < 0 ? 1 : -1);
  };

  // ring positions: 0 = front (flat), 1 = swung off to the right, 2 = swung off to the left
  const faceStyle = (i) => {
    const off = ((i - idx) % M + M) % M;
    if (off === 0) return { transform: 'translateX(0) translateZ(0) rotateY(0deg) scale(1)', opacity: 1, zIndex: 3, pointerEvents: 'auto' };
    if (off === 1) return { transform: 'translateX(60%) translateZ(-320px) rotateY(-48deg) scale(.9)', opacity: 0, zIndex: 1, pointerEvents: 'none' };
    return { transform: 'translateX(-60%) translateZ(-320px) rotateY(48deg) scale(.9)', opacity: 0, zIndex: 1, pointerEvents: 'none' };
  };

  return (
    <div className="wf-deck" ref={deckRef} onPointerDown={onPointerDown} onPointerUp={onPointerUp}>
      <div className="wf-stage" style={{ height: h ? h + 'px' : undefined }}>
        {WF_FLOWS.map((f, i) => (
          <div className="wf-face" key={f.id} ref={el => { if (el) faces.current[i] = el; }} style={faceStyle(i)}>
            {f.kind === 'excel' ? <ExcelAuto animate={animate && i === idx} />
              : f.kind === 'doc' ? <DocIntel animate={animate && i === idx} />
              : <WorkflowRun config={f} animate={animate && i === idx} />}
          </div>
        ))}
      </div>
      <div className="wf-deck-foot">
        <span className="wf-hint">two-finger swipe to rotate flows</span>
        <div className="wf-dots">
          {WF_FLOWS.map((f, i) => (
            <button key={i} className={'wf-dot-btn' + (i === idx ? ' on' : '')} aria-label={'Flow ' + (i + 1) + ': ' + f.name} onClick={() => setIdx(i)} />
          ))}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { WorkflowCanvas, WorkflowDeck });
