// ============================================================
// 18% CLUB · TRADE DESK · v6
// app.jsx — root, data loading, all top-level sections, tour
// ============================================================

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

// ---------- localStorage helpers ----------
const LS_MODE = '18club:mode';
const LS_TOUR_SEEN = '18club:tourSeen';
function lsGet(k, fallback) {try {return localStorage.getItem(k) ?? fallback;} catch (_) {return fallback;}}
function lsSet(k, v) {try {localStorage.setItem(k, v);} catch (_) {}}

// ---------- count-up hook (light) ----------
function useCountUp(target, durationMs = 900) {
  const [v, setV] = useState(0);
  const started = useRef(false);
  useEffect(() => {
    if (started.current) {setV(target);return;}
    started.current = true;
    const t0 = performance.now();
    let raf;
    const tick = (now) => {
      const p = Math.min(1, (now - t0) / durationMs);
      const eased = 1 - Math.pow(1 - p, 3);
      setV(target * eased);
      if (p < 1) raf = requestAnimationFrame(tick);else
      setV(target);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target]);
  return v;
}

// ============================================================
// STATUS BAR (sticky top)
// ============================================================
function StatusBar({ next, onDiscord }) {
  const status = next?.market_status || 'closed';
  const tone = status === 'live' ? 'live' : status === 'closed' || status === 'weekend' || status === 'holiday' ? 'closed' : '';
  let line;
  if (status === 'pre_market') {
    line = <><b>Pre-market</b><span className="sep">·</span>US opens in {next.minutes_to_open}m<span className="sep">·</span>today's plan is being built live in Discord</>;
  } else if (status === 'live') {
    line = <><b>Live</b><span className="sep">·</span>US market open<span className="sep">·</span>operating room is open in Discord</>;
  } else if (status === 'post_market') {
    line = <><b>Post-market</b><span className="sep">·</span>today's PMR is being written live in Discord</>;
  } else if (status === 'weekend' || status === 'holiday') {
    line = <><b>Markets closed</b><span className="sep">·</span>this archive shows yesterday's complete cycle</>;
  } else {
    line = <><b>Live data</b><span className="sep">·</span>T+1 archive · published daily</>;
  }
  return (
    <div className={`statusbar ${tone}`} role="status" aria-live="polite">
      <span className="pulse" aria-hidden="true" />
      <span className="status-text">{line}</span>
      <button className="discord-go" onClick={onDiscord} aria-label="Go to Discord">Discord →</button>
    </div>);

}

// ============================================================
// NAV
// ============================================================
function Nav({ onTour, showFirstTime, onDismissFirstTime }) {
  return (
    <div className="nav" role="navigation">
      <a className="logo" href="index.html" aria-label="18% Club home">
        <img className="logo-mark" src={window.__resources && window.__resources.logo || "assets/logo/18club-logo.png"} alt="18% Club" width="40" height="40" />
        <span className="logo-wm">
          <span className="wm-main">18% Club</span>
          <span className="wm-sub">trade desk</span>
        </span>
      </a>
      <nav className="nav-links" aria-label="Primary">
        <a className="nav-link active" href="index.html">Home</a>
        <a className="nav-link" href="dashboard.html?from=index">Performance Dashboard</a>
        <a className="nav-link" href="structural-bias.html">Structural Bias</a>
        <a className="nav-link" href="market-sensing.html?from=index">Market Sensing</a>
        <a className="nav-link" href="rulebook.html">Rulebook</a>
        <a className="nav-link" href="indicator.html">Indicator</a>
      </nav>
      {showFirstTime &&
      <div className="nav-firsttime" role="group" aria-label="First-time visitor">
          <button className="nav-firsttime-btn" onClick={onTour}>First time here?<span className="nav-firsttime-arrow">→</span></button>
          <button className="nav-firsttime-dismiss" onClick={onDismissFirstTime} aria-label="Dismiss" title="Dismiss">×</button>
        </div>
      }
    </div>);

}

// ============================================================
// SPLIT HERO
// ============================================================
function SplitHero({ tradingDay, nextStatus, onDiscord }) {
  // tradingDay is the published (yesterday) day
  return (
    <div className="split-hero">
      <div className="sh-left">
        <div className="sh-eb">on this page · public</div>
        <div className="sh-h">Yesterday · <em>{fmtHuman(tradingDay)}</em> · complete cycle published</div>
      </div>
      <div className="sh-right">
        <div>
          <div className="sh-eb">in the discord · live · members only</div>
          <div className="sh-h">Today · <em>running live</em></div>
        </div>
        <button className="sh-go" onClick={onDiscord}>Join free →</button>
      </div>
    </div>);

}

// ============================================================
// NEW HERE banner — removed; CTA now lives in the Nav (right side).
// ============================================================

// KPI components live in kpis.jsx (KpiHero, HomeKpiTriad, FullKpiGrid, StatSheet)

// ============================================================
// TWEAKS DEFAULTS — exposed via the Tweaks panel
// Edit these values to change the page defaults.
// ============================================================
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "chartType": "area"
} /*EDITMODE-END*/;

