/* Product visuals for ATC product portfolio.
   Exposes window.ProductVisuals — keyed by product id. Each is a compact animated visual.
   Also keeps window.ApexBridgeVisual for backward compat with platform.html. */

const useTick = (interval = 60) => {
  const [tick, setTick] = React.useState(0);
  React.useEffect(() => {
    const id = setInterval(() => setTick(t => (t + 1) % 1000), interval);
    return () => clearInterval(id);
  }, [interval]);
  return tick;
};

const useIsMobile = (breakpoint = 768) => {
  const [isMobile, setIsMobile] = React.useState(
    typeof window !== 'undefined' ? window.innerWidth < breakpoint : false
  );
  React.useEffect(() => {
    const handler = () => setIsMobile(window.innerWidth < breakpoint);
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, [breakpoint]);
  return isMobile;
};

const baseStyles = {
  wrap: { position: 'absolute', inset: 0, padding: 32, display: 'flex', flexDirection: 'column' },
  topbar: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.12em', color: 'var(--text-2)', textTransform: 'uppercase', marginBottom: 24, flexWrap: 'wrap', gap: 6 },
  live: { color: 'var(--accent)' },
  live: { color: 'var(--accent)' },
  body: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' },
};

/* ---------- 1. ApexBridge — pipeline ---------- */
const ApexBridgeVis = () => {
  const tick = useTick(60);
  const isMobile = useIsMobile();
  const dots = Array.from({ length: 8 }, (_, i) => (tick / 100 + i / 8) % 1);
  return (
    <div style={baseStyles.wrap}>
      <div style={baseStyles.topbar} className="viz-topbar">
        <span>// migration pipeline</span>
        <span style={baseStyles.live}>● LIVE</span>
      </div>
      <div style={isMobile ? {
        ...baseStyles.body,
        display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'space-between',
        gap: 12, padding: '4px 0',
      } : {
        ...baseStyles.body,
        display: 'grid', gridTemplateColumns: '1fr auto 1fr auto 1fr', gap: 12,
      }}>
        <Node label="SOURCE" title="Oracle Forms" meta=".fmb / .pll" centered={isMobile} />
        <Flow phases={dots.slice(0, 4)} vertical={isMobile} />
        <Node label="ENGINE" title="AI Translator" meta="parsing PL/SQL..." accent centered={isMobile} />
        <Flow phases={dots.slice(4, 8)} vertical={isMobile} />
        <Node label="TARGET" title="Oracle APEX" meta="cloud-native" centered={isMobile} />
      </div>
    </div>
  );
};

/* ---------- 2. Curio — agentic AI for frontline teams ---------- */
const CurioVis = () => {
  const tick = useTick(70);
  const scenarios = [
    {
      label: 'sales · session #s-8821',
      events: [
        { kind: 'user', text: 'I need 50 units but $42/unit is steep' },
        { kind: 'tool', tool: 'check_inventory', result: '64 units · warehouse 3' },
        { kind: 'tool', tool: 'pricing_engine', result: 'tier discount eligible · -8%' },
        { kind: 'tool', tool: 'crm_lookup', result: 'repeat buyer · LTV $47k' },
        { kind: 'curio', text: 'Approved $38.65/unit. Reserving stock now.' },
        { kind: 'tool', tool: 'reserve_inventory', result: '50 units held · 24h' },
        { kind: 'tool', tool: 'send_quote', result: 'PDF sent · awaiting signature' },
        { kind: 'curio', text: 'Quote sent. Want to lock in delivery?' },
      ],
    },
    {
      label: 'customer service · ticket #cs-4412',
      events: [
        { kind: 'user', text: 'Order #88231 says delivered but I never got it' },
        { kind: 'tool', tool: 'lookup_order', result: '#88231 · marked delivered Apr 24' },
        { kind: 'tool', tool: 'check_carrier_log', result: 'GPS off-route · flagged' },
        { kind: 'tool', tool: 'verify_address', result: 'address confirmed' },
        { kind: 'curio', text: 'Carrier mis-delivery confirmed. Issuing replacement.' },
        { kind: 'tool', tool: 'create_replacement', result: '#88231-R · expedited' },
        { kind: 'tool', tool: 'apply_credit', result: '$15 goodwill · applied' },
        { kind: 'curio', text: 'New order ships today. Credit on your next purchase.' },
      ],
    },
    {
      label: 'internal · work order #wo-2207',
      events: [
        { kind: 'user', text: 'AC on 4th floor east wing is broken again' },
        { kind: 'tool', tool: 'lookup_asset', result: 'HVAC-4E · last repaired Mar 18' },
        { kind: 'tool', tool: 'check_warranty', result: 'covered · vendor: Daikin' },
        { kind: 'tool', tool: 'find_technician', result: 'M. Chen · on-site by 14:00' },
        { kind: 'curio', text: 'Recurring fault — escalating to vendor warranty.' },
        { kind: 'tool', tool: 'open_work_order', result: '#WO-2207 · priority HIGH' },
        { kind: 'tool', tool: 'notify_floor', result: 'Slack #floor-4 · ETA posted' },
        { kind: 'curio', text: 'Tech dispatched. Floor team notified. Tracking it.' },
      ],
    },
  ];

  // Cycle scenarios: each scenario plays through, then advances
  const eventsPerScenario = scenarios[0].events.length + 5; // +5 pause frames between scenarios (~2.8s)
  const totalFrames = scenarios.length * eventsPerScenario;
  const globalFrame = Math.floor(tick / 8) % totalFrames;
  const scenarioIdx = Math.floor(globalFrame / eventsPerScenario);
  const reveal = Math.min(scenarios[scenarioIdx].events.length, globalFrame % eventsPerScenario);
  const scenario = scenarios[scenarioIdx];

  const ToolCall = ({ tool, result, done }) => (
    <div style={{
      display: 'flex', gap: 10, alignItems: 'center',
      padding: '6px 10px',
      background: 'rgba(22,168,184,0.06)',
      border: '1px solid rgba(22,168,184,0.4)',
      borderLeft: '3px solid var(--teal)',
      fontFamily: 'var(--font-mono)', fontSize: 11,
      maxWidth: '85%', alignSelf: 'flex-start',
    }}>
      <span style={{ color: 'var(--teal)', letterSpacing: '0.04em', whiteSpace: 'nowrap' }}>⟶ {tool}()</span>
      <span style={{ color: 'var(--text-2)', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{result}</span>
      <span style={{ color: done ? 'var(--accent)' : 'var(--text-3)', fontSize: 10 }}>{done ? '✓' : '…'}</span>
    </div>
  );

  return (
    <div style={baseStyles.wrap}>
      <div style={baseStyles.topbar} className="viz-topbar">
        <span>// curio · {scenario.label}</span>
        <span style={baseStyles.live}>● ACTING</span>
      </div>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 6, justifyContent: 'flex-end', overflow: 'hidden' }}>
        {scenario.events.slice(0, reveal).map((e, i) => {
          if (e.kind === 'user') {
            return (
              <div key={`${scenarioIdx}-${i}`} style={{
                alignSelf: 'flex-end', background: 'var(--bg-3)',
                border: '1px solid var(--border)', padding: '8px 12px',
                borderRadius: 2, maxWidth: '70%', fontSize: 12,
              }}>{e.text}</div>
            );
          }
          if (e.kind === 'tool') {
            return <ToolCall key={`${scenarioIdx}-${i}`} tool={e.tool} result={e.result} done={i < reveal - 1} />;
          }
          return (
            <div key={`${scenarioIdx}-${i}`} style={{
              alignSelf: 'flex-start',
              background: 'rgba(249,115,22,0.08)',
              border: '1px solid var(--accent)',
              borderLeft: '3px solid var(--accent)',
              padding: '8px 12px', borderRadius: 2, maxWidth: '75%',
              fontSize: 12,
            }}>
              <span style={{ color: 'var(--accent)', marginRight: 8, fontFamily: 'var(--font-mono)', fontSize: 10 }}>◆ CURIO</span>
              {e.text}
            </div>
          );
        })}
        {reveal < scenario.events.length && reveal > 0 && (
          <div style={{ alignSelf: 'flex-start', display: 'flex', gap: 4, padding: '4px 10px' }}>
            {[0, 1, 2].map(i => (
              <span key={i} style={{
                width: 5, height: 5, borderRadius: '50%', background: 'var(--accent)',
                opacity: ((tick / 4 + i) % 3) < 1 ? 1 : 0.25,
              }} />
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

/* ---------- 3. Project Historian ---------- */
const HistorianVis = () => {
  const tick = useTick(60);

  // Graph: center node, then tiers expand outward
  // Layout in a 400×280 viewBox; pre-computed positions for an organic spread.
  const nodes = [
    // tier 0 — root question
    { id: 'q', x: 200, y: 140, label: 'PILLAR B-3', sub: 'why is it here?', tier: 0, big: true },

    // tier 1 — project
    { id: 'p', x: 110, y: 90, label: 'Tower B', sub: 'project · 2007', tier: 1 },

    // tier 2 — subprojects / phases
    { id: 's1', x: 50, y: 50, label: 'Foundation', sub: 'phase 2', tier: 2 },
    { id: 's2', x: 60, y: 130, label: 'Structural', sub: 'phase 3', tier: 2 },

    // tier 3 — issues / decisions
    { id: 'i1', x: 290, y: 50, label: 'ISSUE #214', sub: 'soil shift detected', tier: 3, kind: 'issue' },
    { id: 'i2', x: 320, y: 130, label: 'CHANGE ORDER', sub: 'reinforce SE corner', tier: 3, kind: 'issue' },

    // tier 4 — work orders + people
    { id: 'w1', x: 350, y: 200, label: 'WO-4421', sub: 'extra pillar · spec', tier: 4, kind: 'wo' },
    { id: 'p1', x: 270, y: 230, label: 'M. Rivera', sub: 'engineer · approved', tier: 4, kind: 'person' },
    { id: 'p2', x: 150, y: 240, label: 'L. Tanaka', sub: 'foreman · executed', tier: 4, kind: 'person' },
    { id: 'p3', x: 80, y: 210, label: 'BuildCo Ltd', sub: 'vendor · 2007', tier: 4, kind: 'person' },
  ];

  const edges = [
    ['q', 'p'], ['p', 's1'], ['p', 's2'],
    ['q', 'i1'], ['q', 'i2'],
    ['i1', 'w1'], ['i2', 'w1'],
    ['w1', 'p1'], ['w1', 'p2'], ['s1', 'p3'],
  ];

  // Reveal across ~50 frames, then hold fully-revealed for ~30 frames before restart.
  const revealEnd = 50;
  const pauseFrames = 30;
  const totalFrames = revealEnd + pauseFrames;
  const raw = tick % totalFrames;
  const frame = Math.min(raw, revealEnd); // clamp at fully-revealed during pause
  const tierReveal = (t) => {
    const thresholds = [0, 6, 14, 24, 34, 44];
    return frame >= thresholds[t];
  };

  const nodeById = Object.fromEntries(nodes.map(n => [n.id, n]));

  const colorFor = (kind) => {
    if (kind === 'issue') return 'var(--accent)';
    if (kind === 'wo') return 'var(--teal)';
    if (kind === 'person') return '#fb923c';
    return 'var(--text-2)';
  };

  return (
    <div style={baseStyles.wrap}>
      <div style={baseStyles.topbar} className="viz-topbar">
        <span>// historian · knowledge graph · trace #q-7821</span>
        <span style={baseStyles.live}>● TRACING</span>
      </div>
      <div style={{ flex: 1, position: 'relative' }}>
        <svg viewBox="0 0 400 280" style={{ width: '100%', height: '100%', overflow: 'visible' }}>
          {/* Edges */}
          {edges.map(([a, b], i) => {
            const na = nodeById[a], nb = nodeById[b];
            const visible = tierReveal(Math.max(na.tier, nb.tier));
            return (
              <line key={i}
                x1={na.x} y1={na.y} x2={nb.x} y2={nb.y}
                stroke={visible ? 'rgba(249,115,22,0.4)' : 'rgba(255,255,255,0.04)'}
                strokeWidth="1"
                strokeDasharray={visible ? '0' : '2,3'}
                style={{ transition: 'stroke 0.4s' }}
              />
            );
          })}
          {/* Nodes */}
          {nodes.map((n) => {
            const visible = tierReveal(n.tier);
            const r = n.big ? 18 : 7;
            const c = n.big ? 'var(--accent)' : colorFor(n.kind);
            return (
              <g key={n.id} style={{ opacity: visible ? 1 : 0, transition: 'opacity 0.5s', transformOrigin: `${n.x}px ${n.y}px`, transform: visible ? 'scale(1)' : 'scale(0.3)' }}>
                {n.big && (
                  <circle cx={n.x} cy={n.y} r={r + 8}
                    fill="none" stroke="var(--accent)" strokeOpacity="0.3"
                    strokeWidth="1" />
                )}
                <circle cx={n.x} cy={n.y} r={r}
                  fill={n.big ? 'var(--bg)' : c}
                  stroke={c} strokeWidth={n.big ? '2' : '0'}
                />
                {n.big && (
                  <text x={n.x} y={n.y + 4} textAnchor="middle"
                    fontFamily="var(--font-mono)" fontSize="9"
                    fill="var(--accent)" fontWeight="700">?</text>
                )}
              </g>
            );
          })}
        </svg>

        {/* Labels overlay (HTML for crisper text) */}
        {nodes.map((n) => {
          const visible = tierReveal(n.tier);
          const r = n.big ? 18 : 7;
          const labelOffset = n.big ? r + 16 : r + 8;
          // Position label to side based on x to avoid overlap
          const labelLeft = n.x > 200;
          return (
            <div key={n.id} style={{
              position: 'absolute',
              left: `${(n.x / 400) * 100}%`,
              top: `${(n.y / 280) * 100}%`,
              transform: n.big
                ? `translate(-50%, ${labelOffset}px)`
                : labelLeft
                  ? `translate(${labelOffset / 4}px, -50%)`
                  : `translate(calc(-100% - ${labelOffset / 4}px), -50%)`,
              opacity: visible ? 1 : 0,
              transition: 'opacity 0.5s 0.1s',
              fontFamily: 'var(--font-mono)',
              pointerEvents: 'none',
              textAlign: n.big ? 'center' : (labelLeft ? 'left' : 'right'),
              whiteSpace: 'nowrap',
            }}>
              <div style={{
                fontSize: n.big ? 10 : 9,
                fontWeight: 700,
                letterSpacing: '0.06em',
                color: n.big ? 'var(--accent)' : (n.kind === 'issue' ? 'var(--accent)' : n.kind === 'wo' ? 'var(--teal)' : 'var(--text)'),
                textTransform: 'uppercase',
              }}>{n.label}</div>
              <div style={{ fontSize: 9, color: 'var(--text-2)', marginTop: 1 }}>{n.sub}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
};

/* ---------- 4. Core Bridge — super app of micro-apps on Oracle APEX + OCI ---------- */
const CoreBridgeVis = () => {
  const tick = useTick(70);
  const apps = [
    { name: 'HR Pulse', dept: 'HR', glyph: '◐' },
    { name: 'Spend AI', dept: 'FINANCE', glyph: '◑' },
    { name: 'Field Ops', dept: 'OPS', glyph: '◒' },
    { name: 'Sales Coach', dept: 'SALES', glyph: '◓' },
    { name: 'Compliance', dept: 'LEGAL', glyph: '◔' },
    { name: 'Asset Mgmt', dept: 'PLANT', glyph: '◕' },
  ];
  const activeIdx = Math.floor(tick / 14) % apps.length;
  return (
    <div style={baseStyles.wrap}>
      <div style={baseStyles.topbar} className="viz-topbar">
        <span>// core_bridge · 1 super app · {apps.length} micro-apps</span>
        <span style={baseStyles.live}>● PRIVATE OCI</span>
      </div>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 10, padding: '4px 0', minHeight: 0 }}>
        {/* Super-app shell containing the grid */}
        <div style={{
          flex: 1, position: 'relative',
          border: '1px solid var(--border)',
          borderRadius: 4,
          background: 'linear-gradient(180deg, var(--bg-3) 0%, var(--bg-2) 100%)',
          padding: 14,
          boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.02), 0 0 24px rgba(249,115,22,0.05)',
        }}>
          {/* Shell header */}
          <div style={{
            display: 'flex', justifyContent: 'space-between', alignItems: 'center',
            marginBottom: 12, paddingBottom: 8, borderBottom: '1px solid var(--border)',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <div style={{ width: 16, height: 16, border: '1.5px solid var(--accent)', borderRadius: 3, position: 'relative' }}>
                <div style={{ position: 'absolute', inset: 3, background: 'var(--accent)', borderRadius: 1 }} />
              </div>
              <span style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 13, letterSpacing: '0.04em' }}>
                CORE_BRIDGE
              </span>
            </div>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 9, letterSpacing: '0.1em', color: 'var(--text-3)' }}>
              SSO · BIOMETRIC
            </span>
          </div>
          {/* Micro-app grid */}
          <div style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(3, 1fr)',
            gridTemplateRows: 'repeat(2, 1fr)',
            gap: 8,
            height: 'calc(100% - 38px)',
          }}>
            {apps.map((a, i) => {
              const active = activeIdx === i;
              return (
                <div key={a.name} style={{
                  background: active ? 'rgba(249,115,22,0.08)' : 'var(--bg)',
                  border: `1px solid ${active ? 'var(--accent)' : 'var(--border)'}`,
                  borderRadius: 3, padding: 10,
                  display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
                  transition: 'all 0.3s',
                  position: 'relative',
                  boxShadow: active ? '0 0 16px rgba(249,115,22,0.15)' : 'none',
                }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
                    <span style={{
                      fontSize: 18, color: active ? 'var(--accent)' : 'var(--text-3)',
                      transition: 'color 0.3s',
                    }}>{a.glyph}</span>
                    <span style={{
                      fontFamily: 'var(--font-mono)', fontSize: 7, letterSpacing: '0.12em',
                      color: active ? 'var(--accent)' : 'var(--text-3)',
                      padding: '2px 5px', border: `1px solid ${active ? 'var(--accent)' : 'var(--border)'}`,
                      borderRadius: 1,
                    }}>{a.dept}</span>
                  </div>
                  <div>
                    <div style={{
                      fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 11,
                      color: 'var(--text)', textTransform: 'uppercase', letterSpacing: '0.02em',
                    }}>{a.name}</div>
                    <div style={{
                      fontFamily: 'var(--font-mono)', fontSize: 8, color: 'var(--text-2)',
                      marginTop: 2,
                    }}>{active ? 'ai · running' : 'micro-app'}</div>
                  </div>
                  {active && (
                    <span style={{
                      position: 'absolute', top: 6, right: 6,
                      width: 5, height: 5, borderRadius: '50%',
                      background: 'var(--accent)',
                      boxShadow: '0 0 8px var(--accent)',
                    }} />
                  )}
                </div>
              );
            })}
          </div>
        </div>

        {/* Foundation layer: Oracle APEX + OCI */}
        <div style={{
          display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, flexShrink: 0,
        }}>
          {[
            { label: 'ORACLE APEX', sub: 'low-code' },
            { label: 'ORACLE CLOUD', sub: 'OCI · private' },
          ].map((f) => (
            <div key={f.label} style={{
              border: '1px solid var(--border)',
              borderTop: '2px solid var(--accent)',
              padding: '8px 12px',
              background: 'var(--bg-3)',
              display: 'flex', justifyContent: 'space-between', alignItems: 'center',
            }}>
              <span style={{
                fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 11,
                letterSpacing: '0.06em', color: 'var(--text)',
              }}>{f.label}</span>
              <span style={{
                fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--text-2)',
                letterSpacing: '0.08em',
              }}>{f.sub}</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

/* ---------- 5. Hyperassist — AI tools hub ---------- */
const HyperassistVis = () => {
  const tick = useTick(70);
  const tools = ['Summarize', 'Translate', 'Extract', 'Classify', 'Generate', 'Search'];
  const activeIdx = Math.floor(tick / 10) % tools.length;
  const VB = 320; // viewBox size, square; centered
  const cx = VB / 2, cy = VB / 2;
  const r = 110;
  return (
    <div style={baseStyles.wrap}>
      <div style={baseStyles.topbar} className="viz-topbar">
        <span>// hyperassist · tool routing</span>
        <span style={baseStyles.live}>● 24 TOOLS</span>
      </div>
      <div style={{ flex: 1, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <div style={{ position: 'relative', width: VB, height: VB }}>
          {/* Connector lines drawn in a single SVG */}
          <svg viewBox={`0 0 ${VB} ${VB}`} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
            {tools.map((t, i) => {
              const angle = (i / tools.length) * Math.PI * 2 - Math.PI / 2;
              const x = cx + Math.cos(angle) * r;
              const y = cy + Math.sin(angle) * r;
              const active = activeIdx === i;
              return (
                <line key={t}
                  x1={cx} y1={cy} x2={x} y2={y}
                  stroke={active ? 'var(--accent)' : 'rgba(255,255,255,0.06)'}
                  strokeWidth="1"
                  strokeDasharray={active ? '0' : '2,4'}
                />
              );
            })}
          </svg>
          {/* Hub */}
          <div style={{
            position: 'absolute', left: cx, top: cy,
            transform: 'translate(-50%, -50%)',
            width: 64, height: 64, borderRadius: '50%',
            border: '2px solid var(--accent)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            background: 'var(--bg-3)', zIndex: 2,
            fontFamily: 'var(--font-display)', fontWeight: 700, color: 'var(--accent)',
            boxShadow: '0 0 24px rgba(249,115,22,0.3)',
          }}>HUB</div>
          {/* Tool chips */}
          {tools.map((t, i) => {
            const angle = (i / tools.length) * Math.PI * 2 - Math.PI / 2;
            const x = cx + Math.cos(angle) * r;
            const y = cy + Math.sin(angle) * r;
            const active = activeIdx === i;
            return (
              <div key={t} style={{
                position: 'absolute', left: x, top: y,
                transform: 'translate(-50%, -50%)',
                background: active ? 'rgba(249,115,22,0.1)' : 'var(--bg-3)',
                border: `1px solid ${active ? 'var(--accent)' : 'var(--border)'}`,
                padding: '6px 12px', fontSize: 11,
                fontFamily: 'var(--font-mono)', color: active ? 'var(--accent)' : 'var(--text-2)',
                whiteSpace: 'nowrap', textTransform: 'uppercase', letterSpacing: '0.08em',
                transition: 'all 0.2s', zIndex: 2,
              }}>{t}</div>
            );
          })}
        </div>
      </div>
    </div>
  );
};

/* ---------- 6. Certiflow — onboarding ---------- */
const CertiflowVis = () => {
  const tick = useTick(80);
  const steps = ['Identity', 'Documents', 'Compliance', 'KYC Check', 'Approved'];
  const active = Math.floor(tick / 10) % (steps.length + 2);
  return (
    <div style={baseStyles.wrap}>
      <div style={baseStyles.topbar} className="viz-topbar">
        <span>// certiflow · onboarding · case #4821</span>
        <span style={baseStyles.live}>● PROCESSING</span>
      </div>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 12, justifyContent: 'center' }}>
        {steps.map((s, i) => {
          const done = i < active;
          const current = i === active;
          return (
            <div key={s} style={{
              display: 'flex', alignItems: 'center', gap: 16,
              padding: '12px 16px',
              background: current ? 'rgba(249,115,22,0.06)' : 'transparent',
              border: `1px solid ${current ? 'var(--accent)' : 'var(--border)'}`,
              borderRadius: 2,
              opacity: i > active ? 0.4 : 1,
              transition: 'all 0.3s',
            }}>
              <div style={{
                width: 24, height: 24, borderRadius: '50%',
                background: done ? 'var(--accent)' : 'transparent',
                border: `2px solid ${done || current ? 'var(--accent)' : 'var(--border)'}`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                fontSize: 12, color: done ? 'var(--bg)' : 'var(--accent)',
                fontWeight: 700,
              }}>{done ? '✓' : i + 1}</div>
              <div style={{ flex: 1, fontFamily: 'var(--font-display)', fontWeight: 600, textTransform: 'uppercase', fontSize: 14, letterSpacing: '0.02em' }}>{s}</div>
              {current && (
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--accent)', letterSpacing: '0.1em' }}>RUNNING…</span>
              )}
              {done && (
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-3)', letterSpacing: '0.1em' }}>OK</span>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};

/* ---------- 7. DocReference — document intelligence ---------- */
const DocReferenceVis = () => {
  const tick = useTick(70);
  const scenarios = [
    {
      label: 'contract.pdf',
      lines: [
        { txt: 'Section 4.2 — Liability Cap', tag: 'CLAUSE' },
        { txt: '"...not exceed twelve (12) months..."', tag: 'EXTRACT', highlight: true },
        { txt: 'Cross-ref: Master Agreement §8.1', tag: 'LINK' },
        { txt: 'Conflict detected: Schedule B', tag: 'FLAG', warn: true },
        { txt: 'Confidence: 0.94', tag: 'META' },
      ],
    },
    {
      label: 'bank-statement.pdf',
      lines: [
        { txt: 'Account: ****-2218 · Q2 2024', tag: 'HEADER' },
        { txt: '$184,520.40 inflow · 47 transactions', tag: 'EXTRACT', highlight: true },
        { txt: 'Cross-ref: Tax filing FY-23', tag: 'LINK' },
        { txt: 'Anomaly: 3 round-number wires', tag: 'FLAG', warn: true },
        { txt: 'Confidence: 0.97', tag: 'META' },
      ],
    },
    {
      label: 'intel-report.pdf',
      lines: [
        { txt: 'Subject: Asset · code-name MERIDIAN', tag: 'ENTITY' },
        { txt: 'Verified location · Sector 7-B', tag: 'EXTRACT', highlight: true },
        { txt: 'Cross-ref: Report #IR-9942', tag: 'LINK' },
        { txt: 'Source contradicts report #IR-9881', tag: 'FLAG', warn: true },
        { txt: 'Confidence: 0.89', tag: 'META' },
      ],
    },
  ];

  // 5 lines per scenario, then ~2s pause before next.
  // tick increments every 70ms. We advance globalFrame every 4 ticks (~280ms per line).
  // After all lines reveal, hold for `pauseFrames` global frames before cycling.
  const linesPerScenario = scenarios[0].lines.length;
  const pauseFrames = 7; // ~2s pause (7 * 280ms ≈ 2s)
  const cycleFrames = linesPerScenario + pauseFrames;
  const totalFrames = scenarios.length * cycleFrames;
  const globalFrame = Math.floor(tick / 4) % totalFrames;
  const scenarioIdx = Math.floor(globalFrame / cycleFrames);
  const reveal = Math.min(linesPerScenario, globalFrame % cycleFrames);
  const scenario = scenarios[scenarioIdx];

  return (
    <div style={baseStyles.wrap}>
      <div style={baseStyles.topbar} className="viz-topbar">
        <span>// docreference · {scenario.label}</span>
        <span style={baseStyles.live}>● PARSING</span>
      </div>
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 10, justifyContent: 'center' }}>
        {scenario.lines.slice(0, reveal).map((l, i) => (
          <div key={`${scenarioIdx}-${i}`} style={{
            display: 'flex', gap: 12, alignItems: 'center', padding: '8px 12px',
            background: l.highlight ? 'rgba(249,115,22,0.06)' : (l.warn ? 'rgba(239,68,68,0.06)' : 'var(--bg-3)'),
            border: `1px solid ${l.highlight ? 'var(--accent)' : (l.warn ? '#ef4444' : 'var(--border)')}`,
            borderLeft: `3px solid ${l.highlight ? 'var(--accent)' : (l.warn ? '#ef4444' : 'var(--text-3)')}`,
            opacity: 0, animation: 'fadeIn 0.3s forwards',
          }}>
            <span style={{
              fontFamily: 'var(--font-mono)', fontSize: 9, letterSpacing: '0.1em',
              color: l.warn ? '#ef4444' : (l.highlight ? 'var(--accent)' : 'var(--text-3)'),
              minWidth: 56,
            }}>{l.tag}</span>
            <span style={{ fontSize: 12, color: 'var(--text)', fontFamily: l.highlight ? 'Georgia, serif' : 'var(--font-body)' }}>{l.txt}</span>
          </div>
        ))}
      </div>
    </div>
  );
};

/* ---------- 8. Insightscore — assessments ---------- */
const InsightscoreVis = () => {
  const tick = useTick(70);
  const factors = [
    { name: 'Technical Skills', val: 0.88, w: 0.30 },
    { name: 'Communication', val: 0.74, w: 0.20 },
    { name: 'Problem Solving', val: 0.91, w: 0.25 },
    { name: 'Domain Knowledge', val: 0.69, w: 0.15 },
    { name: 'Cultural Fit', val: 0.82, w: 0.10 },
  ];
  // Sources scanned in phase 1
  const sources = [
    { kind: 'CV', txt: 'resume.pdf · 4 pages' },
    { kind: 'CHAT', txt: 'screening transcript · 41 turns' },
    { kind: 'CODE', txt: 'github · 12 repos · 247 commits' },
    { kind: 'REF', txt: 'reference call #r-1 · 18 min' },
    { kind: 'TEST', txt: 'tech assessment · 6 problems' },
    { kind: 'CHAT', txt: 'panel interview · 67 turns' },
  ];

  // Two phases: SCAN (analyzing) then SCORE (chart reveal). Loop.
  const scanFrames = sources.length + 4;   // reveal each source + brief hold
  const scoreFrames = factors.length + 8;  // reveal bars + hold the chart
  const totalFrames = scanFrames + scoreFrames;
  const globalFrame = Math.floor(tick / 4) % totalFrames;
  const phase = globalFrame < scanFrames ? 'scan' : 'score';
  const scanReveal = Math.min(sources.length, globalFrame);
  const scoreReveal = phase === 'score' ? Math.min(factors.length, globalFrame - scanFrames) : 0;

  return (
    <div style={baseStyles.wrap}>
      <div style={baseStyles.topbar} className="viz-topbar">
        <span>// insightscore · candidate #c-2241</span>
        <span style={baseStyles.live}>● {phase === 'scan' ? 'ANALYZING' : 'SCORED'}</span>
      </div>

      {phase === 'scan' ? (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 6, justifyContent: 'center', minHeight: 0, overflow: 'hidden' }}>
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-2)', letterSpacing: '0.08em', marginBottom: 4 }}>
            INGESTING SOURCES · {scanReveal}/{sources.length}
          </div>
          {sources.slice(0, scanReveal).map((s, i) => {
            const isCurrent = i === scanReveal - 1;
            return (
              <div key={i} style={{
                display: 'flex', gap: 12, alignItems: 'center',
                padding: '6px 10px',
                background: isCurrent ? 'rgba(249,115,22,0.06)' : 'transparent',
                border: `1px solid ${isCurrent ? 'var(--accent)' : 'var(--border)'}`,
                borderLeft: `3px solid ${isCurrent ? 'var(--accent)' : 'var(--text-3)'}`,
                opacity: 0, animation: 'fadeIn 0.25s forwards',
              }}>
                <span style={{
                  fontFamily: 'var(--font-mono)', fontSize: 9, letterSpacing: '0.1em',
                  color: isCurrent ? 'var(--accent)' : 'var(--text-3)',
                  minWidth: 38,
                }}>{s.kind}</span>
                <span style={{ flex: 1, fontSize: 12, color: 'var(--text)' }}>{s.txt}</span>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: isCurrent ? 'var(--accent)' : 'var(--text-3)' }}>
                  {isCurrent ? '…' : '✓'}
                </span>
              </div>
            );
          })}
          {/* progress bar */}
          <div style={{ height: 2, background: 'rgba(255,255,255,0.06)', marginTop: 12, position: 'relative' }}>
            <div style={{
              position: 'absolute', left: 0, top: 0, height: '100%',
              width: `${(scanReveal / sources.length) * 100}%`,
              background: 'var(--accent)', transition: 'width 0.3s',
            }} />
          </div>
        </div>
      ) : (
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 8, justifyContent: 'center', minHeight: 0, overflow: 'hidden' }}>
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-2)', letterSpacing: '0.08em', marginBottom: 4 }}>
            EVIDENCE-BASED SCORE · 6 SOURCES
          </div>
          {factors.map((f, i) => (
            <div key={f.name} style={{ opacity: i < scoreReveal ? 1 : 0.15, transition: 'opacity 0.3s' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 3 }}>
                <span style={{ fontSize: 12, color: 'var(--text)' }}>{f.name}</span>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--accent)' }}>{(f.val * 100).toFixed(0)} · w{(f.w * 100).toFixed(0)}%</span>
              </div>
              <div style={{ height: 4, background: 'rgba(255,255,255,0.06)', borderRadius: 2, overflow: 'hidden' }}>
                <div style={{ height: '100%', width: i < scoreReveal ? `${f.val * 100}%` : '0%', background: 'var(--accent)', transition: 'width 0.6s ease' }} />
              </div>
            </div>
          ))}
          <div style={{
            marginTop: 8, padding: '10px 14px', borderTop: '1px solid var(--border)',
            display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
            opacity: scoreReveal >= factors.length ? 1 : 0,
            transition: 'opacity 0.5s 0.3s',
          }}>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.1em', color: 'var(--text-2)' }}>WEIGHTED SCORE</span>
            <span style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 28, color: 'var(--accent)' }}>82</span>
          </div>
        </div>
      )}
    </div>
  );
};

/* ---------- shared sub-components ---------- */
const Node = ({ label, title, meta, accent, centered }) => (
  <div style={{
    background: 'var(--bg-3)',
    border: `1px solid ${accent ? 'var(--accent)' : 'var(--border)'}`,
    boxShadow: accent ? '0 0 24px rgba(249,115,22,0.15)' : 'none',
    padding: 14, borderRadius: 2, minHeight: 100,
    minWidth: centered ? 220 : undefined,
    textAlign: centered ? 'center' : 'left',
  }}>
    <div style={{ fontFamily: 'var(--font-mono)', fontSize: 9, letterSpacing: '0.12em', color: accent ? 'var(--accent)' : 'var(--text-3)', textTransform: 'uppercase', marginBottom: 6 }}>{label}</div>
    <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 16, textTransform: 'uppercase', marginBottom: 4 }}>{title}</div>
    <div style={{ fontSize: 11, color: 'var(--text-2)' }}>{meta}</div>
  </div>
);
const Flow = ({ phases, vertical }) => (
  <div style={vertical
    ? { position: 'relative', height: 32, width: 2, background: 'var(--border)', alignSelf: 'center' }
    : { position: 'relative', width: 40, height: 2, background: 'var(--border)' }
  }>
    {phases.map((p, i) => (
      <div key={i} style={{
        position: 'absolute',
        ...(vertical
          ? { top: `${p * 100}%`, left: '50%' }
          : { left: `${p * 100}%`, top: '50%' }
        ),
        transform: 'translate(-50%,-50%)',
        width: 5, height: 5, borderRadius: '50%',
        background: 'var(--accent)',
        opacity: p < 0.95 ? 1 - Math.abs(p - 0.5) * 1.2 : 0,
        boxShadow: '0 0 8px var(--accent)',
      }} />
    ))}
  </div>
);

window.ProductVisuals = {
  apexbridge: ApexBridgeVis,
  curio: CurioVis,
  historian: HistorianVis,
  corebridge: CoreBridgeVis,
  hyperassist: HyperassistVis,
  certiflow: CertiflowVis,
  docreference: DocReferenceVis,
  insightscore: InsightscoreVis,
};

/* Backward compat for platform.html (expects ApexBridgeVisual) */
window.ApexBridgeVisual = ({ variant }) => <ApexBridgeVis />;
