// TradeXchange — landing sections + App shell.
// Sections: Hero, §01 Capabilities, §02 Verification, §03 Brokers,
//           §04 Risk check, §05 Pricing, §06 FAQ, footer.
//
// All copy is now client-facing by default. The internal/technical voice
// is preserved as an "Engineer" tweak so you can compare or revert.

// ── Copy library — keyed by voice ──────────────────────────────
// trader   → what a customer reads. Plain English, no internal jargon.
// engineer → original technical voice. Still useful for staff/recruiters.
const COPY = {
  trader: {
    ticker: ['TRADEXCHANGE', 'COUNCIL · 25 NFTs ON BASE', 'ALPACA · MOOMOO', 'VERIFIED PICKS ONLY', 'RECEIPTS ON EVERY TRADE'],
    systemOnline: 'MARKETS LIVE',
    nav: [
      { n: '01', label: 'The Council', href: 'Council.html' },
      { n: '02', label: 'Verification', href: '#gate' },
      { n: '03', label: 'Brokers',      href: '#brokers' },
      { n: '04', label: 'Risk check',   href: '#quantum' },
      { n: '05', label: 'Pricing',      href: '#pricing' },
    ],
    navCta: 'Open terminal',
    hero: {
      eyebrow: 'NEW · v2.1',
      tag: 'tradexchange.cc · multi-broker · verified picks',
      headline1: 'One terminal.',
      headline2: 'Two brokers.',
      headline3: 'Only the picks',
      headline3Accent: 'we really agree on.',
      sub: 'Trade Alpaca or MooMoo from one screen. Every signal is verified before it reaches you. Risky orders are refused, never silently filled. A receipt on every trade.',
      cta1: 'Open terminal',
      cta2: 'See how verification works',
      specs: [
        { k: 'CONVICTION',  v: '92%+',       sub: 'high-conviction picks only' },
        { k: 'RESPONSE',    v: '< 1 sec',    sub: 'verified in real time' },
        { k: 'RECEIPTS',    v: '90 days',    sub: 'audit any trade later' },
        { k: 'RISK CHECK',  v: 'every order', sub: 'before it hits the broker' },
      ],
      figLabel: 'FIG · 01 — VERIFICATION LIVE · 5 ORDERS',
      figSub: 'AUTO-CYCLING · TAP TO PIN',
    },
    capabilities: {
      eyebrow: 'What it is',
      title: 'A verified-picks trading platform — not a router.',
      intro: 'Most platforms accept your order and forward it. TradeXchange runs every signal and every order through two independent checks before your broker sees a thing.',
      cards: [
        { n: '01', t: 'Two brokers, one screen',     d: 'Trade Alpaca for US stocks and MooMoo for US, Hong Kong, and China — from the same interface. Switch broker per order, per strategy, or set a default.',                                                meta: 'Alpaca + MooMoo' },
        { n: '02', t: 'Verified by the Council of 25',  d: 'Every pick fans out to the Council — 25 named on-chain agents on Base, each one an NFT with its own smart wallet. You can audit every vote, every signer, every receipt yourself. No backend trust required.',                       meta: 'Council · 25 verifiers' },
        { n: '03', t: 'Risk check on every order',   d: 'After a pick passes verification, every order runs a final risk check before it hits the broker. Clear, caution, risky, or refused. Risky and refused both block the order.',                             meta: 'On every order' },
        { n: '04', t: 'A receipt for every trade',   d: 'Every approval and refusal generates a signed receipt with the full verification trace. Stored 90 days. Compliance can verify any fill independently.',                                                   meta: '90-day audit window' },
      ],
    },
    gate: {
      eyebrow: 'How verification works',
      title: 'The Council of 25 verifies every pick.',
      intro: 'Each signal is voted on by 25 named on-chain agents — every one an NFT on Base, every one a smart wallet that signs its own vote. The bar is set extremely high. We do not show you the pick unless the Council really agrees on it.',
      cells: [
        { k: 'WHY WE REFUSE',         v: 'If the Council cannot agree, we never silently let an order through. Refused, every time.' },
        { k: 'CONVICTION BAR',         v: 'Picks of the Day · 92%+ Council agreement. Full Sweep · 96%+ Council agreement. We do not sell sub-conviction picks.' },
        { k: 'WHY THE BAR IS HIGH',    v: 'Most models agree on coin flips. Anything under 90% is noise dressed up. We only speak when the 25 verifiers really agree.' },
        { k: 'RECEIPTS',               v: 'Every approve and refusal is a signed receipt anchored to a Base block. Stored 90 days. You can audit any trade — or non-trade — yourself.' },
      ],
    },
    brokers: {
      eyebrow: 'Brokers',
      title: 'Trade Alpaca and MooMoo from one screen.',
      intro: 'Same interface, same verification, same receipts — whichever broker you pick. Interactive Brokers is the most requested and is next on the list.',
    },
    quantum: {
      eyebrow: 'The second check',
      title: 'A risk pass before every order.',
      intro: 'After a pick passes verification, every order runs through a final risk check before your broker sees it. Outcomes are clear, caution, risky, or refused. Risky and refused both block the order — always.',
    },
    pricing: {
      eyebrow: 'Pricing',
      title: 'Pay per session. Verified picks only.',
      intro: 'No subscriptions. No commitments. Pay only for the sessions you want to trade. Both tiers ship with an extremely high conviction bar so you only see picks the panel really agrees on.',
      featuredTag: 'MOST CHOSEN',
      tiers: {
        pick5: {
          tag: 'DAY PASS',
          // href set by scripts/provision_stripe_per_session.py once products are live.
          // Until then, fall back to mailto so prospects can still reach sales.
          href: 'mailto:ip@epochcore.com?subject=TradeXchange%20-%20Picks%20of%20the%20Day%20%2410%2Fsession',
          name: 'Picks of the Day',
          price: '$10', unit: '/ session',
          tagline: 'Five high-conviction picks unique to today. One trading session.',
          perks: [
            '5 picks unique to today',
            'Daily timeframe — buy-and-hold style entries',
            'Only 92%+ conviction picks',
            'Live at the open, valid through close',
            'No commitment — session ends at 4 PM ET',
          ],
          meta: ['5 PICKS', 'DAILY', 'PER SESSION'],
        },
        pro: {
          tag: 'MOST CHOSEN',
          // href set by scripts/provision_stripe_per_session.py once products are live.
          // Until then, fall back to mailto so prospects can still reach sales.
          href: 'mailto:ip@epochcore.com?subject=TradeXchange%20-%20Full%20Sweep%20%2420%2Fsession',
          name: 'Full Sweep',
          price: '$20', unit: '/ session',
          tagline: 'Every pattern, every level, every block trade — plus an outcome simulation on every alert.',
          perks: [
            'Every pattern, support, and resistance level',
            'Moving averages, block trades, and order flow',
            'Sub-daily alerts — hourly, 5-min, and 30-sec',
            'Outcome simulation on every alert',
            'Only 96%+ conviction — noise rejected',
            'Live for the whole trading session',
          ],
          meta: ['ALL SIGNALS', 'SUB-DAILY', 'SIMULATED'],
        },
      },
      profitShareNote: 'Institutional clients can opt into a 15–30% profit-share addendum in lieu of session fees.',
      profitShareLink: 'Email about profit-share →',
    },
    faq: {
      eyebrow: 'Questions',
      title: 'Things traders ask.',
      intro: 'The plain-English version. If yours is not here, email us — we will answer it.',
      items: [
        { q: 'Is this investment advice?', a: 'No. The verification panel refuses orders but never originates them. You retain full responsibility for trading decisions and regulatory compliance in your jurisdiction.' },
        { q: 'What happens when my order is refused?', a: 'You see a clear refusal with the panel vote, the reason, and a receipt ID. The order never reaches your broker. The receipt stays on file for 90 days so you can review it later.' },
        { q: 'Can I override a refusal?', a: 'No. There is no "approve anyway" button. Verification runs before your broker ever sees the order. It is an internal control, not a suggestion.' },
        { q: 'Can I lower the conviction bar?', a: 'No. Both sessions ship with a deliberately high bar — 92% for Picks of the Day, 96% for Full Sweep. Lower bars let in noise. If you want raw, unverified signals, that is not what we sell.' },
        { q: 'What brokers will you add next?', a: 'Interactive Brokers is the most-requested and is next on the list. Crypto brokers are intentionally out of scope in v1 — different settlement model.' },
        { q: 'How do you handle outages?', a: 'If the verification panel cannot reach a verdict in time, every order is refused with a clear reason — we never silently fill. If your broker goes down, you see their error unchanged.' },
        { q: 'Refunds?', a: 'Pro-rated on cancel for the current month. Disputes go through Stripe. We do not refund losses from trades the panel allowed — that is what the verification is for, but it is an internal control, not a guarantee.' },
      ],
    },
    footer: {
      tagline: 'A verified-picks multi-broker trading platform by EpochCore. Not investment advice.',
      legal: '© 2026 EPOCHCORE LLC · TRADEMARK PENDING · PATENT PENDING',
      version: 'v2.1 · UPDATED MAY 2026',
      cols: [
        { title: 'Council', links: [
          { l: 'The 25 agents',          href: 'Council.html#roster' },
          { l: 'How proof works',        href: 'Council.html#proof' },
          { l: 'Agreement matrix',       href: 'Council.html#matrix' },
          { l: 'Verify on Basescan ↗',   href: 'https://basescan.org/token/0x8d5b1246CA5fb193F9E76bAdB4405D5024DD431e' },
        ]},
        { title: 'Platform', links: [
          { l: 'How verification works', href: '#gate' },
          { l: 'Brokers',                href: '#brokers' },
          { l: 'Risk check',             href: '#quantum' },
          { l: 'System status',          href: '/__probe' },
        ]},
        { title: 'Engage', links: [
          { l: 'Open terminal',          href: '/app' },
          { l: 'Customer onboarding →',  href: 'Onboarding Flow.html' },
          { l: 'Pricing',                href: '#pricing' },
          { l: 'Profit-share (Inst.)',   href: 'mailto:ip@epochcore.com' },
        ]},
      ],
    },
  },
  engineer: {
    ticker: ['TRADEXCHANGE · v2', 'GATE · 26 AGENTS · 0.66', 'HQTS · 2159 Hz', 'φ · 1.618033988749895', 'MESH · 36 NODES', 'CLOUDFLARE-FRONTED'],
    systemOnline: 'SYSTEM ONLINE',
    nav: [
      { n: '01', label: 'The gate',  href: '#gate' },
      { n: '02', label: 'Brokers',   href: '#brokers' },
      { n: '03', label: 'Quantum',   href: '#quantum' },
      { n: '04', label: 'Pricing',   href: '#pricing' },
      { n: '05', label: 'FAQ',       href: '#faq' },
    ],
    navCta: 'Open terminal',
    hero: {
      eyebrow: 'NEW · v2.1 · OSCULATOR',
      tag: 'tradexchange.cc · MULTI-BROKER · CONSENSUS-GATED',
      headline1: 'One terminal.',
      headline2: 'Two brokers.',
      headline3: '26 agents',
      headline3Accent: 'between you and every fill.',
      sub: 'Place orders through Alpaca or MooMoo from a single interface. Every order fans out to 26 independent agents for a YES/NO vote before it reaches the broker — sub-250 ms, fail-closed, cryptographically audited.',
      cta1: 'Open terminal',
      cta2: 'See the gate',
      specs: [
        { k: 'THRESHOLD',  v: '≥ 0.92',      sub: 'signal-grade only' },
        { k: 'P99 LATENCY', v: '< 250ms',    sub: 'fail-closed' },
        { k: 'AUDIT',      v: '90 day',      sub: 'Ed25519 + ML-DSA' },
        { k: 'QUANTUM',    v: 'always-on',   sub: '14 algos · 200q' },
      ],
      figLabel: 'FIG · 01 — LIVE GATE · 5 ORDERS',
      figSub: 'AUTO-CYCLING · TAP TO PIN',
    },
    capabilities: {
      eyebrow: 'What it is',
      title: 'A gated trading platform, not a router.',
      intro: 'Most platforms accept your order and forward it. TradeXchange runs every order through two pre-trade gates — consensus and quantum verdict — before the broker sees a thing.',
      cards: [
        { n: '01', t: 'Multi-broker by design',    d: 'Alpaca for US equities. MooMoo for US, HK, CN. Switch broker per-order or per-strategy. Add new brokers by implementing one interface.', meta: '6-method adapter' },
        { n: '02', t: '26-agent consensus gate',    d: 'Every QSS signal fans out to 26 agents. We default to a 0.92+ threshold — at least 24 must agree before a pick surfaces. Noise gets rejected at the gate.', meta: 'p99 250ms · signal-grade' },
        { n: '03', t: 'Quantum verdict, always-on', d: 'A second gate runs the order through Q-Routed: 14 proprietary algorithms, 200q circuit-knitting, 50q MPS tensor reduction, IBM Runtime overflow.', meta: '$0.013/shot · $6k pool' },
        { n: '04', t: 'Cryptographic audit',         d: 'Every successful response is signed with Ed25519 + ML-DSA-65. Consensus proofs stored 90 days. Compliance can verify any fill independently.', meta: '90-day window' },
      ],
    },
    gate: {
      eyebrow: 'The gate',
      title: 'Pre-trade consensus. Always.',
      intro: 'Every signal is voted on by 26 independent agents. The bar is set extremely high — 24 of 26 must agree before a pick is shown. This is how we keep signal in and noise out.',
      cells: [
        { k: 'WHY FAIL-CLOSED',  v: 'A stalled gate cannot silently let orders through. Timeout = refuse, always.' },
        { k: 'THRESHOLD POLICY',  v: 'Picks of the Day · 0.92  ·  Full Sweep · 0.96  ·  Both refuse below the bar. No "approve anyway" path.' },
        { k: 'WHY SO HIGH',       v: 'Default 0.66 is fine for raw plumbing. For trading picks, sub-0.9 is noise. We tuned to where the swarm only speaks when it really agrees.' },
        { k: 'AUDIT',             v: 'consensus_proof persisted for 90 days. order:<broker_id> references back via consensus_proof_id.' },
      ],
    },
    brokers: {
      eyebrow: 'Brokers',
      title: 'One adapter interface. Any broker.',
      intro: 'Pick a broker, get the same normalized shapes back. Order routing, consensus, and audit work identically across them. Adding a new broker is one file + one registry entry.',
    },
    quantum: {
      eyebrow: 'The second gate',
      title: 'Quantum verdict on every order.',
      intro: "After consensus approves, the order is routed by Q-Routed's Gödel Task Router across 14 proprietary algorithms. Verdicts are { clear · caution · risky · refused }. risky and refused block — always.",
    },
    pricing: {
      eyebrow: 'Pricing',
      title: 'Pay per session. Signal only.',
      intro: 'QuantumSwarmSearch (QSS) runs an extremely high consensus threshold so you only see what the swarm agrees on. No subscription · no commitment · pay only for the sessions you trade.',
      featuredTag: 'MOST CHOSEN',
      tiers: {
        pick5: {
          tag: 'DAY PASS',
          name: 'Picks of the Day',
          price: '$10', unit: '/ session',
          tagline: "Five high-conviction picks unique to today's QuantumSwarmSearch. One trading session.",
          perks: [
            "5 picks unique to today's QSS",
            'Daily-timeframe consensus only',
            'Threshold ≥ 0.92 · signal-grade picks only',
            'Refreshes at the open · valid through close',
            'No commitment · session ends at 16:00 ET',
          ],
          meta: ['5 picks', 'Daily QSS', 'Per session'],
        },
        pro: {
          tag: 'MOST CHOSEN',
          name: 'Full Sweep',
          price: '$20', unit: '/ session',
          tagline: 'Every pattern, line, level, and block trade — plus a Monte Carlo simulation of every outcome.',
          perks: [
            'QSS of every pattern · support · resistance',
            'All moving averages · block trades · order flow',
            'Sub-daily alerts · hourly · 5-min · 30-sec',
            'Batch Quantum Monte Carlo on every outcome',
            'Threshold ≥ 0.96 · noise-rejecting',
            'Live for the whole trading session',
          ],
          meta: ['Full QSS', 'Sub-daily', 'QMC batch'],
        },
      },
      profitShareNote: 'Institutional clients can opt into a 15–30% profit-share addendum in lieu of monthly fees.',
      profitShareLink: 'Email IP about profit-share →',
    },
    faq: {
      eyebrow: 'Questions',
      title: 'Things buyers ask.',
      intro: 'The honest, technical version. If yours is not here, email us — we will answer it in plain English.',
      items: [
        { q: 'Is this investment advice?', a: 'No. The consensus gate refuses orders but never originates them. You retain full responsibility for trading decisions and regulatory compliance in your jurisdiction.' },
        { q: 'What happens when the gate refuses my order?', a: 'You get HTTP 412 Precondition Failed with the full consensus_proof in the body — agent vote breakdown, ratio, latency, proof_id. The proof is stored 90 days for audit. The broker never sees the order.' },
        { q: 'Can I bypass the gate?', a: 'No. The gate runs server-side immediately before broker placement, after request validation. There is no "approve anyway" path on the user side. CONSENSUS_FAIL_OPEN=true exists for debug only and is never set in production.' },
        { q: 'Can I tune the threshold?', a: 'No. Both sessions ship with an extremely high default (≥0.92 for Picks of the Day, ≥0.96 for Full Sweep). Lowering it would let in the noise the swarm is supposed to reject. If you want lower threshold raw signals, that is not what we sell.' },
        { q: 'What brokers will you add next?', a: 'Adapter interface is documented; we add brokers when a paying tenant asks. Interactive Brokers is the most-requested. Crypto brokers are intentionally out of scope in v1 — different settlement model.' },
        { q: 'How do you handle outages?', a: 'Gateway p95 < 50ms. Consensus p99 < 250ms. If COMETAGENT degrades, every order refuses with reason=consensus_unavailable — fail-closed, never silently fill. Broker outages return 503 from the adapter, surfaced unchanged.' },
        { q: 'Refunds?', a: 'Pro-rated on cancel for the current month. Disputes are handled in Stripe. We do not refund losses from trades that the gate allowed — that is what the gate is for, but it is an internal control, not a guarantee.' },
      ],
    },
    footer: {
      tagline: 'A consensus-gated multi-broker trading platform by EpochCore. Built on Cloudflare Workers. Patent pending. Not investment advice.',
      legal: '© 2026 EPOCHCORE LLC · TRADEMARK PENDING · PATENT PENDING',
      version: 'Platform v2.2 · dual-root anchored',
      cols: [
        { title: 'Platform', links: [
          { l: 'The gate',          href: '#gate' },
          { l: 'Brokers',           href: '#brokers' },
          { l: 'Quantum verdict',   href: '#quantum' },
          { l: 'Status / probe',    href: '/__probe' },
        ]},
        { title: 'Engage', links: [
          { l: 'Open terminal',          href: '/app' },
          { l: 'Customer onboarding →',  href: 'Onboarding Flow.html' },
          { l: 'Pricing',                href: '#pricing' },
          { l: 'Profit-share (Inst.)',   href: 'mailto:ip@epochcore.com' },
        ]},
        { title: 'Identifiers', links: [
          { l: 'HQTS · 2159.0 Hz',       href: '#' },
          { l: 'Flash sync · 7777.77 Hz', href: '#' },
          { l: 'Mesh · 36 nodes',         href: '#' },
          { l: 'φ · 1.618033988…',        href: '#' },
        ]},
      ],
    },
  },
};