// ============================================================
// EQUITY CURVE
// ============================================================
function EquityCard({ equity, initialCapital }) {
  const [period, setPeriod] = useState('30D');
  // Slice
  const sliced = useMemo(() => {
    const n = equity.length;
    if (period === '7D') return equity.slice(Math.max(0, n - 7));
    if (period === '14D') return equity.slice(Math.max(0, n - 14));
    return equity;
  }, [equity, period]);

  const W = 540,H = 200;
  const padL = 42,padR = 14,padT = 14,padB = 30;
  const innerW = W - padL - padR;
  const innerH = H - padT - padB;
  const all = sliced.flatMap((e) => [e.mark_usd, e.realized_usd]);
  const minV = Math.min(...all, initialCapital);
  const maxV = Math.max(...all, initialCapital);
  const pad = (maxV - minV) * 0.1 || 10000;
  const yMin = Math.floor((minV - pad) / 10000) * 10000;
  const yMax = Math.ceil((maxV + pad) / 10000) * 10000;
  const yRange = yMax - yMin;

  const xForIdx = (i) => padL + i / Math.max(1, sliced.length - 1) * innerW;
  const yForV = (v) => padT + (1 - (v - yMin) / yRange) * innerH;

  const markPath = sliced.map((d, i) => `${i === 0 ? 'M' : 'L'} ${xForIdx(i)} ${yForV(d.mark_usd)}`).join(' ');
  const realizedPath = sliced.map((d, i) => `${i === 0 ? 'M' : 'L'} ${xForIdx(i)} ${yForV(d.realized_usd)}`).join(' ');
  const fillPath = markPath + ` L ${xForIdx(sliced.length - 1)} ${padT + innerH} L ${padL} ${padT + innerH} Z`;

  // Y gridlines: 5
  const ylines = [];
  for (let i = 0; i <= 4; i++) {
    const v = yMin + yRange * i / 4;
    ylines.push({ v, y: yForV(v) });
  }
  const yInitial = yForV(initialCapital);

  // X tick dates
  const xTicks = [];
  const step = Math.max(1, Math.floor(sliced.length / 4));
  for (let i = 0; i < sliced.length; i += step) xTicks.push({ i, d: sliced[i].date });
  if (xTicks.length && xTicks[xTicks.length - 1].i !== sliced.length - 1) {
    xTicks.push({ i: sliced.length - 1, d: sliced[sliced.length - 1].date });
  }

  return (
    <div className="equity-card">
      <div className="stat-h">
        <span className="stat-t">Account equity</span>
        <span className="stat-m">REALIZED + MARK · STARTS ${initialCapital.toLocaleString()}</span>
      </div>
      <div className="eq-pills">
        {['30D', '14D', '7D'].map((p) =>
        <button key={p} className={`eq-pill ${period === p ? 'on' : ''}`} onClick={() => setPeriod(p)}>{p}</button>
        )}
      </div>
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet" style={{ width: '100%', height: 'auto' }} role="img" aria-label="Account equity curve">
        <g stroke="#DBD3CD" strokeWidth="0.4">
          {ylines.map((g, i) => <line key={i} x1={padL} y1={g.y} x2={W - padR} y2={g.y} />)}
        </g>
        <g fontFamily="ui-monospace, monospace" fontSize="9" fill="#917F6C">
          {ylines.map((g, i) => <text key={i} x={padL - 4} y={g.y + 3} textAnchor="end">${(g.v / 1000).toFixed(0)}k</text>)}
        </g>
        <line x1={padL} y1={yInitial} x2={W - padR} y2={yInitial} stroke="#917F6C" strokeWidth="0.7" strokeDasharray="3 3" />
        <text x={W - padR} y={yInitial - 4} textAnchor="end" fontFamily="ui-monospace, monospace" fontSize="9" fill="#917F6C">${(initialCapital / 1000).toFixed(0)}k start</text>
        <path d={fillPath} fill="#213744" opacity="0.04" />
        <path d={realizedPath} fill="none" stroke="#917F6C" strokeWidth="1" strokeDasharray="3 2" />
        <path d={markPath} fill="none" stroke="#213744" strokeWidth="1.6" strokeLinejoin="round" strokeLinecap="round" />
        <g fontFamily="ui-monospace, monospace" fontSize="9" fill="#917F6C">
          {xTicks.map((t, i) =>
          <text key={i} x={xForIdx(t.i)} y={H - 12} textAnchor={i === 0 ? 'start' : i === xTicks.length - 1 ? 'end' : 'middle'}>{fmtShort(t.d)}</text>
          )}
        </g>
      </svg>
      <div className="legend" style={{ marginTop: 4 }}>
        <span className="lg-i"><span className="lg-line" /> account equity · mark-to-market</span>
        <span className="lg-i"><span className="lg-line" style={{ background: '#917F6C' }} /> realized only</span>
        <span className="lg-i"><span className="lg-line" style={{ background: 'transparent', borderBottom: '1px dashed #917F6C', height: 0 }} /> initial capital</span>
      </div>
    </div>);

}

