// Shared React building blocks for the interactive pitch deck.
// Exposed to window so each slide's babel script can grab them.

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

// ─────────────────────────────────────────────────────────────────────
// Small UI primitives
// ─────────────────────────────────────────────────────────────────────

const C = {
  navy:   '#000099',
  orange: '#ff663a',
  purple: '#7f3399',
  ink:    '#111827',
  muted:  '#565658',
  muted2: '#8a8a8f',
  bg:     '#ffffff',
  bgSoft: '#f7f7fb',
  hair:   '#e4e4ea',
  hair2:  '#d3d3da',
  sea:    '#1e6dff',
  seaBg:  '#e8f0ff',
  moss:   '#1e7a52',
  mossBg: '#e1f3ea',
  amber:  '#c97a00',
  amberBg:'#fff3d9',
  ember:  '#c63a3a',
  emberBg:'#fde7e7',
};

// "JetBrains Mono" stack
const monoFont = '"JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace';

function StatusPill({ state, label }) {
  // state: 'queued' | 'running' | 'done' | 'error'
  const styles = {
    queued:  { bg: '#f0f0f5', fg: C.muted2, label: 'Queued',  dot: C.muted2, anim: false },
    running: { bg: C.seaBg,   fg: C.sea,    label: 'Running', dot: C.sea,    anim: true  },
    done:    { bg: C.mossBg,  fg: C.moss,   label: 'Done',    dot: C.moss,   anim: false },
    error:   { bg: C.emberBg, fg: C.ember,  label: 'Failed',  dot: C.ember,  anim: false },
  };
  const s = styles[state] || styles.queued;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 8,
      padding: '4px 12px 4px 10px',
      borderRadius: 999,
      background: s.bg, color: s.fg,
      fontSize: 16, fontWeight: 600,
      letterSpacing: 0.01,
      fontFamily: 'Hanken Grotesk, sans-serif',
    }}>
      <span style={{
        width: 8, height: 8, borderRadius: '50%',
        background: s.dot,
        animation: s.anim ? 'pulse-dot 1.2s ease-in-out infinite' : 'none',
      }} />
      {label || s.label}
    </span>
  );
}

function ToolBadge({ kind = 'tool', children }) {
  const colors = {
    tool:   { bg: '#eef0f6', fg: C.ink,   bd: C.hair },
    angle:  { bg: '#fff1ec', fg: C.orange, bd: '#ffd9c9' },
    sql:    { bg: '#eef5ff', fg: C.sea,   bd: '#cfdfff' },
    answer: { bg: '#f3eef7', fg: C.purple, bd: '#e0d2ea' },
  };
  const c = colors[kind] || colors.tool;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      padding: '4px 10px',
      borderRadius: 6,
      background: c.bg, color: c.fg,
      border: `1px solid ${c.bd}`,
      fontFamily: monoFont,
      fontSize: 14, fontWeight: 500,
      letterSpacing: -0.005,
    }}>
      {children}
    </span>
  );
}