// Fallback CTAs used when a tier object doesn't define its own .href.
// Per-session products are NOT yet provisioned in Stripe (display = $10/$20,
// but the legacy live products are $99/$497 monthly — wiring them here would
// charge monthly when prospects clicked "$10 session"). Mailto fallback
// reaches sales without misrepresenting price. Once
// scripts/provision_stripe_per_session.py runs successfully, it writes
// per-tier .href fields onto the tier objects (which take priority below).
const STRIPE_URLS = {
  pick5: 'mailto:ip@epochcore.com?subject=TradeXchange%20-%20Picks%20of%20the%20Day%20%2410%2Fsession',
  pro:   'mailto:ip@epochcore.com?subject=TradeXchange%20-%20Full%20Sweep%20%2420%2Fsession',
};

// ── Top ticker bar ─────────────────────────────────────────────
function TopTicker({ copy }) {
  return (
    <div style={{
      background: T.ink, color: T.textInv,
      borderBottom: `1px solid ${T.inkBorder}`, padding: '8px 0',
    }}>
      <div style={{
        maxWidth: 1240, margin: '0 auto', padding: '0 32px',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 24,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 22, flexWrap: 'wrap' }}>
          <a href="../Portfolio.html" style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8 }}>
            <Mono color={T.muteInv} size={10}>← EPOCHCORE</Mono>
          </a>
          <span style={{ width: 1, height: 10, background: 'rgba(244,244,240,0.18)' }}/>
          {copy.ticker.map((t, i) => (
            <React.Fragment key={t}>
              {i > 0 && <span style={{ width: 1, height: 10, background: 'rgba(244,244,240,0.18)' }}/>}
              <Mono color={T.muteInv} size={10}>{t}</Mono>
            </React.Fragment>
          ))}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, whiteSpace: 'nowrap' }}>
          <Pulse color={T.allow} size={6}/>
          <Mono color={T.allow} size={10} weight={500}>{copy.systemOnline}</Mono>
        </div>
      </div>
    </div>
  );
}