// ============================================================
// OPEN EXPOSURE
// ============================================================
function ExposureCard({ positions, risk }) {
  return (
    <div className="exposure-card">
      <div className="stat-h">
        <span className="stat-t">Open exposure</span>
        <span className="stat-m">LIVE MARK</span>
      </div>
      {positions.map((p) =>
      <div key={p.id} className="ex-pos">
          <div>
            <div className="ex-pos-l">{p.label}</div>
            <div className="ex-pos-d">{p.direction.toUpperCase()} {p.size} {p.contract} · entry {fmtPrice(p.entry_price)}</div>
          </div>
          <div>
            <div className="ex-pos-v">+${p.unrealized_pnl_usd.toLocaleString()}</div>
            <div className="ex-pos-vs">+{p.points.toFixed(2)} pts</div>
          </div>
        </div>
      )}
      <div className="risk-box">
        <div className="risk-l">max risk · if all stops hit</div>
        <div className="risk-v">{fmtUSD(risk.max_loss_if_all_stops_hit_usd)}</div>
        <div className="risk-s"><b>{(risk.max_loss_pct_of_nav * 100).toFixed(2)}% of NAV</b> · {risk.stops_summary}</div>
      </div>
    </div>);

}

// ============================================================
// SYSTEM FLOW VISUAL — raw data → plan → order execution
// High-level intro to the system for first-time visitors.
// Subtle CSS animations on each stage; the page below is the proof.
// ============================================================
// Rulebook stage glyph — when active, cycles V4.7→V4.8→V4.9 and shows
// an animated data inflow arrow, emphasising "upgraded based on latest
// market conditions."
function RulebookGlyph({ active }) {
  const versions = ['V4.7', 'V4.8', 'V4.9'];
  const [vIdx, setVIdx] = useState(0);
  useEffect(() => {
    if (!active) {setVIdx(0);return;}
    const t = setInterval(() => setVIdx((i) => (i + 1) % versions.length), 380);
    return () => clearInterval(t);
  }, [active]);
  return (
    <svg viewBox="0 0 120 40" className="sf-svg">
      {/* Incoming data — only when active */}
      <g className={`sf-rb-inflow ${active ? 'on' : ''}`} aria-hidden="true">
        <line className="sf-g-inflow" x1="1" y1="20" x2="13" y2="20" strokeDasharray="2 2" />
        <polyline className="sf-g-inflow-arrow" points="9,16 13,20 9,24" fill="none" />
      </g>
      {/* book "page" */}
      <rect className="sf-g sf-g-page" x="14" y="6" width="72" height="30" rx="1.5" />
      <line className="sf-g sf-g-rule" x1="20" y1="14" x2="78" y2="14" />
      <line className="sf-g sf-g-rule" x1="20" y1="21" x2="66" y2="21" />
      <line className="sf-g sf-g-rule" x1="20" y1="28" x2="72" y2="28" />
      {/* version badge — cycles when active */}
      <g className={`sf-g-badge ${active ? 'cycling' : ''}`}>
        <rect x="90" y="15" width="24" height="10" rx="1" />
        <text key={vIdx} x="102" y="22.2" textAnchor="middle">{versions[vIdx]}</text>
      </g>
      {/* upward delta tick when active — "upgraded" cue */}
      {active &&
      <polyline className="sf-g-delta" points="118,18 114,12 110,18" fill="none" />
      }
    </svg>);
}

