/* Product section, Pricing, and Waitlist.
 *
 * The Product section (brief §5) is the Intake-to-Finance pipeline: five
 * sequential stages plus one cross-cutting layer. Every page here is named
 * for the procurement capability, never for the internal screen — the
 * mockups show the real UI with its real labels, the marketing IA does not.
 *
 * Stage order and routes live in PRODUCT_STAGES / PRODUCT_LAYER
 * (components.jsx) so nav, footer, and these pages can never drift apart.
 */

/* ══════════════════════════════════════════════════════════════════════
   Shared page furniture
   ══════════════════════════════════════════════════════════════════════ */

/* Cross-links out of a stage page — into other stages, programs, or
   audience pages. Keeps each page a junction rather than a dead end. */
function ProductCrossLinks({ title, links }) {
  return (
    <section className="section off">
      <div className="container">
        <SectionHead title={title} />
        <div className="row-3">
          {links.map(l => (
            <a
              key={l.path}
              href={l.path}
              className="cross-link"
              onClick={(e) => navTo(e, l.path)}
            >
              <div className="mono" style={{ color: 'var(--blue)' }}>{l.kicker}</div>
              <div className="cross-link-title">{l.label}</div>
              <div className="cross-link-desc">{l.desc}</div>
              <div className="cross-link-arrow">→</div>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}


/* Reads once, at mount. A visitor who has asked for reduced motion gets
   the static layout: every bullet at full weight, first visual only. */
function usePrefersReducedMotion() {
  // Read synchronously on first render, so a visitor who has asked for
  // reduced motion never gets even a frame of the scroll-driven version
  // (and no observer is ever constructed for them).
  const [reduced, setReduced] = useState(
    () => !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches)
  );
  useEffect(() => {
    if (!window.matchMedia) return;
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    setReduced(mq.matches);
    const on = (e) => setReduced(e.matches);
    mq.addEventListener ? mq.addEventListener('change', on) : mq.addListener(on);
    return () => { mq.removeEventListener ? mq.removeEventListener('change', on) : mq.removeListener(on); };
  }, []);
  return reduced;
}

/* Given the clusters' viewport rects and which of them currently intersect
   the centre band, pick the one whose middle sits closest to the centre
   line. Pulled out as a pure function so the choice can be tested without
   a live IntersectionObserver — and because when two clusters straddle the
   band at once, "first in DOM order" is the wrong answer.
   Returns -1 when nothing intersects, meaning "leave the active one be". */
function pickActiveCluster(rects, hits, centre) {
  let best = -1;
  let bestDist = Infinity;
  for (let i = 0; i < rects.length; i++) {
    if (!hits[i]) continue;
    const r = rects[i];
    // Distance from the centre line to the nearest point of the cluster —
    // zero while the line is inside it, so a cluster spanning the centre
    // always wins over one merely clipping the band's edge.
    const d = r.top > centre ? r.top - centre : r.bottom < centre ? centre - r.bottom : 0;
    if (d < bestDist) { bestDist = d; best = i; }
  }
  return best;
}

/* Active cluster = the one intersecting a thin band across the middle of
   the viewport. rootMargin collapses the root to that band, so "which one
   is the reader looking at" needs no scroll-position arithmetic. */
function useActiveCluster(count, enabled) {
  const [active, setActive] = useState(0);
  const refs = useRef([]);

  useEffect(() => {
    if (!enabled || typeof IntersectionObserver === 'undefined') return;
    const nodes = refs.current.slice(0, count).filter(Boolean);
    if (!nodes.length) return;
    const hits = new Array(nodes.length).fill(false);
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        const i = nodes.indexOf(e.target);
        if (i >= 0) hits[i] = e.isIntersecting;
      });
      const rects = nodes.map((n) => n.getBoundingClientRect());
      const idx = pickActiveCluster(rects, hits, window.innerHeight / 2);
      if (idx >= 0) setActive(idx);
    }, { rootMargin: '-45% 0px -45% 0px', threshold: 0 });
    nodes.forEach((n) => io.observe(n));
    return () => io.disconnect();
  }, [count, enabled]);

  return [active, refs];
}

/* The bullet list, grouped. Numbering stays continuous (01…06) across
   clusters so the list still reads as one sequence. */
function FeatureClusters({ clusters, active, refs, enabled }) {
  let n = 0;
  return (
    <div className="flist feature-clusters">
      {clusters.map((c, ci) => {
        const isActive = !enabled || ci === active;
        return (
          <div
            key={c.label}
            ref={(el) => { refs.current[ci] = el; }}
            className={'feature-cluster' + (isActive ? ' active' : '') + (enabled ? ' scrollable' : '')}
          >
            {c.items.map((it) => {
              n += 1;
              return (
                <div className="row" key={it.title}>
                  <div className="num">{String(n).padStart(2, '0')}</div>
                  <div className="title">{it.title}</div>
                  <div className="desc">{it.desc}</div>
                </div>
              );
            })}
          </div>
        );
      })}
    </div>
  );
}