// ── Sticky nav ─────────────────────────────────────────────────
function Nav({ copy }) {
  return (
    <nav style={{
      position: 'sticky', top: 0, zIndex: 50,
      background: 'rgba(10,11,14,0.85)', color: T.textInv,
      backdropFilter: 'saturate(180%) blur(16px)',
      WebkitBackdropFilter: 'saturate(180%) blur(16px)',
      borderBottom: `1px solid ${T.inkBorder}`,
    }}>
      <div style={{
        maxWidth: 1240, margin: '0 auto', padding: '0 32px',
        height: 64, display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      }}>
        <a href="#top" style={{ textDecoration: 'none' }}><Wordmark size={15} color={T.textInv}/></a>
        <div style={{ display: 'flex', gap: 28 }}>
          {copy.nav.map(it => (
            <a key={it.n} href={it.href} style={{
              textDecoration: 'none', color: T.muteInv,
              fontFamily: GMONO, fontSize: 11, letterSpacing: 1, textTransform: 'uppercase',
              display: 'inline-flex', gap: 8, alignItems: 'center',
              transition: 'color .15s ease',
            }}
              onMouseEnter={e => e.currentTarget.style.color = T.textInv}
              onMouseLeave={e => e.currentTarget.style.color = T.muteInv}
            >
              <span style={{ color: T.allow }}>{it.n}</span>
              <span>{it.label}</span>
            </a>
          ))}
        </div>
        <TXButton variant="allow" href="#pricing" size="md">{copy.navCta}</TXButton>
      </div>
    </nav>
  );
}