function SystemFlowVisual() {
  const N = 5;
  const [activeIdx, setActiveIdx] = useState(0);
  const reduced = typeof window !== 'undefined' &&
  window.matchMedia &&
  window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  useEffect(() => {
    if (reduced) return;
    const t = setInterval(() => {
      setActiveIdx((i) => (i + 1) % N);
    }, 1400);
    return () => clearInterval(t);
  }, [reduced]);

  const stages = [
  { id: 'data', title: 'Raw market data' },
  { id: 'rulebook', title: 'Rulebook' },
  { id: 'plan', title: 'Pre-market plan' },
  { id: 'order', title: 'Intraday order' },
  { id: 'review', title: 'Post-market review' }];


  const Glyph = ({ id, isActive }) => {
    switch (id) {
      case 'data':
        return (
          <svg viewBox="0 0 120 40" preserveAspectRatio="none" className="sf-svg">
            {[
            { x: 8, h: 14 },
            { x: 24, h: 26 },
            { x: 40, h: 18 },
            { x: 56, h: 32 },
            { x: 72, h: 22 },
            { x: 88, h: 36 },
            { x: 104, h: 28 }].
            map((b, i) =>
            <rect key={i} className="sf-g sf-g-bar" x={b.x} y={40 - b.h} width="10" height={b.h} rx="1" />
            )}
          </svg>);
      case 'rulebook':
        return <RulebookGlyph active={isActive} />;
      case 'plan':
        return (
          <svg viewBox="0 0 120 40" className="sf-svg">
            {/* target & stop dashed levels */}
            <line className="sf-g sf-g-level" x1="10" y1="10" x2="110" y2="10" strokeDasharray="3 2" />
            <line className="sf-g sf-g-level" x1="10" y1="30" x2="110" y2="30" strokeDasharray="3 2" />
            {/* projected path */}
            <polyline className="sf-g sf-g-path" points="12,26 30,22 48,24 66,18 84,14 104,12" fill="none" />
            {/* entry dot */}
            <circle className="sf-g sf-g-entry" cx="12" cy="26" r="2.6" />
          </svg>);
      case 'order':
        return (
          <svg viewBox="0 0 120 40" className="sf-svg">
            {/* baseline */}
            <line className="sf-g sf-g-base" x1="10" y1="30" x2="110" y2="30" />
            {/* arrow up-right */}
            <line className="sf-g sf-g-shaft" x1="14" y1="30" x2="100" y2="10" />
            <polyline className="sf-g sf-g-head" points="90,8 102,8 102,20" fill="none" />
            {/* live fill pulse */}
            <circle className="sf-g sf-g-fill" cx="100" cy="10" r="3.2" />
          </svg>);
      case 'review':
        return (
          <svg viewBox="0 0 120 40" className="sf-svg">
            <circle className="sf-g sf-g-ring" cx="60" cy="20" r="14" fill="none" />
            <polyline className="sf-g sf-g-check" points="53,21 58,26 68,14" fill="none" />
          </svg>);
      default:
        return null;
    }
  };

  return (
    <div className="system-flow" aria-label="The system loop: raw data, rulebook, pre-market plan, intraday order, post-market review">
      <div className="sf-track">
        {stages.map((s, i) =>
        <React.Fragment key={s.id}>
            <div className={`sf-stage ${activeIdx === i ? 'active' : ''}`} data-stage={s.id}>
              <div className="sf-art" aria-hidden="true"><Glyph id={s.id} isActive={activeIdx === i} /></div>
              <div className="sf-title">{s.title}</div>
            </div>
            {i < stages.length - 1 &&
          <div className={`sf-link ${activeIdx === i ? 'flow' : ''}`} aria-hidden="true">
                <svg viewBox="0 0 80 24" preserveAspectRatio="none">
                  <line x1="2" y1="12" x2="68" y2="12" className="sf-link-path" />
                  <polyline points="64,7 72,12 64,17" className="sf-link-arrow" />
                  <circle className="sf-link-particle" cx="2" cy="12" r="1.6" />
                  <circle className="sf-link-particle sf-link-particle-2" cx="2" cy="12" r="1.6" />
                  <circle className="sf-link-particle sf-link-particle-3" cx="2" cy="12" r="1.6" />
                </svg>
              </div>
          }
          </React.Fragment>
        )}
      </div>
    </div>);

}