/* One stage page. Hero → features → mockup → sourced stat → cross-links → CTA. */
function ProductStagePage({ screen, stage, h1, sub, vocabulary, clusters, stats, statsNote, extra, crossTitle, crossLinks }) {
  const reduced = usePrefersReducedMotion();
  const enabled = !reduced && clusters.length > 1;
  const [active, refs] = useActiveCluster(clusters.length, enabled);
  const shown = enabled ? active : 0;

  return (
    /* page-no-transform: the default page-entry animation translates the
       whole page, and a transformed ancestor becomes the containing block
       for sticky descendants — which would break the sticky visual column
       below. These pages fade in without the rise. */
    <div className="page page-no-transform" data-screen-label={screen}>
      <section className="hero product-stage-hero" style={{ paddingBottom: 48 }}>
        <div className="container">
          <h1 className="display-lg" style={{ maxWidth: '18ch' }}>{h1}</h1>
          <p className="hero-sub" style={{ maxWidth: '62ch' }}>{sub}</p>
        </div>
      </section>

      <section className="section">
        <div className="container">
          <div className="row-2" style={{ alignItems: 'start' }}>
            <div>
              <h2 className="display-md" style={{ marginBottom: 28, maxWidth: '20ch' }}>{stage.label} on Strike SCF.</h2>
              <FeatureClusters clusters={clusters} active={active} refs={refs} enabled={enabled} />
              {vocabulary && (
                <p className="mono product-vocab">{vocabulary}</p>
              )}
            </div>
            {/* The column stretches to the row's full height; the inner
                wrapper is what sticks, so the mockup follows the feature
                list instead of leaving the right half of the viewport
                empty, and releases at the section boundary. */}
            <div className="product-visual-col">
              <div className="product-visual-sticky">
                <div className="stage-visual-stack">
                  <div className="stage-visual in" key={clusters[shown].label}>
                    <div className="frame-glow">{clusters[shown].visual()}</div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </section>

      {stats && stats.length > 0 && (
        <section className="section off">
          <div className="container">
            <div className={'sourced-stat-strip cols-' + stats.length}>
              {stats.map((st, i) => <SourcedStat key={i} {...st} />)}
            </div>
            <StatDisclaimer />
            {statsNote && <p className="sourced-stat-note">{statsNote}</p>}
          </div>
        </section>
      )}

      {extra && extra()}

      {crossLinks && <ProductCrossLinks title={crossTitle} links={crossLinks} />}

      <section className="section">
        <div className="container">
          <div className="row-2" style={{ alignItems: 'center' }}>
            <h2 className="display-md" style={{ maxWidth: '22ch' }}>See it on your own supply chain.</h2>
            <div>
              <p className="body body-gray" style={{ maxWidth: '44ch', marginBottom: 28 }}>
                <WaitlistCounter />. Pilot slots open in cohorts — tell us what you're
                trying to solve and we'll route you to the right desk.
              </p>
              <CtaPair />
            </div>
          </div>
        </div>
      </section>
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Stage-1 mockup — the sourcing-event list. The homepage owns the
   chat-search moment; this shows what the desk looks like once several
   events are in flight at once.
   ══════════════════════════════════════════════════════════════════════ */
const SOURCING_EVENTS = [
  { ref: 'RFX-2026-0311', title: 'Precision CNC Housings — 40,000 units', status: 'Comparing', tone: 'b', responses: '6 of 9 responded', value: '$612,000', badge: 'blue' },
  { ref: 'RFX-2026-0309', title: 'Injection-Moulded Enclosures — 120,000 units', status: 'Sent', tone: 'c', responses: '2 of 11 responded', value: '$184,500', badge: '' },
  { ref: 'RFX-2026-0304', title: 'Anodised Aluminium Extrusion — 80 MT', status: 'Awarded', tone: 'a', responses: 'Coventry Trade Ltd', value: '$327,900', badge: 'green' },
  { ref: 'RFX-2026-0316', title: 'Cold-Chain Freight — Q4 lanes', status: 'Draft', tone: 'b', responses: 'Not yet sent', value: '—', badge: 'gray' },
];

/* Ranked responses on the event the list shows as "Comparing". */
const SOURCING_RESPONSES = [
  { name: 'Meridian Optics SA', score: 92, bid: '$612,000', lead: '6 weeks', tone: 'b', best: true },
  { name: 'Coventry Trade Ltd', score: 78, bid: '$598,400', lead: '9 weeks', tone: 'a' },
  { name: 'Halden Precision GmbH', score: 64, bid: '$641,200', lead: '4 weeks', tone: 'c' },
];

/* Everything that arrives through the front door, before a buyer touches it. */
const SOURCING_INTAKE = [
  { ref: 'REQ-4471', title: 'Cold-chain freight — Q4 lanes', who: 'Logistics · D. Rowe', tag: 'Requisition', badge: 'blue' },
  { ref: 'CASE-208', title: 'Supplier onboarding — Halden Precision', who: 'Procurement · S. Nyman', tag: 'Case', badge: 'gray' },
  { ref: 'REQ-4468', title: 'Anodised extrusion — replenishment', who: 'Production · B. Tan', tag: 'Requisition', badge: 'blue' },
  { ref: 'REQ-4462', title: 'Packaging redesign — pilot run', who: 'Design · L. Weiss', tag: 'Requisition', badge: 'gray' },
];

/* The draft a buyer is composing for the freight event sitting in the
   intake queue — same document-and-clause-score pattern the Negotiate page
   uses, with Sourcing's own scenario. */
const SOURCING_RFX = {
  file: 'RFx — Cold-Chain Freight, Q4 Lanes.docx',
  qty: 'temperature-controlled road freight held at 2–8°C across 140 lanes',
  delivery: 'weekly departures from Rotterdam and Valencia, 1 October – 31 December 2026',
  payment: 'a per-lane-kilometre rate card, payable Net 30 from proof of delivery',
};

function SourcingEventsPanel({ view = 'list' }) {
  const [active, setActive] = useState(0);
  const rows = view === 'list' ? SOURCING_EVENTS.length : 0;

  useEffect(() => {
    if (!rows) return;
    const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced) return;
    const id = setInterval(() => setActive(a => (a + 1) % rows), 2200);
    return () => clearInterval(id);
  }, [rows]);

  const head = {
    list:      { url: 'app.strikescf.com/sourcing/events',            label: 'Sourcing events', title: '4 in flight · Q3 direct materials', badge: 'New event' },
    responses: { url: 'app.strikescf.com/sourcing/events/RFX-2026-0311', label: 'RFX-2026-0311 · responses', title: 'Precision CNC Housings — 40,000 units', badge: '6 of 9 in' },
    intake:    { url: 'app.strikescf.com/intake',                      label: 'Intake queue', title: '4 open · unassigned', badge: 'Spend under management' },
  }[view];

  return (
    <MiniFrame url={head.url} className="mini-frame-lg askd-frame">
      <div className="se-panel">
        <div className="se-head">
          <div>
            <div className="pp-label">{head.label}</div>
            <div className="se-title">{head.title}</div>
          </div>
          <span className="pp-badge blue">{head.badge}</span>
        </div>

        {view === 'list' && (
          <div className="se-list">
            {SOURCING_EVENTS.map((ev, i) => (
              <div key={ev.ref} className={'se-row' + (i === active ? ' active' : '')}>
                <div className="se-thumb"><MaterialPhoto tone={ev.tone} /></div>
                <div className="se-body">
                  <div className="se-ref">{ev.ref}</div>
                  <div className="se-row-title">{ev.title}</div>
                  <div className="se-responses">{ev.responses}</div>
                </div>
                <div className="se-meta">
                  <span className={'pp-badge ' + (ev.badge || 'gray')}>{ev.status}</span>
                  <span className="se-value">{ev.value}</span>
                </div>
              </div>
            ))}
          </div>
        )}

        {view === 'responses' && (
          <div className="se-list">
            {SOURCING_RESPONSES.map(r => (
              <div key={r.name} className={'se-row' + (r.best ? ' active' : '')}>
                <div className="se-thumb"><MaterialPhoto tone={r.tone} /></div>
                <div className="se-body">
                  <div className="se-ref">{r.best ? 'Ranked #1 · best fit' : 'Ranked'}</div>
                  <div className="se-row-title">{r.name}</div>
                  <div className="se-responses">Lead time {r.lead}</div>
                </div>
                <div className="se-meta">
                  <span className={'se-score ' + (r.score >= 85 ? 'good' : r.score >= 70 ? 'warn' : 'bad')}>{r.score}</span>
                  <span className="se-value">{r.bid}</span>
                </div>
              </div>
            ))}
          </div>
        )}

        {view === 'intake' && (
          <div className="se-list">
            {SOURCING_INTAKE.map(r => (
              <div key={r.ref} className="se-row">
                <div className="se-body">
                  <div className="se-ref">{r.ref}</div>
                  <div className="se-row-title">{r.title}</div>
                  <div className="se-responses">{r.who}</div>
                </div>
                <div className="se-meta">
                  <span className={'pp-badge ' + r.badge}>{r.tag}</span>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </MiniFrame>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Finance second visual — limits, tenor, and what the programme does to
   the cash conversion cycle. Illustrative product-UI content, not a
   Strike outcome claim.
   ══════════════════════════════════════════════════════════════════════ */
const UTIL_LIMITS = [
  { k: 'PROGRAMME LIMIT', v: '$250.0M', sub: 'BANK BOARD APPROVED' },
  { k: 'UTILISATION', v: '62.4%', sub: '$156.0M IN FLIGHT', tone: 'good' },
  { k: 'ADVANCE RATE', v: '97.0%', sub: 'POSTED · NET 90' },
  { k: 'AVG. TENOR', v: '78 days', sub: 'TARGET 75–95' },
];
const UTIL_CYCLE = [
  { label: 'DSO — supplier', before: 74, after: 12, tone: 'good' },
  { label: 'DPO — buyer', before: 62, after: 90, tone: 'good' },
  { label: 'Cash conversion cycle', before: 41, after: 9, tone: 'good' },
];

function UtilisationPanel() {
  return (
    <MiniFrame url="app.strikescf.com/financing/programmes" className="mini-frame-lg askd-frame">
      <div className="ex-panel">
        <div className="ex-head">
          <div>
            <div className="pp-label">Programme utilisation</div>
            <div className="ex-title">Aurelia Holdings · USD Payables</div>
          </div>
          <span className="pp-badge green">Live</span>
        </div>

        <div className="ex-tiles">
          {UTIL_LIMITS.map(t => (
            <div className="ex-tile" key={t.k}>
              <div className="ex-tile-k">{t.k}</div>
              <div className={'ex-tile-v' + (t.tone ? ' ' + t.tone : '')}>{t.v}</div>
              <div className="ex-tile-sub">{t.sub}</div>
            </div>
          ))}
        </div>

        <div className="ex-chart">
          <div className="ex-chart-head">
            <span className="pp-label">Cash conversion cycle</span>
            <span className="ex-chart-delta">Before → on programme</span>
          </div>
          <div className="util-rows">
            {UTIL_CYCLE.map(r => (
              <div className="util-row" key={r.label}>
                <span className="util-row-label">{r.label}</span>
                <span className="util-row-before">{r.before}d</span>
                <span className="util-row-arrow">→</span>
                <span className={'util-row-after ' + r.tone}>{r.after}d</span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </MiniFrame>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Operate & Report second visual — the executive roll-up. The board is
   the work; this is what the work reports. Figures here are illustrative
   product-UI content, not Strike outcome claims.
   ══════════════════════════════════════════════════════════════════════ */
const EXEC_TILES = [
  { k: 'SPEND UNDER MANAGEMENT', v: '$48.2M', sub: 'ROLLING 12 MONTHS', blue: true },
  { k: 'AVG. CYCLE TIME', v: '6.4 days', sub: 'INTAKE TO CONTRACT' },
  { k: 'EXCEPTION RATE', v: '3.1%', sub: 'OF MATCHED INVOICES' },
  { k: 'PROGRAM UTILISATION', v: '62%', sub: 'OF APPROVED LIMITS' },
];
const EXEC_BARS = [34, 41, 38, 52, 47, 58, 63, 61, 72, 69, 78, 84];

function ExecReportPanel() {
  return (
    <MiniFrame url="app.strikescf.com/reporting" className="mini-frame-lg askd-frame">
      <div className="ex-panel">
        <div className="ex-head">
          <div>
            <div className="pp-label">Executive reporting</div>
            <div className="ex-title">All entities · Trailing 12 months</div>
          </div>
          <span className="pp-badge blue">Scheduled · Monthly</span>
        </div>

        <div className="ex-tiles">
          {EXEC_TILES.map(t => (
            <div className="ex-tile" key={t.k}>
              <div className="ex-tile-k">{t.k}</div>
              <div className={'ex-tile-v' + (t.blue ? ' blue' : '')}>{t.v}</div>
              <div className="ex-tile-sub">{t.sub}</div>
            </div>
          ))}
        </div>

        <div className="ex-chart">
          <div className="ex-chart-head">
            <span className="pp-label">Spend under management</span>
            <span className="ex-chart-delta">+ 18 PP</span>
          </div>
          <div className="ex-bars">
            {EXEC_BARS.map((h, i) => (
              <span key={i} className={'ex-bar' + (i > 8 ? ' lit' : '')} style={{ height: h + '%', animationDelay: (i * 0.05) + 's' }} />
            ))}
          </div>
        </div>

        <div className="ex-entities">
          {[
            ['Aurelia Holdings · US', '$21.4M', '48%'],
            ['Aurelia Europe · NL', '$16.8M', '38%'],
            ['Aurelia APAC · SG', '$10.0M', '14%'],
          ].map(r => (
            <div className="ex-entity-row" key={r[0]}>
              <span className="ex-entity-name">{r[0]}</span>
              <span className="ex-entity-val">{r[1]}</span>
              <span className="ex-entity-pct">{r[2]}</span>
            </div>
          ))}
        </div>
      </div>
    </MiniFrame>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Reconcile mockup — a 3-way match with one live exception. Built in the
   same visual language as the homepage demos rather than commissioned as
   a generic illustration.
   ══════════════════════════════════════════════════════════════════════ */
const MATCH_ROWS = [
  { line: 'Hot-Rolled Steel Coil — A36', po: '500 MT', grn: '500 MT', inv: '500 MT', state: 'matched' },
  { line: 'Unit price', po: '$780.00', grn: '—', inv: '$780.00', state: 'matched' },
  { line: 'Delivery — Port of Houston', po: '16 Sep', grn: '16 Sep', inv: '16 Sep', state: 'matched' },
  { line: 'Freight surcharge', po: '—', grn: '—', inv: '$4,200', state: 'exception' },
];

/* stage 'match' walks the clean lines; stage 'exception' lands on the one
   that fails and the dispute it raises.
   One-shot, not a loop: the panel is mounted only while its cluster is
   active and remounts on every change, so replaying forever would mean a
   reader who scrolled here to see the exception could arrive mid-cycle and
   watch it disappear again. It reveals once, then holds. */
function MatchPanel({ stage = 'exception' }) {
  const target = stage === 'match' ? MATCH_ROWS.length - 1 : MATCH_ROWS.length;
  const [revealed, setRevealed] = useState(0);

  useEffect(() => {
    const reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced) { setRevealed(target); return; }
    const timers = [];
    for (let i = 1; i <= target; i++) {
      timers.push(setTimeout(() => setRevealed(i), 220 + i * 260));
    }
    return () => timers.forEach(clearTimeout);
  }, [target]);

  const exceptionFound = stage !== 'match' && revealed >= MATCH_ROWS.length;

  return (
    <MiniFrame url="app.strikescf.com/invoices/INV-88102" className="mini-frame-lg askd-frame">
      <div className="match-panel">
        <div className="match-panel-head">
          <div>
            <div className="pp-label">3-way match</div>
            <div className="match-panel-title">INV-88102 · Ironbridge Steel Works</div>
          </div>
          <span className={'pp-badge ' + (exceptionFound ? 'amber' : 'blue')}>
            {exceptionFound ? '1 exception' : 'Matching…'}
          </span>
        </div>

        <div className="match-grid">
          <div className="match-row head">
            <div>Line</div><div>PO</div><div>Receipt</div><div>Invoice</div><div />
          </div>
          {MATCH_ROWS.slice(0, target).map((r, i) => {
            const on = i < revealed;
            return (
              <div key={r.line} className={'match-row' + (on ? ' in' : '') + (on && r.state === 'exception' ? ' exception' : '')}>
                <div className="match-line">{r.line}</div>
                <div>{r.po}</div>
                <div>{r.grn}</div>
                <div>{r.inv}</div>
                <div className="match-flag">
                  {on && (r.state === 'exception' ? '!' : '✓')}
                </div>
              </div>
            );
          })}
        </div>

        <div className={'match-exception' + (exceptionFound ? ' in' : '')}>
          <span className="match-exception-avatar">S</span>
          <span>
            Freight surcharge of $4,200 is on the invoice but not on the PO or the receipt.
            Routed to the buyer as a dispute — nothing pays until it clears.
          </span>
        </div>
      </div>
    </MiniFrame>
  );
}

/* ── Stage-2: a different counterparty from the homepage's Ironbridge,
      shown as the full profile rather than the listing-preview card. ── */
const PASSPORT_MERIDIAN = {
  initials: 'MO',
  name: 'Meridian Optics SA',
  dba: 'doing business as Meridian Optics',
  score: 92,
  tier: 'Green Tier',
  tierClass: 'green',
  meta: '11 docs read · High confidence',
  bars: [
    { label: 'KYB & Compliance', value: 24, max: 25, color: '#059669' },
    { label: 'Financial Health', value: 22, max: 25, color: '#059669' },
    { label: 'Trade Reliability', value: 25, max: 25, color: '#059669' },
    { label: 'Network Reputation', value: 21, max: 25, color: '#D97706' },
  ],
  note: 'Meridian Optics SA scores 92/100 — Green tier, high confidence. Four years of trade history across 61 completed deals with a 99% on-time record. Network reputation is the only component below full marks, and only because volume with counterparties outside this network is still thin.',
  strengths: [
    '61 completed trades, 99% on-time, 1 dispute resolved in 4 days',
    'Audited financials filed for three consecutive years',
    'Beneficial ownership verified to ultimate parent',
  ],
};

/* ── Stage-3: a different commodity and counterparty from the homepage's
      steel negotiation, carried through both visuals on the page. ── */
const NEGOTIATION_DEAL = {
  tone: 'b',
  commodity: 'Precision CNC Housings — 6061-T6, 40,000 units',
  room: 'Precision CNC Housings — Deal Room',
  prompt: 'Negotiate this deal. Ceiling $640,000, floor $590,000. Keep FOB, Net 45.',
  log: [
    { side: 'out', label: 'Your Strike AI', tag: 'OFFER', text: '$598,000 · FOB · Net 45 — opening inside your floor-to-ceiling band.' },
    { side: 'inb', label: 'Meridian Optics · Strike AI', tag: 'COUNTER', text: '$628,000, and Net 30 instead of Net 45.' },
    { side: 'out', label: 'Your Strike AI', tag: 'HOLD', text: 'Net 45 is a hard term. $612,000 at Net 45 — routing to your approver.' },
  ],
};

const NEGOTIATION_RFX = {
  file: 'RFx — Precision CNC Housings.docx',
  qty: '40,000 precision CNC housings, 6061-T6 aluminium, anodised to spec MS-114',
  delivery: 'FOB Rotterdam, in four monthly lots beginning 3 November 2026',
  payment: '$612,000 at $15.30 per unit, payable Net 45 from delivery acceptance',
};

/* ── Stage-5: a different deal from the homepage's Walmart carton PO. ── */
const FINANCING_DEAL = {
  ref: 'My Deals · PO-MER-2026-01187',
  item: 'Precision CNC Housings, 40,000 Units',
  amount: '$612,000',
  erp: 'Synced from SAP S/4HANA',
  erpLine: 'Cash position $3.4M · 41 days runway',
  advice: 'Two supplier payments and a duty settlement land inside the next 14 days. Financing this receivable now covers all three without drawing on the revolver.',
  structure: 'Reverse Factoring',
  bank: 'Northgate Trade Bank',
  rows: [
    { label: 'Discount (28 days early)', value: '−$4,284' },
    { label: 'Strike Service Fee (0.3%)', value: '−$1,836' },
  ],
  net: '$605,880',
  successSub: '$612,000 · Northgate Trade Bank · Net $605,880 after fees',
};

/* ── Cross-cutting layer: a different board from the homepage's. ── */
const OPERATIONS_BOARD = {
  nodes: [
    { label: 'Intake', color: '#9AA0AB', agent: null, task: 'Requisition · Cold-chain freight, Q4' },
    { label: 'Sourcing', color: '#7C6FEB', agent: { name: 'Scout · Supplier Discovery', letter: 'S' }, task: 'RFX-2026-0311 · 6 of 9 responded' },
    { label: 'Contracting', color: '#D97706', agent: { name: 'Quill · Drafting', letter: 'Q' }, task: 'Meridian Optics · clause score 87' },
    { label: 'Reconciled', color: '#059669', agent: { name: 'Tally · Matching', letter: 'T' }, task: 'INV-88102 · 1 exception cleared' },
  ],
  activity: [
    { agent: 'Scout', text: 'shortlisted 3 of 9 respondents', time: '6m ago' },
    { agent: 'Quill', text: 'drafted RFx for Meridian Optics', time: '35m ago' },
    { agent: 'Tally', text: 'cleared freight surcharge dispute', time: '2h ago' },
  ],
  team: ['DR', 'SN', 'BT', 'LW', '+5'],
  teamLabel: '5 people · 4 agents',
};

/* ══════════════════════════════════════════════════════════════════════
   /product — the whole pipeline on one page
   ══════════════════════════════════════════════════════════════════════ */
function ProductOverviewPage() {
  return (
    <div className="page" data-screen-label="Product">
      <section className="hero" style={{ paddingBottom: 48 }}>
        <div className="container">
          <h1 className="display-lg" style={{ maxWidth: '16ch' }}>Intake-to-Finance with Strike AI.</h1>
          <p className="hero-sub" style={{ maxWidth: '62ch' }}>
            One system carries a request from the moment someone raises it to the moment
            it's financed and paid. Five stages, one record, and an operating layer that
            runs alongside all of them.
          </p>
          <div style={{ marginTop: 36 }}><CtaPair /></div>
        </div>
      </section>

      {/* THE PIPELINE — the five stages as a single visual sequence */}
      <section className="section">
        <div className="container">
          <SectionHead title="Five stages. One record." />
          <div className="pipeline">
            {PRODUCT_STAGES.map((st, i) => (
              <FadeIn key={st.path} delay={i * 90}>
                <a href={st.path} className="pipeline-stage" onClick={(e) => navTo(e, st.path)}>
                  <div className="pipeline-rail">
                    <span className="pipeline-dot" />
                    {i < PRODUCT_STAGES.length - 1 && <span className="pipeline-connector" />}
                  </div>
                  <div className="pipeline-num">{st.n}</div>
                  <div className="pipeline-label">{st.label}</div>
                  <div className="pipeline-desc">{st.desc}</div>
                  <div className="pipeline-arrow">→</div>
                </a>
              </FadeIn>
            ))}
          </div>

          {/* The cross-cutting layer sits under the pipeline as its own band,
              not as a sixth step — it runs alongside all five, not after them. */}
          <a href={PRODUCT_LAYER.path} className="pipeline-layer" onClick={(e) => navTo(e, PRODUCT_LAYER.path)}>
            <div>
              <div className="mono" style={{ color: 'var(--blue)' }}>ACROSS EVERY STAGE</div>
              <div className="pipeline-layer-title">{PRODUCT_LAYER.label}</div>
              <div className="pipeline-layer-desc">
                Spend controls, budgets, team and AI-agent workflow boards, executive
                reporting, and multi-entity controls — the same system of record from
                intake through settlement.
              </div>
            </div>
            <span className="pipeline-arrow">→</span>
          </a>
        </div>
      </section>

      {/* WHY IT MATTERS — one stat block on this page, not two. Every
          figure is a third-party benchmark for the manual process; the
          bridge under each is Strike's own claim about its own mechanism. */}
      <section className="section off">
        <div className="container">
          <SectionHead title="What the manual version costs." />
          <div className="sourced-stat-strip cols-3">
            <SourcedStat
              label="No manual keying"
              value="$2.78"
              compare="Best-in-class AP cost per invoice — manual runs $9.40–$40+"
              source="Ardent Partners, AP Metrics That Matter, 2025"
              blue
            />
            <SourcedStat
              label="Automated, not queued"
              value="3.1 days"
              compare="Best-in-class invoice processing time — manual workflows run 17.4 days"
              source="Ardent Partners, AP Metrics That Matter, 2025"
              tone="good"
            />
            <SourcedStat
              label="Process, not headcount"
              value="2.6x"
              compare="ROI at best-in-class procurement teams, with 31% fewer FTEs"
              source="The Hackett Group, 2025 Digital World Class Procurement"
              tone="good"
            />
          </div>
          <StatDisclaimer />
        </div>
      </section>

      <section className="section off">
        <div className="container">
          <div className="row-2" style={{ alignItems: 'center' }}>
            <h2 className="display-md" style={{ maxWidth: '20ch' }}>Start anywhere in the pipeline.</h2>
            <div>
              <p className="body body-gray" style={{ maxWidth: '44ch', marginBottom: 28 }}>
                <WaitlistCounter />. Most teams start with a single stage — sourcing, or
                reconciliation — and extend from there.
              </p>
              <CtaPair />
              {/* Borrowed credibility, kept to one line and clearly about other
                  vendors — never presented as a Strike SCF result. */}
              <p className="category-aside">
                Independent, Forrester-audited studies of AI-native procurement platforms report
                270–390% three-year ROI — we'll publish our own the moment we have pilot data.
              </p>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Stage 1 — Intake & Sourcing
   ══════════════════════════════════════════════════════════════════════ */
function ProductSourcingPage() {
  return (
    <ProductStagePage
      screen="ProductSourcing"
      stage={PRODUCT_STAGES[0]}
      h1="Every request starts here."
      sub="Requisitions, cases, and sourcing events all enter through one intake. Strike AI drafts the e-RFx, finds candidate suppliers across your private network and the open marketplace, and hands you ranked offers to compare."
      clusters={[
        {
          label: "Drafting the request",
          items: [
            { title: 'e-RFx Creation', desc: 'Draft and send a request for quote or proposal in minutes. Strike AI writes the first pass from the requisition, so the buyer edits rather than starts from nothing.' },
          ],
          visual: () => <RfxDemo url="app.strikescf.com/sourcing/events/new" doc={SOURCING_RFX} />,
        },
        {
          label: "Events in flight",
          items: [
            { title: 'Sourcing Events', desc: 'Run the full sourcing cycle in one place — issue the RFx, collect responses, compare offers side by side, and close with the best-fit supplier.' },
          ],
          visual: () => <SourcingEventsPanel view="list" />,
        },
        {
          label: "Ranked, scored responses",
          items: [
            { title: 'AI-Matched Supplier Discovery', desc: 'Strike scans your private network and the open marketplace for a match, then ranks and scores every candidate against trade history and compliance before you see a name.' },
            { title: 'Bilateral Trading Accounts', desc: 'One account sources as a buyer on one deal and sells as a supplier on the next. No separate buyer and supplier logins, no second onboarding.' },
          ],
          visual: () => <SourcingEventsPanel view="responses" />,
        },
        {
          label: "One front door for spend",
          items: [
            { title: 'Requisitions & Case Intake', desc: 'Internal requesters raise a purchase requisition or a case through the same front door. Nothing arrives by email attachment and nothing gets lost on the way to a buyer.' },
            { title: 'Spend Under Management', desc: 'Because every request enters through intake, the spend that runs through Strike is visible and governed by default rather than reconstructed after the quarter closes.' },
          ],
          visual: () => <SourcingEventsPanel view="intake" />,
        },
      ]}
      vocabulary="E-RFX · SOURCING EVENT · INTAKE · REQUISITION · SUPPLIER DISCOVERY · BILATERAL TRADING RELATIONSHIP · SPEND UNDER MANAGEMENT"
      stats={[
        {
          label: 'One intake, no re-keying',
          value: '58%',
          compare: 'Shorter requisition-to-PO cycles at best-in-class teams',
          source: 'The Hackett Group, 2025 Digital World Class Procurement',
          blue: true,
        },
        {
          label: "You edit, you don't assemble",
          value: '24%',
          compare: 'Shorter sourcing cycles at best-in-class teams',
          source: 'The Hackett Group, 2025 Digital World Class Procurement',
          tone: 'good',
        },
      ]}
      crossTitle="Where a sourced request goes next."
      crossLinks={[
        { path: '/product/counterparty-risk', kicker: 'STAGE 02', label: 'Verify & Score', desc: 'Every candidate carries a KYB-backed risk grade before you shortlist them.' },
        { path: '/product/negotiation', kicker: 'STAGE 03', label: 'Negotiate & Contract', desc: 'Set your ceiling and floor, then let the terms come back inside them.' },
        { path: '/suppliers', kicker: "WHO IT'S FOR", label: 'Suppliers', desc: 'Sell into the same network you buy from, on one bilateral account.' },
      ]}
    />
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Stage 2 — Verify & Score
   ══════════════════════════════════════════════════════════════════════ */
function ProductRiskPage() {
  return (
    <ProductStagePage
      screen="ProductCounterpartyRisk"
      stage={PRODUCT_STAGES[1]}
      h1="Know who you're dealing with before you ever see a name."
      sub="Every counterparty on Strike carries a composite score built from KYB, financial health, trade reliability, and network reputation. It travels with them from deal to deal, so due diligence is something you read rather than something you run."
      clusters={[
        {
          label: "The four score components",
          items: [
            { title: 'KYB Counterparty Verification', desc: 'Full Know Your Business onboarding — entity, ownership, and sanctions checks — completed once and reusable across every counterparty relationship that follows.' },
            { title: 'Financial-Health Scoring', desc: 'Solvency and liquidity signals rolled into a component score, so a counterparty that looks fine on paper and one that is genuinely fundable are not confused with each other.' },
            { title: 'Trade-Reliability History', desc: 'On-time delivery, short shipments, and dispute record across prior deals on the network — the operational track record a credit file never captures.' },
            { title: 'Network-Reputation Scoring', desc: 'How a counterparty has actually behaved with everyone else on the network, weighted by volume and recency rather than by testimonial.' },
          ],
          visual: () => (
            <MiniFrame url="app.strikescf.com/strike-passport" className="mini-frame-lg askd-frame">
              <PassportScorePanel org={PASSPORT_MERIDIAN} focus="components" />
            </MiniFrame>
          ),
        },
        {
          label: "The grade, and where it travels",
          items: [
            { title: 'Tiered Risk Grading', desc: 'The four components resolve to one 0–100 composite and a colour tier. A Green Tier counterparty clears straight through; anything lower routes to a human with the reason attached.' },
            { title: 'Portable Risk Identity', desc: 'The score belongs to the counterparty, not to your instance of it. A supplier that has earned a strong profile arrives at the next buyer with it already in hand.' },
          ],
          visual: () => (
            <MiniFrame url="app.strikescf.com/strike-passport" className="mini-frame-lg askd-frame">
              <PassportScorePanel org={PASSPORT_MERIDIAN} focus="tier" />
            </MiniFrame>
          ),
        },
      ]}
      vocabulary="KYB · COUNTERPARTY DUE DILIGENCE · RISK TIERING · TRADE CREDIT SIGNAL · PORTABLE RISK IDENTITY"
      stats={[
        {
          label: 'Verified before you see a name',
          value: '69%',
          compare: 'of companies were targeted by vendor/supplier fraud in 2024',
          source: 'AFP Payments Fraud and Control Survey, 2025',
          tone: 'bad',
        },
      ]}
      crossTitle="What a verified counterparty unlocks."
      crossLinks={[
        { path: '/product/negotiation', kicker: 'STAGE 03', label: 'Negotiate & Contract', desc: 'Score-gated guardrails — better tiers get wider negotiating room.' },
        { path: '/product/financing', kicker: 'STAGE 05', label: 'Finance', desc: 'The same score funders underwrite against when they price a facility.' },
        { path: '/banks', kicker: "WHO IT'S FOR", label: 'Banks', desc: 'One risk surface across every anchor program you fund.' },
      ]}
    />
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Stage 3 — Negotiate & Contract
   ══════════════════════════════════════════════════════════════════════ */
function ProductNegotiationPage() {
  return (
    <ProductStagePage
      screen="ProductNegotiation"
      stage={PRODUCT_STAGES[2]}
      h1="Set the guardrails. Let the terms come to you."
      sub="You define the ceiling, the floor, and what is never on the table. Strike AI negotiates inside those bounds against the counterparty's own agent, round by round, and stops at the approval gate — a person signs off before anything is final."
      clusters={[
        {
          label: "Inside the deal room",
          items: [
            { title: 'Negotiation Guardrails', desc: 'Price ceiling, price floor, tenor limits, and non-negotiable clauses are set before the first round. The agent cannot agree to anything outside them, by construction.' },
            { title: 'Human Approval Gates', desc: 'Nothing becomes binding without a person approving it. The AI negotiates and recommends; a named approver accepts. This gate is not configurable away.' },
            { title: 'Structured Deal Rooms', desc: 'Every round, every counter, and every concession is recorded in the room. When the deal closes you have the negotiation record, not a reconstructed email thread.' },
          ],
          visual: () => <NegotiateDemo url="app.strikescf.com/strike-rooms" deal={NEGOTIATION_DEAL} />,
        },
        {
          label: "From agreed terms to signature",
          items: [
            { title: 'AI-Drafted RFx & Contracts', desc: 'Strike AI drafts the document straight from the agreed terms and scores it clause by clause for completeness, flagging what is missing before it goes out.' },
            { title: 'Approval Workflow', desc: 'Route by value, category, or counterparty tier. Approvers see the guardrails, the rounds, and the delta from your opening position on one screen.' },
            { title: 'E-Signature', desc: 'Sign in place. The executed contract stays attached to the deal, so the terms that get reconciled and financed later are the terms that were actually signed.' },
          ],
          visual: () => <RfxDemo url="app.strikescf.com/strike-place/new-listing" doc={NEGOTIATION_RFX} />,
        },
      ]}
      vocabulary="NEGOTIATION GUARDRAILS · APPROVAL WORKFLOW · E-SIGNATURE · CONTRACT ORCHESTRATION · HUMAN-IN-THE-LOOP"
      crossTitle="From signed terms onward."
      crossLinks={[
        { path: '/product/invoice-po-matching', kicker: 'STAGE 04', label: 'Reconcile', desc: 'The signed terms become the baseline every invoice is matched against.' },
        { path: '/product/financing', kicker: 'STAGE 05', label: 'Finance', desc: 'An executed contract is the first thing a funder asks to see.' },
        { path: '/anchors', kicker: "WHO IT'S FOR", label: 'Anchor Corporates', desc: 'Program rules your treasury team sets and controls end to end.' },
      ]}
    />
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Stage 4 — Reconcile
   ══════════════════════════════════════════════════════════════════════ */
function ProductMatchingPage() {
  return (
    <ProductStagePage
      screen="ProductReconcile"
      stage={PRODUCT_STAGES[3]}
      h1="Invoices and purchase orders that check themselves."
      sub="Strike matches the invoice against the purchase order and the goods receipt line by line, clears what agrees, and routes only the exceptions to a person — with the discrepancy already identified."
      clusters={[
        {
          label: "Matching, line by line",
          items: [
            { title: 'PO-to-Invoice Matching', desc: 'Every invoice is matched against its purchase order automatically on arrival. Quantities, unit prices, and delivery terms are compared line by line, not header to header.' },
            { title: '2-Way and 3-Way Match', desc: 'Match invoice to PO, or bring the goods receipt in for a full three-way match. The tolerance thresholds are yours to set per category or per counterparty.' },
            { title: 'Commercial Invoice Automation', desc: 'Generate the commercial invoice from the deal itself, so the document that goes out already agrees with the PO it will later be matched against.' },
          ],
          visual: () => <MatchPanel stage="match" />,
        },
        {
          label: "When something doesn't agree",
          items: [
            { title: 'Shipment & Delivery Tracking', desc: 'Shipment milestones are tied to the deal, so the receipt side of the match is a live record rather than a document someone remembers to upload.' },
            { title: 'Exception Handling', desc: 'A line that fails the match does not stop the invoice — it isolates. Everything that agrees keeps moving while the exception is worked.' },
            { title: 'Dispute Management', desc: 'Disputes are raised, evidenced, and resolved against the specific line in question, with the full trail attached to the deal for audit.' },
          ],
          visual: () => <MatchPanel stage="exception" />,
        },
      ]}
      vocabulary="PO MATCHING · 2-WAY / 3-WAY MATCH · EXCEPTION HANDLING · DISPUTE MANAGEMENT · COMMERCIAL INVOICE AUTOMATION"
      stats={[
        {
          label: "Caught before it's a missed payment",
          value: '25–40%',
          compare: 'What uncorrected manual errors add on top of direct processing cost',
          source: 'Ardent Partners, AP Metrics That Matter, 2025',
          tone: 'bad',
        },
        {
          label: 'Matched on arrival, paid on time',
          value: '44%',
          compare: 'of B2B invoices in the US are overdue right now',
          source: 'PYMNTS/Visa Growth Corporates Working Capital Index, 2025',
          tone: 'warn',
        },
      ]}
      statsNote="These are industry benchmarks for manual AP processing, published by Ardent Partners — not Strike SCF measurements. We will publish our own once pilot data exists."
      crossTitle="A matched invoice is a financeable invoice."
      crossLinks={[
        { path: '/product/financing', kicker: 'STAGE 05', label: 'Finance', desc: 'Turn the match into working capital — same day, at a posted rate.' },
        { path: '/product/financing#invoice-factoring', kicker: 'STAGE 05', label: 'Invoice Factoring', desc: 'Sell the approved receivable rather than wait out the terms.' },
        { path: '/product/operations', kicker: 'ACROSS EVERY STAGE', label: 'Operate & Report', desc: 'Exception volume, cycle time, and spend on one executive view.' },
      ]}
    />
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Stage 5 — Finance
   ══════════════════════════════════════════════════════════════════════ */
function ProductFinancingPage() {
  return (
    <ProductStagePage
      screen="ProductFinance"
      stage={PRODUCT_STAGES[4]}
      h1="Turn the match into working capital."
      sub="Once an invoice clears the match, financing it is a decision rather than a project. Four structures run on the same platform, and Strike AI reads your live cash position to tell you when financing a receivable beats waiting for it."
      clusters={[
        {
          label: "The financing decision",
          items: [
            { title: 'Four Financing Structures', desc: 'Reverse factoring, invoice factoring, PO financing, and dynamic discounting run side by side on one deal record. The structure changes; the underlying data does not.' },
            { title: 'AI Cash-Position Monitoring', desc: 'Strike AI reads your cash position live from your ERP and flags when financing a receivable is the better move for working capital than holding it to maturity.' },
          ],
          visual: () => <FinanceDemo url="app.strikescf.com/my-deals" deal={FINANCING_DEAL} />,
        },
        {
          label: "Limits, tenor, and the cycle",
          items: [
            { title: 'Program Utilisation', desc: 'Limits are tracked at bank, program, anchor, and supplier level. Utilisation, headroom, and average tenor are visible before a request is raised, not after it is declined.' },
            { title: 'Advance Rate & Tenor', desc: 'Posted advance rates and tenors per program, shown to the supplier before they elect. No renegotiation after acceptance and no rate that only appears on the remittance.' },
            { title: 'Cash Conversion Cycle', desc: 'DSO on the supplier side and DPO on the buyer side move from the same set of transactions, so both parties can see what a program actually does to their cycle.' },
          ],
          visual: () => <UtilisationPanel />,
        },
      ]}
      vocabulary="APPROVED PAYABLES FINANCE · RECEIVABLES DISCOUNTING · TENOR · ADVANCE RATE · PROGRAM UTILISATION · CASH CONVERSION CYCLE · DSO · DPO"
      stats={[
        {
          label: 'Why Strike exists',
          value: '$2.5 trillion',
          compare: 'Global trade finance gap — ~10% of world trade goes unfunded',
          source: 'Asian Development Bank, Global Trade Finance Gap Survey, 2025',
          tone: 'bad',
        },
        {
          label: 'No separate review cycle',
          value: '3.1 days',
          compare: 'Best-in-class invoice processing time — manual workflows run 17.4 days',
          source: 'Ardent Partners, AP Metrics That Matter, 2025',
          tone: 'good',
        },
        {
          label: 'A standing tool, not a last resort',
          value: '+15%',
          compare: 'Growth in adoption of external working-capital solutions in 2024',
          source: 'PYMNTS/Visa Growth Corporates Working Capital Index, 2025',
          tone: 'good',
        },
      ]}
      statsNote="Suppliers are typically paid in around 10 days on early-payment programs, versus standard 30–45 day terms, and buyers on dynamic discounting programs typically capture annualised returns in the 8–18% range. Both are general industry practice rather than findings from a single study."
      extra={() => <FinancingStructures />}
      crossTitle="Where the financing decision comes from."
      crossLinks={[
        { path: '/product/invoice-po-matching', kicker: 'STAGE 04', label: 'Reconcile', desc: 'A matched invoice is what makes a receivable financeable in the first place.' },
        { path: '/product/counterparty-risk', kicker: 'STAGE 02', label: 'Verify & Score', desc: 'The same counterparty grade a funder underwrites against when it prices a facility.' },
        { path: '/who-its-for', kicker: "WHO IT'S FOR", label: 'Banks and anchors', desc: 'How a funder and a buyer each see the same programme.' },
      ]}
    />
  );
}

const FINANCING_STRUCTURES = [
  {
    slug: 'reverse-factoring',
    label: 'Reverse Factoring',
    lede: "Buyer-led supply chain finance. The anchor's creditworthiness unlocks better rates for every supplier in their network, funded by Strike's bank and credit partners.",
    Diagram: () => <ReverseFactoringDiagram />,
    steps: [
      { title: 'Anchor Configures the Program', desc: 'The anchor sets supplier eligibility, payment terms, pricing tiers, and currencies. The program is live in days, not quarters.' },
      { title: 'Suppliers Self-Elect', desc: 'Enrolled suppliers see early payment offers in the portal as their invoices are approved. They choose which invoices to accelerate and which to hold to maturity.' },
      { title: 'Funder Pays Suppliers Early', desc: 'Strike routes the early payment to the supplier from the approved funding bank or private credit partner. The funder carries the credit risk, not the anchor.' },
      { title: 'Anchor Pays on Original Terms', desc: 'The anchor pays the funder on the original invoice maturity date. DSO is extended, working capital is optimised, and balance sheet treatment remains off-balance-sheet.' },
    ],
  },
  {
    slug: 'invoice-factoring',
    label: 'Invoice Factoring',
    lede: "Turn anchor-approved receivables into immediate working capital. No waiting out the payment terms, and no separate credit facility — the anchor's approval on the invoice is the signal that unlocks financing.",
    Diagram: () => <InvoiceFactoringDiagram />,
    steps: [
      { title: 'Invoice Submitted', desc: 'The supplier submits an approved invoice through the Strike portal or via ERP integration. Anchor approval is already confirmed.' },
      { title: 'Instant Offer', desc: 'Strike AI scores the invoice and returns a factoring offer with the exact net amount and annualised rate. No negotiation, no callbacks.' },
      { title: 'Supplier Accepts', desc: "One click to accept. Funds are released to the supplier's nominated account the same business day in most markets." },
      { title: 'Anchor Settles at Maturity', desc: "On the original payment due date, the anchor settles the full invoice amount directly to the funder. No change to the anchor's payment process." },
    ],
  },
  {
    slug: 'po-financing',
    label: 'PO Financing',
    lede: 'Finance supplier purchase orders at the point of commitment, not after delivery. Suppliers get the cash to produce; buyers protect their supply chain before disruption hits.',
    Diagram: () => <POFinancingDiagram />,
    steps: [
      { title: 'Buyer Issues PO', desc: 'The anchor buyer issues a purchase order to a strategic supplier. The PO is submitted to Strike for financing consideration.' },
      { title: 'Strike Scores the Opportunity', desc: 'Strike AI evaluates the supplier, the buyer relationship, the PO value, and the risk profile, and returns a financing recommendation.' },
      { title: 'Funder Approves and Disburses', desc: 'The approved funder releases capital directly to the supplier against the PO. The supplier can begin production without waiting for invoice approval.' },
      { title: 'Repayment on Invoice Maturity', desc: "When the buyer receives goods and approves the invoice, repayment flows through Strike back to the funder. The anchor's balance sheet is not extended." },
    ],
  },
  {
    slug: 'dynamic-discounting',
    label: 'Dynamic Discounting',
    lede: 'Anchor buyers offer early payment using their own balance sheet, then capture the early payment discount as direct income. No bank required.',
    Diagram: () => <DiscountingFlowDiagram />,
    steps: [
      { title: 'Buyer Sets the Discount Rate', desc: 'The anchor defines a sliding scale of discount rates by days paid early. The earlier the payment, the higher the annualised yield captured by the buyer.' },
      { title: 'Suppliers Choose Their Terms', desc: 'Suppliers see the available offer in the portal and choose when to accept. Full transparency on the rate and the net amount at every point.' },
      { title: 'Instant Settlement from Buyer Cash', desc: "Strike handles the settlement. The buyer's cash is deployed, the supplier receives funds, and the transaction is booked and reconciled automatically." },
      { title: 'Yield Captured by the Buyer', desc: 'The early payment discount is recorded as income for the anchor. Strike produces the audit trail and reconciliation reports for your treasury team.' },
    ],
  },
];

/* The four structures, tabbed. Each keeps its own hash so the retired
   /programs/* URLs can land directly on the right one. */
function FinancingStructures() {
  /* Resolve from the hash, falling back to a retired /programs/<slug> path.
     React runs child effects before parent ones, so this component's popstate
     handler fires before the router has rewritten a retired URL — reading the
     path directly keeps the right tab selected regardless of that ordering. */
  const fromHash = () => {
    const path = window.location.pathname || '';
    const slug = (window.location.hash || '').replace('#', '')
      || (path.indexOf('/programs/') === 0 ? path.slice('/programs/'.length) : '');
    return Math.max(0, FINANCING_STRUCTURES.findIndex(f => f.slug === slug));
  };
  const [active, setActive] = useState(fromHash);

  useEffect(() => {
    const on = () => setActive(fromHash());
    window.addEventListener('hashchange', on);
    window.addEventListener('popstate', on);
    return () => { window.removeEventListener('hashchange', on); window.removeEventListener('popstate', on); };
  }, []);

  const f = FINANCING_STRUCTURES[active];

  return (
    <section className="section off" id="structures">
      <div className="container">
        <SectionHead title="Four structures, one deal record." />
        <div className="persona-tabs structure-tabs" role="tablist" aria-label="Financing structures">
          {FINANCING_STRUCTURES.map((x, i) => (
            <a
              key={x.slug}
              href={'#' + x.slug}
              role="tab"
              aria-selected={i === active}
              className={'persona-tab' + (i === active ? ' active' : '')}
              onClick={(e) => { e.preventDefault(); history.replaceState({}, '', '#' + x.slug); setActive(i); }}
            >
              {x.label}
            </a>
          ))}
        </div>

        <div className="row-2" style={{ alignItems: 'start', marginTop: 40 }}>
          <div>
            <h3 className="display-sm" style={{ marginBottom: 16 }}>{f.label} on Strike SCF.</h3>
            <p className="body body-gray" style={{ maxWidth: '52ch', marginBottom: 28 }}>{f.lede}</p>
            <FeatureList items={f.steps} />
          </div>
          <f.Diagram />
        </div>
      </div>
    </section>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   Cross-cutting layer — Operate & Report
   ══════════════════════════════════════════════════════════════════════ */
function ProductOperationsPage() {
  return (
    <ProductStagePage
      screen="ProductOperations"
      stage={PRODUCT_LAYER}
      h1="Every stage, one system of record."
      sub="Sourcing, verification, negotiation, reconciliation, and financing all write to the same record. Operate & Report is where you govern it — budgets, controls, workflow, and the reporting your board actually asks for."
      clusters={[
        {
          label: "The board the work runs on",
          items: [
            { title: 'Team & AI-Agent Workflow Boards', desc: 'Design the workflow your team runs on, then deploy an agent onto a stage the way you would assign it to a teammate. Humans and agents work the same board.' },
            { title: 'Budgets & Spend Controls', desc: 'Budgets are enforced at the point of intake rather than reported on after the fact. A request that would breach one is stopped and routed, not silently approved.' },
            { title: 'Product & Catalog Management', desc: 'Maintain the catalogue buyers order from, so repeat purchasing runs against known items and agreed prices instead of a fresh sourcing event each time.' },
          ],
          visual: () => (
            <MiniFrame url="app.strikescf.com/board" className="mini-frame-lg askd-frame">
              <WorkflowBoardVisual board={OPERATIONS_BOARD} />
            </MiniFrame>
          ),
        },
        {
          label: "What it reports",
          items: [
            { title: 'Executive Reporting & Analytics', desc: 'Cycle time, spend under management, exception rates, and program utilisation on one view, with scheduled reports for the recipients who never log in.' },
            { title: 'Multi-Entity Controls', desc: 'Separate entities, currencies, and approval hierarchies under one roof, with consolidated reporting across all of them for larger organisations.' },
          ],
          visual: () => <ExecReportPanel />,
        },
      ]}
      vocabulary="SPEND CONTROLS · WORKFLOW AUTOMATION · BUDGET GOVERNANCE · EXECUTIVE REPORTING · MULTI-ENTITY"
      crossTitle="What it governs."
      crossLinks={[
        { path: '/product', kicker: 'PRODUCT', label: 'The full pipeline', desc: 'All five stages, and how a request moves through them.' },
        { path: '/pricing', kicker: 'PRICING', label: 'Plans', desc: 'Which controls land in Growth, and which need Enterprise.' },
        { path: '/banks', kicker: "WHO IT'S FOR", label: 'Banks', desc: 'Audit logs, SSO/SCIM, and compliance exports for regulated funders.' },
      ]}
    />
  );
}

/* ══════════════════════════════════════════════════════════════════════
   /pricing — informational only. No dollar figures, no checkout.
   Tiers and feature labels mirror the shipped tier system; re-sync this
   list against the product's FEATURE_LABELS if tiers change.
   ══════════════════════════════════════════════════════════════════════ */
const PRICING_TIERS = [
  {
    name: 'STARTER',
    tagline: 'Free to start',
    summary: 'Core procurement — enough to run real deals end to end.',
    inherits: null,
    features: [
      'Deals & marketplace (manual)',
      'Strike Rooms',
      'Requisitions & cases',
      'One network',
      'KYB & risk scoring',
      'Standard reporting',
    ],
    cta: 'waitlist',
  },
  {
    name: 'GROWTH',
    tagline: 'For growing procurement teams',
    summary: 'Strike AI, your ERP, and the controls a real team needs.',
    inherits: 'Everything in Starter, plus:',
    features: [
      'Strike AI chat & insights',
      'One ERP connector',
      'Unlimited networks',
      'Approval policies',
      'Budgets',
      'Invoices',
      'Product catalog',
      'Contracts',
      'Team workflow board',
      'CEO cockpit',
      'Custom & scheduled reporting',
    ],
    cta: 'sales',
    featured: true,
  },
  {
    name: 'ENTERPRISE',
    tagline: 'For multi-entity, compliance-heavy organizations',
    summary: 'Multi-entity scale, provisioning, and the API.',
    inherits: 'Everything in Growth, plus:',
    features: [
      'Multi-ERP connectors',
      'SSO/SCIM provisioning',
      'Public API access',
      'Supply graph analytics',
      'Audit logs & compliance exports',
    ],
    cta: 'sales',
  },
];

function PricingPage() {
  return (
    <div className="page" data-screen-label="Pricing">
      <section className="hero" style={{ paddingBottom: 48 }}>
        <div className="container">
          <h1 className="display-lg" style={{ maxWidth: '16ch' }}>Start free. Scale when the volume does.</h1>
          <p className="hero-sub" style={{ maxWidth: '58ch' }}>
            Three tiers, built around what your team actually runs — not around seat count.
            Pricing is tailored to program size and financing volume, so the conversation
            starts with your pipeline rather than a price list.
          </p>
        </div>
      </section>

      <section className="section">
        <div className="container">
          <div className="pricing-grid">
            {PRICING_TIERS.map(t => (
              <div key={t.name} className={'pricing-tier' + (t.featured ? ' featured' : '')}>
                {t.featured && <div className="pricing-tier-flag">MOST TEAMS START HERE</div>}
                <div className="pricing-tier-name">{t.name}</div>
                <div className="pricing-tier-tagline">{t.tagline}</div>
                <p className="pricing-tier-summary">{t.summary}</p>
                {t.inherits && <div className="pricing-tier-inherits">{t.inherits}</div>}
                <ul className="pricing-tier-features">
                  {t.features.map(f => <li key={f}>{f}</li>)}
                </ul>
                <div className="pricing-tier-cta">
                  {t.cta === 'waitlist' ? (
                    <a href="/waitlist" className="btn btn-blue btn-arrow" onClick={(e) => navTo(e, '/waitlist')}>{CTA_WAITLIST}</a>
                  ) : (
                    <a href="/contact" className="btn btn-ghost btn-arrow" onClick={(e) => navTo(e, '/contact')}>{CTA_SALES}</a>
                  )}
                </div>
              </div>
            ))}
          </div>
          <p className="pricing-footnote">
            Pricing is tailored to program size and financing volume — talk to us.
          </p>
        </div>
      </section>

      <section className="section">
        <div className="container">
          <SectionHead title="Common questions." />
          <div className="pricing-faq">
            {[
              { q: 'Why are there no prices on this page?', a: 'Because a per-seat number would be misleading. Pricing tracks program size and financing volume, which differ by an order of magnitude between a single-anchor pilot and a multi-entity program. Tell us your pipeline and we will quote against it.' },
              { q: 'Can I start on Starter and move up later?', a: 'Yes — that is the intended path. Starter runs real deals end to end. Growth adds Strike AI, your ERP connector, and the approval and budget controls a larger team needs.' },
              { q: 'What counts as a network?', a: 'A network is a trading community — your suppliers, your buyers, or a bank program and its participants. Starter includes one; Growth and Enterprise are unlimited.' },
              { q: 'Do the financing programs cost extra?', a: 'Financing is priced separately from the platform, because the economics belong to the funder and the program rather than to the software. The four structures are covered on the Programs pages.' },
            ].map(f => (
              <div key={f.q} className="pricing-faq-item">
                <div className="pricing-faq-q">{f.q}</div>
                <div className="pricing-faq-a">{f.a}</div>
              </div>
            ))}
          </div>
        </div>
      </section>

      <section className="section off">
        <div className="container">
          <div className="row-2" style={{ alignItems: 'center' }}>
            <h2 className="display-md" style={{ maxWidth: '20ch' }}>Ready when you are.</h2>
            <div>
              <p className="body body-gray" style={{ maxWidth: '44ch', marginBottom: 28 }}>
                <WaitlistCounter />.
              </p>
              <CtaPair />
            </div>
          </div>
        </div>
      </section>
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════════
   /waitlist
   ══════════════════════════════════════════════════════════════════════ */
function WaitlistPage() {
  return (
    <div className="page" data-screen-label="Waitlist">
      <section className="hero" style={{ paddingBottom: 48 }}>
        <div className="container">
          <h1 className="display-lg" style={{ maxWidth: '18ch' }}>Get on the Strike SCF waitlist.</h1>
          <p className="hero-sub" style={{ maxWidth: '58ch' }}>
            We're onboarding pilot cohorts rather than opening the doors all at once.
            Tell us who you are and what you're trying to solve, and we'll reach out as
            slots open.
          </p>
          <div className="waitlist-counter-badge"><WaitlistCounter /></div>
        </div>
      </section>

      <section className="section">
        <div className="container">
          <div className="waitlist-col">
            <WaitlistForm />
            <div className="contact-block" style={{ marginTop: 40 }}>
              <a className="contact-email" href="mailto:info@strikescf.com">info@strikescf.com</a>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
}

Object.assign(window, {
  pickActiveCluster,
  FinancingStructures,
  SourcingEventsPanel,
  UtilisationPanel,
  ExecReportPanel,
  ProductOverviewPage,
  ProductSourcingPage,
  ProductRiskPage,
  ProductNegotiationPage,
  ProductMatchingPage,
  ProductFinancingPage,
  ProductOperationsPage,
  PricingPage,
  WaitlistPage,
  MatchPanel,
});