// ── Hero ───────────────────────────────────────────────────────
function Hero({ copy, showSpecStrip }) {
  const h = copy.hero;
  return (
    <section id="top" style={{
      background: T.ink, color: T.textInv,
      padding: '64px 0 96px', position: 'relative', overflow: 'hidden',
    }}>
      <div style={{
        position: 'absolute', inset: 0, pointerEvents: 'none',
        backgroundImage:
          'linear-gradient(rgba(244,244,240,0.03) 1px, transparent 1px),' +
          'linear-gradient(90deg, rgba(244,244,240,0.03) 1px, transparent 1px)',
        backgroundSize: '64px 64px',
        maskImage: 'radial-gradient(1200px 700px at 80% 30%, #000 30%, transparent 100%)',
        WebkitMaskImage: 'radial-gradient(1200px 700px at 80% 30%, #000 30%, transparent 100%)',
      }}/>

      <div style={{ maxWidth: 1240, margin: '0 auto', padding: '0 32px', position: 'relative' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.05fr', gap: 56, alignItems: 'start' }}>
          <div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 24, flexWrap: 'wrap' }}>
              <Bracket color={T.allow}>{h.eyebrow}</Bracket>
              <Mono color={T.muteInv}>{h.tag}</Mono>
            </div>

            <h1 style={{
              fontFamily: GEIST, fontWeight: 600,
              fontSize: 'clamp(46px, 6.2vw, 84px)',
              lineHeight: 0.96, letterSpacing: -3,
              margin: 0, color: T.textInv,
            }}>
              {h.headline1}<br/>
              {h.headline2}<br/>
              <span style={{ color: T.allow }}>{h.headline3}</span>{' '}
              {h.headline3Accent}
            </h1>

            <p style={{
              marginTop: 28, fontSize: 18, lineHeight: 1.5,
              color: T.muteInv, letterSpacing: -0.2, maxWidth: 500,
              fontFamily: GEIST,
            }}>{h.sub}</p>

            <div style={{ display: 'flex', gap: 12, marginTop: 36, flexWrap: 'wrap' }}>
              <TXButton variant="allow" size="lg" href="#pricing">{h.cta1}</TXButton>
              <TXButton variant="ghost" size="lg" href="#gate" style={{ color: T.textInv, border: `1px solid ${T.inkBorder}` }}>{h.cta2}</TXButton>
            </div>

            {showSpecStrip && (
              <div style={{
                marginTop: 56,
                display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)',
                borderTop: `1px solid ${T.inkBorder}`, borderBottom: `1px solid ${T.inkBorder}`,
                padding: '20px 0',
              }}>
                {h.specs.map((s, i) => (
                  <div key={s.k} style={{
                    paddingLeft: i === 0 ? 0 : 18,
                    borderLeft: i === 0 ? 'none' : `1px solid ${T.inkBorder}`,
                    paddingRight: 18,
                  }}>
                    <Mono color={T.allow} weight={500}>{s.k}</Mono>
                    <div style={{
                      fontFamily: GMONO, fontWeight: 500, fontSize: 18,
                      color: T.textInv, marginTop: 8, letterSpacing: 0.3, lineHeight: 1,
                    }}>{s.v}</div>
                    <Mono color={T.muteInv} size={10} style={{ marginTop: 6, display: 'block' }}>{s.sub}</Mono>
                  </div>
                ))}
              </div>
            )}
          </div>

          <div style={{ paddingTop: 6 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 14 }}>
              <Mono color={T.textInv} weight={500}>{h.figLabel}</Mono>
              <Mono color={T.muteInv}>{h.figSub}</Mono>
            </div>
            <ConsensusGate/>
          </div>
        </div>
      </div>
    </section>
  );
}