// (Old TraceabilityPillars removed — replaced by SystemFlowVisual above.)

// ============================================================
// TRADE CONNECTOR — visual bridge between chart and selected-trade bar
// Re-keyed on every selection so the strip animates fresh each time.
// ============================================================
function TradeConnector({ tradeId }) {
  return (
    <div className="trade-connector" key={tradeId} aria-hidden="true">
      <span className="tc-line tc-line-l" />
      <span className="tc-pill">
        <span className="tc-pill-arrow">↓</span>
        <span className="tc-pill-text">details of this trade</span>
      </span>
      <span className="tc-line tc-line-r" />
    </div>);

}

// ============================================================
// EVIDENCE CHAIN — 7-node visualization, the real moat made visible
// ============================================================
function EvidenceChain() {
  const nodes = [
  { n: '01', t: 'Raw data' },
  { n: '02', t: 'Pre-market plan' },
  { n: '03', t: 'Timestamped decision' },
  { n: '04', t: 'Schedule & execute trade' },
  { n: '05', t: 'Broker confirmation' },
  { n: '06', t: 'Post-market review' },
  { n: '07', t: 'Rulebook upgrade' }];

  return (
    <section className="chain-section" id="chain">
      <p className="hd-eb">the real moat</p>
      <h2 className="hd-h">A chain you can't fake.</h2>
      <p className="hd-l">
        Anyone can post a winning screenshot. Few can prove the chain.
      </p>
      <p className="hd-l">
        A single trade can be copied. A green P&amp;L can be staged. But an unbroken operating record cannot be faked easily: pre-market plans timestamped before price moves, trades reconciled to real fills, losses published, rules versioned, and every decision traced from raw data to result.
      </p>
      <ol className="chain-list">
        {nodes.map((node, i) =>
        <li key={node.n} className="chain-node">
            <div className="chain-num">{node.n}</div>
            <div className="chain-body">
              <div className="chain-t">{node.t}</div>
            </div>
            {i < nodes.length - 1 && <div className="chain-link" aria-hidden="true" />}
          </li>
        )}
      </ol>
    </section>);
}

// ============================================================
// T+1 CTA — Beta Cohort 1, 100 founding seats, direct subscribe
// ============================================================
function T1Cta({ config, onJoin }) {
  const { cta } = config;
  const cap = cta.founding_cap || 100;
  const current = cta.founding_current ?? 0;
  const remaining = Math.max(0, cap - current);
  const pct = Math.min(100, current / cap * 100);
  return (
    <div className="t1-cta">
      <div className="t1-cta-eb">the t+1 mechanic</div>
      <div className="t1-cta-h">Everything above is <em>yesterday</em>.<br />Today's operating room is running live — in Discord.</div>
      <p className="t1-cta-p">The public site is the retrospective: yesterday's plan, execution, and post-mortem, published in full. Discord is the live room: today's pre-market plan being built, the rulebook being interpreted in real time, and the same-day post-market review within an hour of close. Same system, different latency — <b>a place to learn the operating system while it runs, paper-trade the process, and sharpen execution discipline alongside other members.</b></p>

      <button className="t1-cta-btn" onClick={onJoin} style={{ marginTop: 14 }}>
        Join the Discord →
      </button>
    </div>);

}