function CitationChip({ n, active, onClick }) {
  return (
    <button
      onClick={onClick}
      style={{
        display: 'inline-flex',
        alignItems: 'center', justifyContent: 'center',
        minWidth: 28, height: 24,
        padding: '0 7px',
        border: `1px solid ${active ? C.sea : '#cfdfff'}`,
        background: active ? C.sea : C.seaBg,
        color: active ? '#fff' : C.sea,
        borderRadius: 6,
        fontFamily: monoFont,
        fontSize: 14, fontWeight: 600,
        cursor: 'pointer',
        margin: '0 2px',
        transition: 'all 120ms ease',
        verticalAlign: 'baseline',
        transform: 'translateY(-1px)',
      }}
    >
      [{n}]
    </button>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Tool-call card (used by slides 1 and 4)
// ─────────────────────────────────────────────────────────────────────

// ─────────────────────────────────────────────────────────────────────
// Tool-call card (matches the actual product UI: round status icon,
// tool name, sub-line, "View" pill on the right, chevron)
// ─────────────────────────────────────────────────────────────────────

function ToolCallCard({ name, sub, state, viewLabel = 'View', model }) {
  const isError = state === 'error';
  const isRunning = state === 'running';
  const isDone = state === 'done';

  const circleBg = isError ? C.ember : isDone ? C.moss : isRunning ? C.sea : '#d3d3da';
  const subColor = isError ? C.ember : C.muted;
  const pillBg   = isError ? C.emberBg : isDone ? C.mossBg : isRunning ? C.seaBg : '#f0f0f5';
  const pillFg   = isError ? C.ember : isDone ? C.moss : isRunning ? C.sea : C.muted2;

  return (
    <div style={{
      display: 'flex', alignItems: 'flex-start', gap: 14,
      padding: '12px 14px 12px 12px',
      background: '#fff',
      border: `1px solid ${isError ? '#f3c8c8' : C.hair}`,
      borderRadius: 12,
      boxShadow: isRunning ? `0 0 0 3px ${C.seaBg}` : 'none',
    }}>
      {/* status circle */}
      <span style={{
        width: 28, height: 28, borderRadius: '50%',
        background: circleBg,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        flexShrink: 0,
        marginTop: 1,
        boxShadow: isError ? 'inset 0 0 0 1px rgba(0,0,0,0.05)' : 'none',
      }}>
        {isError ? (
          <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg>
        ) : isRunning ? (
          <span style={{
            width: 10, height: 10, borderRadius: '50%',
            background: '#fff',
            animation: 'pulse-dot 0.9s ease-in-out infinite',
          }} />
        ) : isDone ? (
          <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8.5l3 3 7-7"/></svg>
        ) : null}
      </span>

      {/* name + sub */}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontFamily: 'Hanken Grotesk, sans-serif',
          fontSize: 18, fontWeight: 600,
          color: C.ink,
          lineHeight: 1.2,
          letterSpacing: -0.005,
          wordBreak: 'break-word',
        }}>{name}</div>
        {sub && (
          <div style={{
            fontFamily: 'Hanken Grotesk, sans-serif',
            fontSize: 15,
            color: subColor,
            lineHeight: 1.3,
            marginTop: 3,
            fontWeight: isError ? 500 : 400,
          }}>{sub}</div>
        )}
      </div>

      {/* Model routing chip + View pill + chevron */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
        {model && !isError && (
          <span style={{
            padding: '4px 10px',
            background: '#f3eef7',
            color: C.purple,
            border: `1px solid #e0d2ea`,
            borderRadius: 7,
            fontFamily: monoFont,
            fontSize: 12.5, fontWeight: 600,
            letterSpacing: -0.01,
            whiteSpace: 'nowrap',
          }}>{model}</span>
        )}
        <span style={{
          padding: '5px 14px',
          background: pillBg,
          color: pillFg,
          borderRadius: 8,
          fontFamily: 'Hanken Grotesk, sans-serif',
          fontSize: 14, fontWeight: 600,
          letterSpacing: -0.005,
        }}>{isRunning ? 'Running' : viewLabel}</span>
        <svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke={C.muted2} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 6l4 4 4-4"/></svg>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Chat bubble — user (blue, right) or assistant (white-ish, left)
// ─────────────────────────────────────────────────────────────────────

function ChatBubble({ side = 'user', children, style }) {
  if (side === 'user') {
    return (
      <div style={{
        alignSelf: 'flex-end',
        maxWidth: '90%',
        padding: '12px 20px',
        background: C.sea,
        color: '#fff',
        borderRadius: '20px 20px 4px 20px',
        fontSize: 20,
        lineHeight: 1.35,
        fontWeight: 500,
        boxShadow: '0 2px 8px rgba(30,109,255,0.18)',
        ...style,
      }}>{children}</div>
    );
  }
  return (
    <div style={{
      alignSelf: 'stretch',
      padding: '4px 0',
      fontSize: 18,
      lineHeight: 1.45,
      color: C.ink,
      ...style,
    }}>{children}</div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// App chrome — chat panel & overview sidebar mimicking the real UI
// ─────────────────────────────────────────────────────────────────────

function AppChatPanel({ title, dataset, children }) {
  return (
    <div style={{
      background: '#fff',
      border: `1px solid ${C.hair}`,
      borderRadius: 14,
      display: 'flex', flexDirection: 'column',
      minHeight: 0,
      overflow: 'hidden',
    }}>
      {/* Header */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 14,
        padding: '14px 22px',
        borderBottom: `1px solid ${C.hair}`,
        flexShrink: 0,
      }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 18, fontWeight: 600, color: C.ink, letterSpacing: -0.005 }}>
            {title}
            {dataset && (
              <span style={{ marginLeft: 12, fontSize: 15, color: C.muted2, fontWeight: 500 }}>
                {dataset}
              </span>
            )}
          </div>
        </div>
        <span style={{
          display: 'inline-flex', alignItems: 'center', gap: 8,
          fontFamily: monoFont, fontSize: 13, fontWeight: 600,
          letterSpacing: 0.4, color: C.muted2,
        }}>
          <span style={{
            width: 8, height: 8, borderRadius: '50%', background: C.sea,
            animation: 'pulse-dot 1.2s ease-in-out infinite',
          }} />
          Studio · live agent
        </span>
      </div>
      <div style={{
        flex: 1, minHeight: 0,
        padding: '18px 22px',
        display: 'flex', flexDirection: 'column', gap: 12,
        overflow: 'hidden',
      }}>
        {children}
      </div>
    </div>
  );
}

function HeaderPill({ icon, label }) {
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      padding: '5px 12px',
      border: `1px solid ${C.hair}`,
      borderRadius: 8,
      fontSize: 13, color: C.muted,
      fontFamily: 'Hanken Grotesk, sans-serif',
    }}>
      <HeaderIcon kind={icon} />
      {label}
    </span>
  );
}
function HeaderIcon({ kind }) {
  if (kind === 'charts') return <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke={C.muted} strokeWidth="1.6"><path d="M3 13V7M7 13V4M11 13V9"/></svg>;
  if (kind === 'play')   return <svg width="11" height="11" viewBox="0 0 16 16" fill={C.muted}><path d="M4 3l9 5-9 5z"/></svg>;
  if (kind === 'msg')    return <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke={C.muted} strokeWidth="1.4"><path d="M2 4a2 2 0 012-2h8a2 2 0 012 2v6a2 2 0 01-2 2H7l-3 2v-2h-1a1 1 0 01-1-1V4z"/></svg>;
  return null;
}