// ── §01 Capabilities ───────────────────────────────────────────
function Capabilities({ copy }) {
  const c = copy.capabilities;
  return (
    <Section id="capabilities" num="01" eyebrow={c.eyebrow}
      title={c.title} intro={c.intro}>
      <div style={{
        display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 0,
        borderTop: `1px solid ${T.hair}`, borderLeft: `1px solid ${T.hair}`,
      }}>
        {c.cards.map(card => (
          <div key={card.n} style={{
            padding: '32px 32px 36px', borderRight: `1px solid ${T.hair}`,
            borderBottom: `1px solid ${T.hair}`, background: T.paper,
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 18 }}>
              <Mono color={T.allow} weight={500}>§01.{card.n}</Mono>
              <Mono color={T.mute}>{card.meta}</Mono>
            </div>
            <h3 style={{
              fontFamily: GEIST, fontSize: 22, fontWeight: 600,
              letterSpacing: -0.6, lineHeight: 1.2, margin: 0, color: T.text,
            }}>{card.t}</h3>
            <p style={{
              fontSize: 15, lineHeight: 1.55, color: T.mute, letterSpacing: -0.1,
              marginTop: 10, maxWidth: 440,
            }}>{card.d}</p>
          </div>
        ))}
      </div>
    </Section>
  );
}