// ============================================================
// ABOUT TANK (placeholder)
// ============================================================
function AboutTank() {
  return (
    <section className="about-section" id="about">
      <p className="hd-eb">who runs this</p>
      <h2 className="hd-h">Built by a <em>systems designer</em>, not a guru.</h2>

      <blockquote className="author-pullquote">
        “I built a system, not a story.”
        <cite>— Tank Tangke</cite>
      </blockquote>

      <div className="author-card">
        <div className="author-avatar" aria-hidden="true">T</div>
        <div>
          <div className="author-meta">Tank Tangke · Singapore</div>
          <div className="author-name">Photo &amp; bio placeholder</div>
          <div className="author-bio">
            Day job: Deloitte Partner. Designs SAP Security, GRC, and control-automation systems for enterprises. <b>This trade desk is the same discipline, turned outward</b> — timestamped plans, broker-reconciled trades, versioned rulebook, logged corrections. The traceability you see on this page isn't a feature; it's the only way the founder knows how to operate. <em>Education only · not financial advice.</em>
          </div>
        </div>
      </div>

      <div className="proof-grid">
        <div className="proof-card"><div className="proof-eb">capital</div><div className="proof-v">$500K real</div></div>
        <div className="proof-card"><div className="proof-eb">publish lag</div><div className="proof-v">T+1 archive</div></div>
        <div className="proof-card"><div className="proof-eb">rulebook</div><div className="proof-v">V4.7 · versioned</div></div>
        <div className="proof-card"><div className="proof-eb">asset</div><div className="proof-v">QQQ via NQ</div></div>
        <div className="proof-card"><div className="proof-eb">jurisdiction</div><div className="proof-v">MAS · Singapore</div></div>
        <div className="proof-card"><div className="proof-eb">model</div><div className="proof-v">No custody · ever</div></div>
      </div>
    </section>);

}

// ============================================================
// METHODOLOGY teaser (stub)
// ============================================================
function MethodologyTeaser({ rulebookVersion }) {
  return (
    <section className="method-section" id="methodology">
      <p className="hd-eb">how the system thinks</p>
      <h2 className="hd-h">The rulebook is <em>versioned</em>. Today: <em>{rulebookVersion}</em>.</h2>
      <p className="hd-l">
        Seven scored modules read the market every morning — weekly, daily, SPX, macro, breadth, leadership and monthly derivatives — and that regime read sets which setups can fire. V4.7 adds a QQQ-calibrated Markov model to anchor the 15-day probabilities. Every change ships dated and versioned, with a public changelog.
      </p>

      <div className="annual-note">
        <div className="annual-note-eb">on the 18%</div>
        <div className="annual-note-b">
          <b>18% is the annual return hurdle the system is designed to beat over time</b> — through disciplined planning, controlled execution, post-market review, and continuous rulebook improvement. It is <em>not</em> a monthly-return promise.
        </div>
      </div>

      <div className="method-changelog">
        <div className="mc-eb">rulebook changelog · real version history</div>
        <ol className="mc-list">
          <li className="mc-item current">
            <div className="mc-v">V4.7<span className="mc-date">today</span></div>
            <div className="mc-b"><b>Markov + HMM regime persistence.</b> A QQQ-calibrated Markov model now anchors the §17 probability framework — replacing the old fixed 50% base with a real 10-step forward distribution. An HMM runs daily as an informational divergence flag (it never touches the scoring math). New §5.4; §17 and §18 amended.</div>
          </li>
          <li className="mc-item">
            <div className="mc-v">V4.6<span className="mc-date">19 May</span></div>
            <div className="mc-b"><b>Module-input expansion.</b> Driven by the 18 May correlation sweep: Macro (§6) adds TLT + XLE and a defensive-rotation tag (SPLV/XLU); Breadth (§7) adds IWM; Leadership (§8) adds IGV/FDN, a semi supply-chain pulse, and a Mag-7 dispersion check. The seven-module SBS architecture is unchanged.</div>
          </li>
          <li className="mc-item">
            <div className="mc-v">V4.5<span className="mc-date">12 May</span></div>
            <div className="mc-b"><b>§11B take-profit carve-out.</b> Under Structural Bull only (SBS +5…+7), the planner may emit a stop-sell-at-final-destination per long tier, with a regime-drift kill-switch if SBS slips below +5. Every other execution prohibition stays in force.</div>
          </li>
          <li className="mc-item">
            <div className="mc-v">V4.4.1<span className="mc-date">12 May</span></div>
            <div className="mc-b"><b>High-impact events only.</b> The economic calendar is filtered to high-impact releases; medium- and low-impact events are dropped from all inputs, scenario logic, and gap-risk labeling. <span className="mc-ref">§3.1 · §3.2 · §15</span></div>
          </li>
          <li className="mc-item">
            <div className="mc-v">V4.4</div>
            <div className="mc-b"><b>Swing-mapping baseline.</b> Pre-Market Planner becomes a pure higher-timeframe engine — regime, location, scenario mapping, activation, invalidation, and zone rationale. Narrow-zone discipline is mandatory: every activation band is tighter than 2.00 QQQ points.</div>
          </li>
        </ol>
        <div className="mc-foot">No retroactive edits — every version is dated and stays accessible for rollback. <a href="rulebook.html" className="mc-link">Read the full version history →</a></div>
      </div>
    </section>);

}