// ─────────────────────────────────────────────────────────────────────
// Typewriter — for the user question line and other reveals
// ─────────────────────────────────────────────────────────────────────

function Typewriter({ text, speed = 22, startDelay = 0, onDone, caret = true }) {
  const [n, setN] = useState(0);
  const [running, setRunning] = useState(false);
  useEffect(() => {
    setN(0);
    setRunning(false);
    const start = setTimeout(() => setRunning(true), startDelay);
    return () => clearTimeout(start);
  }, [text, startDelay]);
  useEffect(() => {
    if (!running) return;
    if (n >= text.length) { onDone && onDone(); return; }
    const t = setTimeout(() => setN(n + 1), speed);
    return () => clearTimeout(t);
  }, [n, running, text, speed, onDone]);
  return (
    <span>
      {text.slice(0, n)}
      {caret && n < text.length && (
        <span style={{
          display: 'inline-block',
          width: 2, height: '0.95em',
          background: C.ink,
          marginLeft: 2,
          transform: 'translateY(2px)',
          animation: 'blink-caret 1s steps(1) infinite',
        }} />
      )}
    </span>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Tiny inline bar chart — used in evidence panels / cascade
// ─────────────────────────────────────────────────────────────────────

function MiniBarChart({ data, height = 220, animate = true, accent }) {
  // data: [{label, value, color?}]
  const max = Math.max(...data.map(d => d.value), 1);
  const [mounted, setMounted] = useState(!animate);
  useEffect(() => {
    if (!animate) return;
    const t = setTimeout(() => setMounted(true), 60);
    return () => clearTimeout(t);
  }, [animate]);
  return (
    <div style={{ width: '100%' }}>
      <div style={{
        display: 'flex', alignItems: 'flex-end', gap: 10,
        height: height,
        padding: '0 4px',
      }}>
        {data.map((d, i) => {
          const h = mounted ? (d.value / max) * (height - 30) : 0;
          const color = d.color || accent || C.sea;
          return (
            <div key={d.label} style={{
              flex: 1, display: 'flex', flexDirection: 'column',
              alignItems: 'center', justifyContent: 'flex-end',
              height: '100%',
              gap: 6,
            }}>
              <div style={{
                fontFamily: monoFont, fontSize: 13, color: C.muted,
                opacity: mounted ? 1 : 0,
                transition: 'opacity 240ms ease',
                transitionDelay: `${i * 40 + 280}ms`,
              }}>
                {d.value}%
              </div>
              <div style={{
                width: '100%',
                maxWidth: 56,
                height: h,
                background: color,
                borderRadius: '6px 6px 2px 2px',
                transition: `height 480ms cubic-bezier(.2,.7,.2,1)`,
                transitionDelay: `${i * 50}ms`,
                boxShadow: `0 1px 0 ${color}55`,
              }} />
            </div>
          );
        })}
      </div>
      <div style={{
        display: 'flex', gap: 10, marginTop: 8,
        padding: '0 4px',
      }}>
        {data.map((d) => (
          <div key={d.label} style={{
            flex: 1, textAlign: 'center',
            fontSize: 13, color: C.muted,
            fontFamily: 'Hanken Grotesk, sans-serif',
          }}>
            {d.label}
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Block of "syntax-highlighted" SQL (simple regex paint)
// ─────────────────────────────────────────────────────────────────────

const SQL_KEYWORDS = /\b(SELECT|FROM|WHERE|GROUP BY|ORDER BY|LIMIT|JOIN|ON|AND|OR|AS|WITH|CASE|WHEN|THEN|ELSE|END|HAVING|IN|NOT|IS|NULL|DISTINCT|UNION|ALL|LEFT|RIGHT|INNER|OUTER|COUNT|SUM|AVG|MIN|MAX|CAST|COALESCE)\b/g;
const SQL_NUMBER  = /\b(\d+\.?\d*)\b/g;
const SQL_STRING  = /'([^']*)'/g;

function highlightSQL(src) {
  // Replace with sentinel placeholders that won't collide, then HTML.
  let out = src
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  out = out.replace(SQL_STRING,  (m, s) => `§STR§'${s}'§/STR§`);
  out = out.replace(SQL_KEYWORDS, (m) => `§KW§${m}§/KW§`);
  out = out.replace(SQL_NUMBER,   (m) => `§NUM§${m}§/NUM§`);
  out = out
    .replace(/§KW§/g,   '<span style="color:#9333ea;font-weight:600">')
    .replace(/§\/KW§/g, '</span>')
    .replace(/§NUM§/g,  '<span style="color:#c97a00">')
    .replace(/§\/NUM§/g,'</span>')
    .replace(/§STR§/g,  '<span style="color:#1e7a52">')
    .replace(/§\/STR§/g,'</span>');
  return out;
}

function SqlBlock({ sql, maxHeight }) {
  return (
    <pre style={{
      margin: 0,
      padding: '14px 16px',
      background: C.bgSoft,
      color: C.ink,
      border: `1px solid ${C.hair}`,
      borderRadius: 8,
      fontFamily: monoFont,
      fontSize: 13.5,
      lineHeight: 1.55,
      overflow: 'auto',
      maxHeight: maxHeight || 'none',
      letterSpacing: -0.005,
    }}>
      <code dangerouslySetInnerHTML={{ __html: highlightSQL(sql) }} />
    </pre>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Keyframes (injected once)
// ─────────────────────────────────────────────────────────────────────

(function injectKeyframes() {
  if (document.getElementById('pitch-kf')) return;
  const s = document.createElement('style');
  s.id = 'pitch-kf';
  s.textContent = `
    @keyframes pulse-dot {
      0%, 100% { opacity: 1; transform: scale(1); }
      50%       { opacity: 0.45; transform: scale(0.8); }
    }
    @keyframes blink-caret {
      0%, 50% { opacity: 1; }
      51%, 100% { opacity: 0; }
    }
    @keyframes fade-up {
      from { opacity: 0; transform: translateY(8px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    @keyframes fade-in {
      from { opacity: 0; }
      to   { opacity: 1; }
    }
    @keyframes draw-edge {
      from { stroke-dashoffset: var(--len, 200); }
      to   { stroke-dashoffset: 0; }
    }
    @keyframes pop-in {
      0%   { opacity: 0; transform: scale(0.6); }
      70%  { opacity: 1; transform: scale(1.08); }
      100% { opacity: 1; transform: scale(1); }
    }
    @keyframes slide-in-right {
      from { opacity: 0; transform: translateX(36px); }
      to   { opacity: 1; transform: translateX(0); }
    }
    @keyframes ping {
      0%   { box-shadow: 0 0 0 0   rgba(30,109,255,0.35); }
      80%  { box-shadow: 0 0 0 16px rgba(30,109,255,0); }
      100% { box-shadow: 0 0 0 0   rgba(30,109,255,0); }
    }
  `;
  document.head.appendChild(s);
})();

// ─────────────────────────────────────────────────────────────────────
// Export to window
// ─────────────────────────────────────────────────────────────────────

// ─────────────────────────────────────────────────────────────────────
// Reveal — state-driven appearance using transitions (NOT keyframes).
// Why: the screenshot tool clones the DOM into a static SVG, so CSS
// @keyframes animations capture at frame 0 ("from" state) and look
// invisible. Transitions driven by React state land at the end state
// in the cloned DOM, so they appear correctly in screenshots while
// still animating smoothly in the live page.
// ─────────────────────────────────────────────────────────────────────
function Reveal({ from = 'up', delay = 0, duration = 320, children, style }) {
  const [shown, setShown] = useState(false);
  useEffect(() => {
    const t = setTimeout(() => {
      // double-rAF so the first paint locks in the "from" state, then
      // the second triggers the transition.
      requestAnimationFrame(() => requestAnimationFrame(() => setShown(true)));
    }, delay);
    return () => clearTimeout(t);
  }, []);
  const fromStyles = {
    up:    { opacity: 0, transform: 'translateY(10px)' },
    right: { opacity: 0, transform: 'translateX(36px)' },
    fade:  { opacity: 0, transform: 'none' },
    pop:   { opacity: 0, transform: 'scale(0.94)' },
  };
  const cur = shown
    ? { opacity: 1, transform: 'none' }
    : fromStyles[from];
  return (
    <div style={{
      ...style,
      ...cur,
      transition: `opacity ${duration}ms cubic-bezier(.2,.7,.2,1), transform ${duration}ms cubic-bezier(.2,.7,.2,1)`,
    }}>{children}</div>
  );
}

// ─────────────────────────────────────────────────────────────────────
// Modern evidence primitives — faithful inline-styled ports of the real
// product components in web/.../studio/shared/proof.jsx:
//   • InfoMarker  → the small ring-"i" affordance (ProofMarker's button)
//   • ProofCard   → subject·metric eyebrow, the rendered claim, scope,
//                   an honest significance state, source question + package.
// One citation system, reused by the grounding slides. This replaces the
// older bracketed [N] CitationChip, which is no longer how the app cites.
// ─────────────────────────────────────────────────────────────────────

const SIG = {
  sig:      { dot: C.moss,   tx: C.moss,   label: 'Significant at 95%' },
  ns:       { dot: C.muted2, tx: C.muted,  label: 'Tested — not significant' },
  untested: { dot: C.amber,  tx: C.amber,  label: 'Not significance-tested' },
};

function InfoMarker({ active = false, onClick, size = 18 }) {
  const c = active ? C.sea : C.muted2;
  return (
    <button
      type="button"
      onClick={onClick}
      aria-label="Show the evidence behind this point"
      style={{
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        width: size, height: size, marginLeft: 4, padding: 0,
        borderRadius: '50%',
        border: `1.4px solid ${active ? C.sea : '#d7d7de'}`,
        background: active ? C.seaBg : '#fff',
        color: c, cursor: 'pointer',
        verticalAlign: 'middle', transform: 'translateY(-1px)',
        transition: 'all 140ms ease',
        boxShadow: active ? `0 0 0 3px ${C.sea}1f` : 'none',
        flexShrink: 0,
      }}
    >
      <svg viewBox="0 0 16 16" width={size * 0.62} height={size * 0.62} fill="none" stroke={c} strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round">
        <circle cx="8" cy="8" r="6.2" strokeWidth="1.5" />
        <path d="M8 5h.01M7.4 7.6H8.1v3.2h.7" />
      </svg>
    </button>
  );
}

function ProofCard({ claim, compact = false }) {
  const eyebrow = [claim.subject, claim.metric].filter(Boolean).join(' · ');
  const scope = [
    claim.n != null ? `n=${Number(claim.n).toLocaleString()}` : '',
    claim.period, claim.base,
  ].filter(Boolean).join(' · ');
  const sig = SIG[claim.sig] || SIG.untested;
  return (
    <div style={{ textAlign: 'left' }}>
      {eyebrow && (
        <div style={{
          fontFamily: monoFont, fontSize: 11, fontWeight: 700,
          letterSpacing: 0.6, textTransform: 'uppercase', color: C.muted2,
        }}>{eyebrow}</div>
      )}
      <div style={{ marginTop: 4, display: 'flex', alignItems: 'baseline', gap: 10 }}>
        {claim.value && (
          <span style={{
            fontSize: 26, fontWeight: 700, color: C.ink, letterSpacing: -0.02,
            fontVariantNumeric: 'tabular-nums', lineHeight: 1,
          }}>{claim.value}</span>
        )}
        <span style={{ fontSize: 15.5, lineHeight: 1.3, color: C.ink, fontWeight: 500 }}>{claim.proof}</span>
      </div>
      {scope && (
        <div style={{ marginTop: 8, fontFamily: monoFont, fontSize: 12.5, color: C.muted }}>{scope}</div>
      )}
      <div style={{ marginTop: 8, display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 13, color: sig.tx, fontWeight: 600 }}>
          <span style={{ width: 8, height: 8, borderRadius: '50%', background: sig.dot }} />
          {sig.label}
        </span>
        {claim.lowBase && (
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 13, color: C.amber, fontWeight: 600 }}>
            <span style={{ width: 8, height: 8, borderRadius: '50%', background: C.amber }} />
            Low base (n&lt;100)
          </span>
        )}
      </div>
      {!compact && claim.question && (
        <div style={{
          marginTop: 12, paddingTop: 12, borderTop: `1px solid ${C.hair}`,
          fontSize: 14, fontStyle: 'italic', lineHeight: 1.4, color: C.muted,
        }}>“{claim.question}”</div>
      )}
      {!compact && (claim.qcode || claim.pkg) && (
        <div style={{ marginTop: 11, display: 'flex', alignItems: 'center', gap: 18, flexWrap: 'wrap' }}>
          {claim.qcode && (
            <span style={{ display: 'inline-flex', alignItems: 'baseline', gap: 7 }}>
              <span style={{ fontFamily: monoFont, fontSize: 10.5, fontWeight: 700, letterSpacing: 0.6, textTransform: 'uppercase', color: C.muted2 }}>Survey question</span>
              <span style={{ fontFamily: monoFont, fontSize: 13, color: C.muted }}>{claim.qcode}</span>
            </span>
          )}
          {claim.pkg && (
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 13, color: C.sea }}>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: claim.pkgColor || C.sea }} />
              <span style={{ textDecoration: 'underline dotted', textUnderlineOffset: 2 }}>Open research package</span>
            </span>
          )}
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  pitchC: C,
  pitchMono: monoFont,
  StatusPill, ToolBadge, CitationChip,
  ToolCallCard, Typewriter, MiniBarChart, SqlBlock,
  ChatBubble, AppChatPanel, HeaderPill,
  Reveal, InfoMarker, ProofCard,
});