// ── §02 Gate ───────────────────────────────────────────────────
function Gate({ copy }) {
  const g = copy.gate;
  return (
    <Section id="gate" num="02" eyebrow={g.eyebrow} dark
      title={g.title} intro={g.intro}>
      <ConsensusGate/>
      <div style={{
        marginTop: 20, display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0,
        border: `1px solid ${T.inkBorder}`, background: 'rgba(244,244,240,0.02)',
      }}>
        {g.cells.map((cell, i, arr) => (
          <div key={cell.k} style={{
            padding: 22, borderRight: i < arr.length - 1 ? `1px solid ${T.inkBorder}` : 'none',
          }}>
            <Mono color={T.allow} weight={500}>{cell.k}</Mono>
            <p style={{
              marginTop: 8, fontSize: 13, lineHeight: 1.55, letterSpacing: -0.05,
              color: 'rgba(244,244,240,0.7)', fontFamily: GEIST,
            }}>{cell.v}</p>
          </div>
        ))}
      </div>

      {/* Council CTA — surface the on-chain story prominently */}
      <a href="Council.html" style={{
        marginTop: 20, padding: '22px 26px',
        border: `1px solid ${T.allow}`, background: 'rgba(45,216,129,0.06)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        gap: 24, textDecoration: 'none', cursor: 'pointer',
        transition: 'background .2s ease, transform .2s ease',
      }}
        onMouseEnter={e => { e.currentTarget.style.background = 'rgba(45,216,129,0.12)'; e.currentTarget.style.transform = 'translateY(-1px)'; }}
        onMouseLeave={e => { e.currentTarget.style.background = 'rgba(45,216,129,0.06)'; e.currentTarget.style.transform = 'translateY(0)'; }}
      >
        <div>
          <Mono color={T.allow} weight={500}>THE COUNCIL · ON CHAIN</Mono>
          <div style={{
            marginTop: 8, fontFamily: GEIST, fontSize: 22, fontWeight: 500,
            color: T.textInv, letterSpacing: -0.4, lineHeight: 1.2,
          }}>
            Meet the 25 named agents. Each one a wallet, each one a vote, each one a receipt on Base.
          </div>
          <div style={{
            marginTop: 6, fontSize: 13.5, color: T.muteInv, letterSpacing: -0.05, lineHeight: 1.55, maxWidth: 620,
          }}>
            Public roster, agreement matrix, live council vote, and a one-page guide to verify it all yourself on Basescan.
          </div>
        </div>
        <div style={{
          flexShrink: 0, padding: '14px 22px',
          background: T.allow, color: T.ink,
          fontFamily: GEIST, fontSize: 15, fontWeight: 600, letterSpacing: -0.2,
          display: 'inline-flex', alignItems: 'center', gap: 10,
        }}>
          Enter the Council
          <span style={{ fontSize: 18 }}>→</span>
        </div>
      </a>
    </Section>
  );
}

// ── §03 Brokers ────────────────────────────────────────────────
function Brokers({ copy }) {
  const b = copy.brokers;
  return (
    <Section id="brokers" num="03" eyebrow={b.eyebrow}
      title={b.title} intro={b.intro}>
      <BrokerSwitcher/>
    </Section>
  );
}

// ── §04 Quantum / Risk check ───────────────────────────────────
function Quantum({ copy }) {
  const q = copy.quantum;
  return (
    <Section id="quantum" num="04" eyebrow={q.eyebrow} dark accent={T.caution}
      title={q.title} intro={q.intro}>
      <QuantumDemo/>
    </Section>
  );
}