// ============================================================
// FOOTER
// ============================================================
function Footer({ meta, config }) {
  return (
    <div className="footer footer-simple">
      <img
        className="footer-logo"
        src={window.__resources && window.__resources.logo || "assets/logo/18club-logo.png"}
        alt="18% Club"
        width="36"
        height="36" />
      <span className="footer-copy">© {meta.published_at.slice(0, 4)} 18% Club · All rights reserved. Education only — not financial advice. Operated from Singapore. Past performance is not indicative of future results.</span>
    </div>);

}

// ============================================================
// GUIDED TOUR
// ============================================================
function TourOverlay({ steps, idx, onNext, onSkip }) {
  const isFinal = idx === steps.length - 1;
  return (
    <div className="tour-overlay" role="dialog" aria-modal="true" aria-label="Guided tour">
      <div className="tour-card">
        <div className="tour-eb">guided tour</div>
        <div className="tour-step-n">step {idx + 1} of {steps.length}</div>
        <h3 className="tour-h">{steps[idx]}</h3>
        <div className="tour-dots">
          {steps.map((_, i) =>
          <span key={i} className={`tour-dot ${i === idx ? 'active' : i < idx ? 'done' : ''}`} />
          )}
        </div>
        <div className="tour-actions">
          <button className="tour-skip" onClick={onSkip}>{isFinal ? 'Close' : 'Skip tour'}</button>
          <button className={`tour-next ${isFinal ? 'final' : ''}`} onClick={onNext}>
            {isFinal ? 'Start exploring →' : 'Next →'}
          </button>
        </div>
      </div>
    </div>);

}

// ============================================================
// APP ROOT
// ============================================================
function App() {
  const [data, setData] = useState(null);
  const [err, setErr] = useState(null);
  const [currentTradeId, setCurrentTradeId] = useState(null);
  const [showFirstTime, setShowFirstTime] = useState(() => lsGet(LS_TOUR_SEEN, '0') !== '1');
  const [tourActive, setTourActive] = useState(false);
  const [tourIdx, setTourIdx] = useState(0);
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);

  // Load data — Part 1 (performance.json) + Part 2 (trades.json), merged client-side.
  // Falls back to a single bundled file when window.__resources.tradeData is set (legacy / standalone).
  useEffect(() => {
    const R = window.__resources || {};
    const okJson = (label) => (r) => {if (!r.ok) throw new Error('Failed to load ' + label + ': ' + r.status);return r.json();};
    if (R.tradeData && !R.performance) {
      fetch(R.tradeData).then(okJson('trade data')).then(setData).catch((e) => {console.error(e);setErr(e.message);});
      return;
    }
    const perfUrl = R.performance || 'data/performance.json';
    const tradesUrl = R.trades || 'data/trades.json';
    Promise.all([
    fetch(perfUrl).then(okJson('performance.json')),
    fetch(tradesUrl).then(okJson('trades.json'))]).
    then(([perf, tr]) => {
      setData({
        ...perf,
        ...tr,
        meta: { ...(tr.meta || {}), ...(perf.meta || {}) },
        chart: { ...(perf.chart || {}), ...(tr.chart || {}) } });

    }).
    catch((e) => {console.error(e);setErr(e.message);});
  }, []);

  // First-visit tour
  useEffect(() => {
    if (!data) return;
    const seen = lsGet(LS_TOUR_SEEN, '0');
    if (seen !== '1' && data.config?.tour?.enabled) {
      setTourActive(true);
    }
  }, [data]);

  const hasTrade = useCallback((id) => data?.trades.some((t) => t.id === id) ?? false, [data]);

  const selectTrade = (id) => {
    if (id === null) {setCurrentTradeId(null);return;}
    if (!hasTrade(id)) return;
    // Toggle: clicking the same plan again deselects it.
    setCurrentTradeId((prev) => prev === id ? null : id);
  };

  const startTour = () => {setTourIdx(0);setTourActive(true);};
  const closeTour = () => {setTourActive(false);lsSet(LS_TOUR_SEEN, '1');setShowFirstTime(false);};
  const dismissFirstTime = () => {setShowFirstTime(false);lsSet(LS_TOUR_SEEN, '1');};
  const nextTour = () => {
    if (tourIdx >= (data?.config?.tour?.steps?.length ?? 1) - 1) closeTour();else
    setTourIdx((i) => i + 1);
  };

  const goDiscord = () => {
    window.open('https://discord.gg/sbpjpCBbFv', '_blank', 'noopener,noreferrer');
  };

  if (err) {
    return (
      <div className="app">
        <div className="pad">
          <h2 className="hd-h">Couldn't load trade data.</h2>
          <p className="hd-l">{err}</p>
          <p className="hd-l">Try opening <a href="data/performance.json"><code>data/performance.json</code></a> and <a href="data/trades.json"><code>data/trades.json</code></a> directly.</p>
        </div>
      </div>);

  }
  if (!data) {
    return (
      <div className="app">
        <div className="pad" style={{ textAlign: 'center', paddingTop: 80 }}>
          <div style={{ fontFamily: 'Georgia, serif', fontStyle: 'italic', color: '#917F6C' }}>Loading the trade desk…</div>
        </div>
      </div>);

  }

  const currentTrade = data.trades.find((t) => t.id === currentTradeId) || data.trades[0];

  return (
    <div className="app">
      <Nav onTour={startTour} showFirstTime={showFirstTime} onDismissFirstTime={dismissFirstTime} />
      <SplitHero tradingDay={data.meta.trading_day} nextStatus={data.next_session} onDiscord={goDiscord} />

      <div className="pad">
        <h2 className="hd-h">Build your discipline. Compound your <em className="accent">wealth</em>.</h2>

        <HomeKpiTriad
          kpis={data.kpis}
          initialCapital={data.account.initial_capital_usd}
          currentNav={data.account.current_nav_usd}
          inceptionDate={data.account.inception_date}
          tradingDay={data.meta.trading_day}
          openPositionsCount={(data.open_positions || []).length} />

        <TradePlanWorkbench data={data} currentTradeId={currentTradeId} onSelect={selectTrade} />
      </div>

      <StructuralBias />

      <RadarTeaser />

      <div className="pad">
        <div id="cta">
          <T1Cta config={data.config} onJoin={goDiscord} />
        </div>
      </div>

      <MethodologyTeaser rulebookVersion={data.meta.rulebook_version} />
      <EvidenceChain />
      <Footer meta={data.meta} config={data.config} />

      {tourActive &&
      <TourOverlay
        steps={data.config.tour.steps}
        idx={tourIdx}
        onNext={nextTour}
        onSkip={closeTour} />

      }

      <TweaksPanel>
        <TweakSection label="Order-execution chart" />
        <TweakRadio
          label="Type"
          value={tweaks.chartType}
          options={['candle', 'line', 'area']}
          onChange={(v) => setTweak('chartType', v)} />
        <div style={{ fontSize: 10.5, color: 'rgba(41,38,27,0.55)', lineHeight: 1.5, padding: '8px 2px 0', borderTop: '1px solid rgba(0,0,0,0.06)', marginTop: 6 }}>
          The NQ chart in the Order-execution tab shows just the selected trade's window with key levels (entry, stop, target, exit) marked. Pick a day in the calendar to switch trades.
        </div>
      </TweaksPanel>
    </div>);

}

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