// ── §05 Pricing ────────────────────────────────────────────────
function Pricing({ copy, featuredTier }) {
  const p = copy.pricing;
  const tierIds = ['pick5', 'pro'];
  const tiers = tierIds.map(id => ({
    ...p.tiers[id],
    id,
    feat: featuredTier === id || (featuredTier === 'fullsweep' && id === 'pro') || (featuredTier === 'picks' && id === 'pick5'),
    // Prefer the tier object's own .href (set by provision_stripe_per_session.py
    // when per-session products are live); fall back to STRIPE_URLS (mailto).
    url: p.tiers[id].href || STRIPE_URLS[id],
  }));

  const [hover, setHover] = React.useState(null);

  return (
    <Section id="pricing" num="05" eyebrow={p.eyebrow}
      title={p.title} intro={p.intro}>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 16, maxWidth: 880, margin: '0 auto' }}>
        {tiers.map((t, i) => {
          const isHov = hover === t.id;
          const isFeat = t.feat;
          const bg = isFeat ? T.ink : T.paper;
          const fg = isFeat ? T.textInv : T.text;
          return (
            <div key={t.id}
              onMouseEnter={() => setHover(t.id)}
              onMouseLeave={() => setHover(null)}
              style={{
                background: bg, color: fg,
                border: `1px solid ${isFeat ? T.ink : T.hair}`,
                padding: '28px 28px 32px', position: 'relative',
                transform: isHov ? 'translateY(-6px)' : 'translateY(0)',
                transition: 'transform .25s ease, box-shadow .25s ease',
                boxShadow: isHov ? '0 30px 60px -20px rgba(10,11,14,0.18)' : 'none',
                display: 'flex', flexDirection: 'column',
              }}>
              <CornerMark where="tl" color={isFeat ? 'rgba(244,244,240,0.3)' : T.hairBold}/>
              <CornerMark where="tr" color={isFeat ? 'rgba(244,244,240,0.3)' : T.hairBold}/>
              <CornerMark where="bl" color={isFeat ? 'rgba(244,244,240,0.3)' : T.hairBold}/>
              <CornerMark where="br" color={isFeat ? 'rgba(244,244,240,0.3)' : T.hairBold}/>

              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 18 }}>
                <Mono color={T.allow} weight={500}>{isFeat ? p.featuredTag : t.tag}</Mono>
                <Mono color={isFeat ? 'rgba(244,244,240,0.5)' : T.mute}>§05.0{i+1}</Mono>
              </div>

              <div style={{
                fontFamily: GEIST, fontWeight: 600, fontSize: 26,
                letterSpacing: -0.6, marginBottom: 6,
              }}>{t.name}</div>
              <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 14 }}>
                <span style={{ fontFamily: GMONO, fontSize: 44, fontWeight: 500, letterSpacing: -1.6 }}>{t.price}</span>
                <span style={{ fontSize: 13, opacity: 0.6, letterSpacing: -0.1 }}>{t.unit}</span>
              </div>

              <p style={{ fontSize: 14, lineHeight: 1.5, letterSpacing: -0.1, opacity: 0.75, margin: '0 0 24px', maxWidth: 280 }}>{t.tagline}</p>

              <div style={{
                paddingTop: 18, borderTop: `1px solid ${isFeat ? 'rgba(244,244,240,0.14)' : T.hair}`,
                display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 24,
              }}>
                {t.perks.map(perk => (
                  <div key={perk} style={{ display: 'flex', alignItems: 'flex-start', gap: 10, fontSize: 14, lineHeight: 1.4, letterSpacing: -0.1 }}>
                    <span style={{ flexShrink: 0, marginTop: 6, width: 8, height: 8, background: T.allow }}/>
                    <span>{perk}</span>
                  </div>
                ))}
              </div>

              <div style={{
                marginTop: 'auto', paddingTop: 16,
                borderTop: `1px dashed ${isFeat ? 'rgba(244,244,240,0.14)' : T.hair}`,
                display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8,
                opacity: isHov ? 1 : 0.4, transition: 'opacity .25s ease',
              }}>
                {t.meta.map(m => (
                  <Mono key={m} color={isFeat ? 'rgba(244,244,240,0.5)' : T.mute} size={9}>{m}</Mono>
                ))}
              </div>

              <a href={t.url} target="_blank" rel="noopener" style={{ textDecoration: 'none', marginTop: 20 }}>
                <TXButton variant={isFeat ? 'allow' : 'primary'} style={{ width: '100%' }}>
                  Start {t.name}
                </TXButton>
              </a>
            </div>
          );
        })}
      </div>

      <div style={{
        marginTop: 24, padding: '14px 18px', background: T.paper,
        border: `1px solid ${T.hair}`, display: 'flex', alignItems: 'center', gap: 16,
      }}>
        <Mono color={T.allow} weight={500}>OPT-IN</Mono>
        <p style={{ fontSize: 13.5, color: T.text, letterSpacing: -0.1, margin: 0, lineHeight: 1.5 }}>
          {p.profitShareNote} <a href="mailto:ip@epochcore.com" style={{ color: T.allow, textDecoration: 'none' }}>{p.profitShareLink}</a>
        </p>
      </div>
    </Section>
  );
}

// ── §06 FAQ ────────────────────────────────────────────────────
function FAQ({ copy }) {
  const f = copy.faq;
  const [open, setOpen] = React.useState(0);
  return (
    <Section id="faq" num="06" eyebrow={f.eyebrow}
      title={f.title} intro={f.intro}>
      <div>
        {f.items.map((item, i) => {
          const isOpen = open === i;
          return (
            <div key={item.q} style={{ borderTop: `1px solid ${T.hair}` }}>
              <button onClick={() => setOpen(isOpen ? -1 : i)} style={{
                width: '100%', padding: '22px 0', background: 'transparent', border: 'none',
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                cursor: 'pointer', textAlign: 'left',
              }}>
                <span style={{
                  fontFamily: GEIST, fontSize: 19, fontWeight: 500,
                  letterSpacing: -0.4, color: T.text, paddingRight: 24,
                }}>{item.q}</span>
                <span style={{
                  width: 28, height: 28, flexShrink: 0,
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  color: T.allow, border: `1px solid ${T.hair}`,
                  transition: 'transform .2s ease',
                  transform: isOpen ? 'rotate(45deg)' : 'rotate(0deg)',
                }}>
                  <svg width="12" height="12" viewBox="0 0 12 12">
                    <path d="M6 1v10M1 6h10" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
                  </svg>
                </span>
              </button>
              <div style={{ maxHeight: isOpen ? 280 : 0, overflow: 'hidden', transition: 'max-height .3s ease' }}>
                <div style={{
                  padding: '0 60px 24px 0', fontSize: 15.5, lineHeight: 1.6,
                  color: T.mute, letterSpacing: -0.1, maxWidth: 720,
                }}>{item.a}</div>
              </div>
            </div>
          );
        })}
        <div style={{ borderTop: `1px solid ${T.hair}` }}/>
      </div>
    </Section>
  );
}

// ── Footer ─────────────────────────────────────────────────────
function Footer({ copy, showInternalIds }) {
  const f = copy.footer;
  return (
    <footer style={{ background: T.ink, color: T.textInv, padding: '80px 0 32px' }}>
      <div style={{ maxWidth: 1240, margin: '0 auto', padding: '0 32px' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr 1fr 1fr', gap: 48, marginBottom: 56 }}>
          <div>
            <Wordmark color={T.textInv} size={20}/>
            <p style={{
              marginTop: 18, fontSize: 14, lineHeight: 1.55,
              color: 'rgba(244,244,240,0.6)', maxWidth: 360, letterSpacing: -0.1,
            }}>{f.tagline}</p>
          </div>
          {f.cols.map(col => <FootCol key={col.title} title={col.title} links={col.links}/>)}
        </div>
        <div style={{
          paddingTop: 24, borderTop: `1px solid ${T.inkBorder}`,
          display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12,
        }}>
          <Mono color={T.muteInv}>{f.legal}</Mono>
          {showInternalIds && <Mono color={T.muteInv}>{f.version}</Mono>}
        </div>
      </div>
    </footer>
  );
}
function FootCol({ title, links }) {
  return (
    <div>
      <Mono color={T.allow} weight={500}>{title}</Mono>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 16 }}>
        {links.map(l => (
          <a key={l.l} href={l.href} style={{
            color: 'rgba(244,244,240,0.75)', textDecoration: 'none',
            fontFamily: GEIST, fontSize: 14, letterSpacing: -0.1,
          }}>{l.l}</a>
        ))}
      </div>
    </div>
  );
}

// ── Scroll progress strip ──────────────────────────────────────
function ScrollProgress() {
  const [p, setP] = React.useState(0);
  React.useEffect(() => {
    const onScroll = () => {
      const h = document.documentElement;
      const max = h.scrollHeight - h.clientHeight;
      setP(max > 0 ? h.scrollTop / max : 0);
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return (
    <div style={{
      position: 'fixed', top: 0, left: 0, right: 0, height: 2,
      zIndex: 100, pointerEvents: 'none',
    }}>
      <div style={{
        height: '100%', width: `${p * 100}%`, background: T.allow,
        transition: 'width .1s linear',
      }}/>
    </div>
  );
}

// ── App ────────────────────────────────────────────────────────
function App() {
  const [tw, setTweak] = useTweaks(window.TWEAK_DEFAULTS);
  const copy = COPY[tw.voice] || COPY.trader;

  return (
    <div style={{ background: T.paper, color: T.text, minHeight: '100vh' }}>
      <ScrollProgress/>
      {tw.showTopTicker && <TopTicker copy={copy}/>}
      <Nav copy={copy}/>
      <Hero copy={copy} showSpecStrip={tw.showSpecStrip}/>
      <Capabilities copy={copy}/>
      <Gate copy={copy}/>
      <Brokers copy={copy}/>
      <Quantum copy={copy}/>
      <Pricing copy={copy} featuredTier={tw.featuredTier}/>
      <FAQ copy={copy}/>
      <Footer copy={copy} showInternalIds={tw.showInternalIds}/>

      <TweaksPanel title="TradeXchange tweaks">
        <TweakSection label="Voice"/>
        <TweakRadio label="Audience" value={tw.voice}
                    options={['trader', 'engineer']}
                    onChange={(v) => setTweak('voice', v)}/>

        <TweakSection label="Layout"/>
        <TweakToggle label="Top ticker"          value={tw.showTopTicker}
                     onChange={(v) => setTweak('showTopTicker', v)}/>
        <TweakToggle label="Hero spec strip"     value={tw.showSpecStrip}
                     onChange={(v) => setTweak('showSpecStrip', v)}/>
        <TweakToggle label="Internal IDs in footer" value={tw.showInternalIds}
                     onChange={(v) => setTweak('showInternalIds', v)}/>

        <TweakSection label="Pricing"/>
        <TweakRadio label="Featured tier" value={tw.featuredTier}
                    options={['picks', 'fullsweep']}
                    onChange={(v) => setTweak('featuredTier', v)}/>
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
