// ============================================================
// Betting Dashboard — Tufte-style dense evidence sheet
// ============================================================

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

// formatters (same family as primitives.jsx but inlined to keep self-contained)
const _fmt = {
  pct: (v, d=1) => v == null || isNaN(v) ? '—' : (v*100).toFixed(d) + '%',
  pp:  (v, d=1) => v == null || isNaN(v) ? '—' : (v>=0?'+':'') + (v*100).toFixed(d) + 'pp',
  odds: (o) => typeof o === 'number' ? (o>0?'+':'') + o : o,
  units: (u, d=2) => (u >= 0 ? '+' : '') + u.toFixed(d) + 'u',
  pct0: (v) => v == null ? '—' : (v*100).toFixed(0) + '%',
};
const _c = {
  green: '#22c55e', red: '#ef4444', amber: '#f59e0b', blue: '#3b82f6',
  fg1: '#e1e4ea', fg2: '#8892a6', fg3: '#5a6478',
  border: '#252d42', borderLight: '#2e3850',
  surface: '#111520', surface2: '#161b28', surface3: '#1c2235',
};

// ============================================================
// INLINE GRAPHICS (Tufte's "barnacles")
// ============================================================

// Horizontal edge bar: shows magnitude of edge on a 0-15% scale
function EdgeBar({ edge, width = 56, height = 8 }) {
  const maxE = 0.15;
  const v = Math.min(Math.max(edge, 0), maxE);
  const w = (v / maxE) * width;
  return (
    <svg width={width} height={height} style={{ display: 'inline-block', verticalAlign: 'middle' }}>
      <rect x="0" y={height/2 - 1} width={width} height="2" fill={_c.border} />
      <rect x="0" y={height/2 - 2} width={w} height="4" fill={edge >= 0.05 ? _c.green : _c.amber} rx="1" />
      {/* tick at 5% (bet threshold) */}
      <line x1={(0.05/maxE)*width} x2={(0.05/maxE)*width} y1="0" y2={height} stroke={_c.fg3} strokeWidth="1" opacity="0.5" />
    </svg>
  );
}

// Stake bar (relative to 1.5u max)
function StakeBar({ u, width = 36, height = 6 }) {
  const v = Math.min(u, 1.5) / 1.5;
  return (
    <svg width={width} height={height} style={{ display: 'inline-block', verticalAlign: 'middle', marginLeft: 4 }}>
      <rect x="0" y={height/2 - 1} width={width} height="2" fill={_c.border} />
      <rect x="0" y={0} width={v*width} height={height} fill={_c.fg2} rx="1" />
    </svg>
  );
}

// Confidence dots: 3 dots filled by level
function ConfDots({ level }) {
  const map = { high: 3, med: 2, low: 1, pass: 0 };
  const n = map[level] ?? 0;
  const color = level === 'high' ? _c.green : level === 'med' ? _c.blue : _c.amber;
  return (
    <span style={{ display: 'inline-flex', gap: 2, verticalAlign: 'middle' }}>
      {[0,1,2].map(i => (
        <span key={i} style={{
          width: 5, height: 5, borderRadius: '50%',
          background: i < n ? color : _c.border, display: 'inline-block',
        }} />
      ))}
    </span>
  );
}

// CLV trend sparkline (5 points)
function CLVSpark({ trend, width = 50, height = 14 }) {
  if (!trend || trend.length < 2) return null;
  const minV = Math.min(...trend), maxV = Math.max(...trend);
  const range = maxV - minV || 1;
  const path = trend.map((v, i) => {
    const x = (i / (trend.length - 1)) * width;
    const y = height - ((v - minV) / range) * height;
    return `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`;
  }).join(' ');
  const trendUp = trend[trend.length - 1] > trend[0];
  return (
    <svg width={width} height={height} style={{ display: 'inline-block', verticalAlign: 'middle' }}>
      <path d={path} stroke={trendUp ? _c.green : _c.red} strokeWidth="1.25" fill="none" />
      <circle cx={width} cy={height - ((trend[trend.length-1] - minV) / range) * height}
              r="1.5" fill={trendUp ? _c.green : _c.red} />
    </svg>
  );
}

// Inline model-vs-market gap visualization
function ModelMarketBar({ modelP, marketP, width = 70, height = 12 }) {
  const xModel = modelP * width;
  const xMarket = marketP * width;
  const x1 = Math.min(xModel, xMarket);
  const x2 = Math.max(xModel, xMarket);
  return (
    <svg width={width} height={height} style={{ display: 'inline-block', verticalAlign: 'middle' }}>
      <line x1="0" x2={width} y1={height/2} y2={height/2} stroke={_c.border} strokeWidth="1" />
      <line x1={x1} x2={x2} y1={height/2} y2={height/2} stroke={modelP > marketP ? _c.green : _c.red} strokeWidth="2" />
      <line x1={xMarket} x2={xMarket} y1={2} y2={height-2} stroke={_c.fg3} strokeWidth="1" />
      <circle cx={xModel} cy={height/2} r="2.5" fill={_c.fg1} />
    </svg>
  );
}

// Label pill (BET / LEAN / PASS)
function LabelPill({ label }) {
  const map = {
    BET:  { bg: 'rgba(34,197,94,0.18)',  fg: _c.green, border: 'rgba(34,197,94,0.4)' },
    LEAN: { bg: 'rgba(245,158,11,0.15)', fg: _c.amber, border: 'rgba(245,158,11,0.35)' },
    PASS: { bg: 'rgba(80,88,105,0.2)',   fg: _c.fg3,   border: 'rgba(80,88,105,0.3)' },
  };
  const s = map[label] || map.PASS;
  return (
    <span style={{
      display: 'inline-block', padding: '1px 6px',
      background: s.bg, color: s.fg, border: `1px solid ${s.border}`,
      fontFamily: 'var(--sb-font-mono)', fontSize: 9, fontWeight: 700,
      letterSpacing: '0.06em', borderRadius: 2,
    }}>{label}</span>
  );
}

// Market type tag
function MarketTag({ market }) {
  const labels = { ML: 'ML', TOT: 'TOT', KPROP: 'K', HR: 'HR' };
  return (
    <span style={{
      fontFamily: 'var(--sb-font-mono)', fontSize: 9, fontWeight: 600,
      color: _c.fg3, letterSpacing: '0.04em', textTransform: 'uppercase',
      width: 28, display: 'inline-block', textAlign: 'right', marginRight: 6,
    }}>{labels[market] || market}</span>
  );
}

// ============================================================
// HEADER
// ============================================================
function Header() {
  return (
    <div className="bet-topbar">
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 3, flexWrap: 'nowrap', whiteSpace: 'nowrap' }}>
          <span style={{
            width: 18, height: 18, border: `2px solid ${_c.blue}`,
            borderRadius: 3, position: 'relative', display: 'inline-block',
          }}>
            <span style={{ position: 'absolute', top: '50%', left: '50%',
              width: 5, height: 5, borderRadius: '50%', background: _c.blue,
              transform: 'translate(-50%,-50%)' }}></span>
          </span>
          <span style={{ fontWeight: 700, fontSize: 14, letterSpacing: '-0.01em' }}>SugarBets</span>
          <span style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 12, color: _c.fg2 }}>/ MLB Slate</span>
        </div>
        <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 11, color: _c.fg3, marginTop: 4 }}>
          {SLATE.date} · {GAMES.length} games · {SLATE.book} live odds · Model {SLATE.model_version}
          {' · '}Starters {SLATE.starters_verified}/{SLATE.starters_total} verified
        </div>
      </div>
      <div className="nav-links">
        <a href="index.html" className="nav-link">← Dashboard</a>
        <a href="performance.html" className="nav-link">Performance ↗</a>
      </div>
    </div>
  );
}

// ============================================================
// EXECUTIVE BETS TABLE — the heart of the page
// ============================================================
function ExecBetsTable({ bets, onlyPlayable, hideLeans, minEdge, setOnlyPlayable, setHideLeans, setMinEdge }) {
  const filtered = bets.filter(b => {
    if (onlyPlayable && b.label === 'PASS') return false;
    if (hideLeans && b.label === 'LEAN') return false;
    if (b.edge < minEdge) return false;
    return true;
  });
  const betCount = filtered.filter(b => b.label === 'BET').length;
  const leanCount = filtered.filter(b => b.label === 'LEAN').length;
  const totalStake = filtered.reduce((a,b) => a + parseFloat(b.stakeU), 0);

  return (
    <div className="bet-section">
      <div className="bet-section-head">
        <div>
          <h2>Today's bets</h2>
          <div className="bet-section-sub">
            <span>{betCount} BET</span>
            <span>·</span>
            <span>{leanCount} LEAN</span>
            <span>·</span>
            <span>{totalStake.toFixed(1)}u total stake</span>
            <span style={{ marginLeft: 10, color: _c.fg3 }}>
              Playable rule: edge ≥ 3% · verified starter · market open · odds {'<'} 10 min stale
            </span>
          </div>
        </div>
        <div className="bet-filters">
          <label><input type="checkbox" checked={onlyPlayable} onChange={e => setOnlyPlayable(e.target.checked)} /> Only playable</label>
          <label><input type="checkbox" checked={hideLeans} onChange={e => setHideLeans(e.target.checked)} /> Hide leans</label>
          <div className="filter-pills">
            {[0.03, 0.05, 0.07].map(v => (
              <button key={v} className={'pill ' + (minEdge === v ? 'active' : '')} onClick={() => setMinEdge(v)}>
                ≥{(v*100).toFixed(0)}%
              </button>
            ))}
          </div>
        </div>
      </div>

      <table className="bet-table">
        <thead>
          <tr>
            <th style={{ width: 24 }}>#</th>
            <th style={{ width: 38 }}>M</th>
            <th>Pick</th>
            <th className="num" style={{ width: 50 }}>Price</th>
            <th style={{ width: 110 }}>Model vs Market</th>
            <th className="num" style={{ width: 50 }}>Edge</th>
            <th style={{ width: 80 }}></th>
            <th className="num" style={{ width: 50 }}>Fair</th>
            <th className="num" style={{ width: 56 }}>Stake</th>
            <th style={{ width: 36 }}>Conf</th>
            <th>Why / risk</th>
          </tr>
        </thead>
        <tbody>
          {filtered.map((b, i) => (
            <tr key={i} className={'lab-' + b.label.toLowerCase()}>
              <td className="rank">{i + 1}</td>
              <td><MarketTag market={b.market} /></td>
              <td className="pick">
                <span className="pick-name">{b.pick}</span>
                <span className="pick-game"> · {b.game.away}@{b.game.home}</span>
              </td>
              <td className="num odds">{_fmt.odds(b.odds)}</td>
              <td className="num model-market">
                <span style={{ color: _c.fg1, fontFamily: 'var(--sb-font-mono)' }}>
                  {(b.modelP*100).toFixed(0)}% · <span style={{ color: _c.fg3 }}>{(b.marketP*100).toFixed(0)}%</span>
                </span>
                <ModelMarketBar modelP={b.modelP} marketP={b.marketP} />
              </td>
              <td className="num edge">
                <span style={{ color: b.edge >= 0.05 ? _c.green : _c.amber, fontWeight: 600 }}>
                  +{(b.edge*100).toFixed(1)}%
                </span>
              </td>
              <td><EdgeBar edge={b.edge} /></td>
              <td className="num">{_fmt.odds(b.fair)}</td>
              <td className="num stake">
                {b.stakeU}u
                <StakeBar u={parseFloat(b.stakeU)} />
              </td>
              <td><ConfDots level={b.confidence} /> <LabelPill label={b.label} /></td>
              <td className="why">
                <span style={{ color: _c.fg2 }}>{b.reason}</span>
                {b.risk && <span style={{ color: _c.amber, marginLeft: 8 }}>· risk: {b.risk}</span>}
              </td>
            </tr>
          ))}
          {filtered.length === 0 && (
            <tr><td colSpan={11} style={{ textAlign: 'center', padding: 24, color: _c.fg3 }}>
              No bets match these filters.
            </td></tr>
          )}
        </tbody>
      </table>
    </div>
  );
}

// ============================================================
// GAME BOARD — game-first, market details nested
// ============================================================
function GameBoard({ rows }) {
  const [expanded, setExpanded] = useState(null);

  return (
    <div className="bet-section">
      <div className="bet-section-head">
        <div>
          <h2>Game board</h2>
          <div className="bet-section-sub">
            <span>{rows.length} games · all markets per game · click row to expand</span>
          </div>
        </div>
      </div>
      <table className="game-table">
        <thead>
          <tr>
            <th style={{ width: 80 }}>Game</th>
            <th style={{ width: 60 }}>Time</th>
            <th>Pitchers</th>
            <th style={{ width: 100 }}>Park · Wx</th>
            <th style={{ width: 130 }}>Moneyline</th>
            <th style={{ width: 130 }}>Total</th>
            <th>Best play</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((g, i) => {
            const isExp = expanded === g.gameKey;
            const best = g.bets[0];
            const [vfA, vfH] = [g.dk_away, g.dk_home].map(o => o<0 ? Math.abs(o)/(Math.abs(o)+100) : 100/(o+100));
            const vfSum = vfA + vfH;
            const mktA = vfA/vfSum, mktH = vfH/vfSum;
            const edgeA = g.away_win - mktA;
            const edgeH = g.home_win - mktH;
            const tdelta = g.totalDelta;
            return (
              <React.Fragment key={g.gameKey}>
                <tr className={isExp ? 'expanded' : ''} onClick={() => setExpanded(isExp ? null : g.gameKey)}>
                  <td className="game-cell">
                    <span style={{ fontWeight: 600 }}>{g.away}</span>
                    <span style={{ color: _c.fg3, margin: '0 4px' }}>@</span>
                    <span style={{ fontWeight: 600 }}>{g.home}</span>
                  </td>
                  <td className="num game-time">
                    {new Date(g.commence).toLocaleTimeString('en-US', {hour:'numeric', minute:'2-digit', timeZone:'America/New_York'})}
                  </td>
                  <td className="pitchers">
                    <span>{g.away_pitcher}</span>
                    <span style={{ color: _c.fg3, margin: '0 6px' }}>vs</span>
                    <span style={{ color: g.home_pitcher === 'TBD' ? _c.amber : _c.fg1 }}>{g.home_pitcher}</span>
                  </td>
                  <td className="park">
                    <div>{g.park}</div>
                    <div style={{ color: _c.fg3, fontSize: 10 }}>{g.weather}</div>
                  </td>
                  <td className="ml-cell">
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                      <span style={{ color: edgeA > edgeH ? (edgeA > 0.03 ? _c.green : _c.fg1) : _c.fg3, fontWeight: 600 }}>{g.away}</span>
                      <span style={{ color: _c.fg3 }}>·</span>
                      <span style={{ color: edgeH > edgeA ? (edgeH > 0.03 ? _c.green : _c.fg1) : _c.fg3, fontWeight: 600 }}>{g.home}</span>
                    </div>
                    <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 10, color: _c.fg3, marginTop: 1 }}>
                      {(g.away_win*100).toFixed(0)}/{(g.home_win*100).toFixed(0)}
                      <span style={{ color: g.mlEdge > 0.05 ? _c.green : g.mlEdge > 0.025 ? _c.amber : _c.fg3, marginLeft: 6 }}>
                        +{(g.mlEdge*100).toFixed(1)}%
                      </span>
                    </div>
                  </td>
                  <td className="tot-cell">
                    <div style={{ fontFamily: 'var(--sb-font-mono)' }}>
                      <span style={{ color: _c.fg1 }}>{g.model_total.toFixed(1)}</span>
                      <span style={{ color: _c.fg3, margin: '0 4px' }}>vs</span>
                      <span style={{ color: _c.fg3 }}>{g.dk_total.toFixed(1)}</span>
                    </div>
                    <div style={{ fontSize: 10, fontFamily: 'var(--sb-font-mono)', color: _c.fg3, marginTop: 1 }}>
                      {tdelta >= 0 ? <span style={{ color: tdelta >= 1 ? _c.green : _c.fg3 }}>Over +{tdelta.toFixed(1)}r</span> : <span style={{ color: tdelta <= -1 ? _c.green : _c.fg3 }}>Under {tdelta.toFixed(1)}r</span>}
                    </div>
                  </td>
                  <td className="best-play">
                    {best ? (
                      <span>
                        <LabelPill label={best.label} />
                        <span style={{ marginLeft: 6, color: _c.fg1 }}>{best.pick}</span>
                        <span style={{ color: _c.fg3, marginLeft: 6 }}>+{(best.edge*100).toFixed(1)}% · {best.stakeU}u</span>
                      </span>
                    ) : (
                      <span style={{ color: _c.fg3 }}>—</span>
                    )}
                  </td>
                </tr>
                {isExp && (
                  <tr className="game-expand">
                    <td colSpan={7}>
                      <ExpandedGame game={g} />
                    </td>
                  </tr>
                )}
              </React.Fragment>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function ExpandedGame({ game }) {
  return (
    <div style={{ padding: '14px 16px', background: _c.surface2, borderRadius: 6, fontSize: 12 }}>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 24 }}>
        <div>
          <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 10, color: _c.fg3, textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 6 }}>
            Moneyline detail
          </div>
          <div style={{ fontFamily: 'var(--sb-font-mono)', fontVariantNumeric: 'tabular-nums' }}>
            <div>{game.away}: model {(game.away_win*100).toFixed(1)}% · DK {_fmt.odds(game.dk_away)}</div>
            <div>{game.home}: model {(game.home_win*100).toFixed(1)}% · DK {_fmt.odds(game.dk_home)}</div>
          </div>
        </div>
        <div>
          <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 10, color: _c.fg3, textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 6 }}>
            Total detail
          </div>
          <div style={{ fontFamily: 'var(--sb-font-mono)', fontVariantNumeric: 'tabular-nums' }}>
            <div>Model: {game.model_total.toFixed(2)}r · σ {game.model_total_std.toFixed(1)}</div>
            <div>Line: {game.dk_total} ({_fmt.odds(game.dk_over)}/{_fmt.odds(game.dk_under)})</div>
            <div style={{ color: _c.fg2, marginTop: 2 }}>Δ {game.totalDelta >= 0 ? '+' : ''}{game.totalDelta.toFixed(2)}r</div>
          </div>
        </div>
        <div>
          <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 10, color: _c.fg3, textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 6 }}>
            Context
          </div>
          <div style={{ color: _c.fg2 }}>
            <div>{game.park}</div>
            <div>{game.weather}</div>
            {game.note && <div style={{ color: _c.amber, marginTop: 4 }}>⚠ {game.note}</div>}
          </div>
        </div>
      </div>
      {game.bets.length > 0 && (
        <div style={{ marginTop: 14, paddingTop: 14, borderTop: `1px solid ${_c.border}` }}>
          <div style={{ fontFamily: 'var(--sb-font-mono)', fontSize: 10, color: _c.fg3, textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 6 }}>
            Plays from this game
          </div>
          {game.bets.map((b, i) => (
            <div key={i} style={{
              display: 'flex', gap: 12, padding: '5px 0',
              borderBottom: i < game.bets.length-1 ? `1px solid rgba(45,52,68,0.4)` : 'none',
              fontFamily: 'var(--sb-font-mono)', fontSize: 12, fontVariantNumeric: 'tabular-nums', alignItems: 'center',
            }}>
              <LabelPill label={b.label} />
              <span style={{ flex: 1, color: _c.fg1 }}>{b.pick}</span>
              <span style={{ color: _c.fg3 }}>{_fmt.odds(b.odds)}</span>
              <span style={{ color: _c.green }}>+{(b.edge*100).toFixed(1)}%</span>
              <span style={{ color: _c.fg2 }}>{b.stakeU}u</span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ============================================================
// DIAGNOSTICS STRIP — ROI, CLV, calibration by market
// ============================================================
function DiagStrip() {
  return (
    <div className="bet-section">
      <div className="bet-section-head">
        <div>
          <h2>Diagnostics by market <span style={{ fontWeight: 400, color: _c.fg3, fontSize: 12 }}>· last 30 days</span></h2>
          <div className="bet-section-sub">
            <span>Treat HR-props and Totals with caution — both have ECE {'>'} 10%, meaning the model probabilities are not trustworthy yet.</span>
          </div>
        </div>
      </div>
      <table className="diag-table">
        <thead>
          <tr>
            <th>Market</th>
            <th className="num">Bets</th>
            <th className="num">ROI</th>
            <th className="num">CLV</th>
            <th className="num">Win%</th>
            <th className="num">Expected</th>
            <th>Distribution</th>
            <th>Status</th>
          </tr>
        </thead>
        <tbody>
          {DIAGNOSTICS.byMarket.map((m, i) => {
            const ok = m.roi > 0;
            const calOk = m.calibration === 'good';
            return (
              <tr key={i}>
                <td className="market-name">{m.market}</td>
                <td className="num">{m.bets}</td>
                <td className="num" style={{ color: ok ? _c.green : _c.red, fontWeight: 600 }}>
                  {m.roi >= 0 ? '+' : ''}{(m.roi*100).toFixed(1)}%
                </td>
                <td className="num" style={{ color: m.clv >= 0 ? _c.green : _c.red }}>
                  {m.clv >= 0 ? '+' : ''}{(m.clv*100).toFixed(1)}pp
                </td>
                <td className="num">{(m.hitRate*100).toFixed(1)}%</td>
                <td className="num" style={{ color: _c.fg3 }}>{(m.expected*100).toFixed(1)}%</td>
                <td><MarketDist hit={m.hitRate} expected={m.expected} /></td>
                <td>
                  <span style={{
                    fontFamily: 'var(--sb-font-mono)', fontSize: 10, fontWeight: 600,
                    color: calOk ? _c.green : m.calibration === 'weak' ? _c.amber : _c.red,
                    textTransform: 'uppercase', letterSpacing: '0.04em',
                  }}>
                    {m.calibration}
                  </span>
                  <span style={{ color: _c.fg3, marginLeft: 6, fontSize: 10 }}>ECE {(m.ece*100).toFixed(0)}%</span>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

function MarketDist({ hit, expected }) {
  // Mini horizontal bar with two markers
  const w = 90, h = 12;
  const xHit = hit * w;
  const xExp = expected * w;
  return (
    <svg width={w} height={h}>
      <rect x="0" y={h/2 - 1} width={w} height="2" fill={_c.border} />
      <line x1={xExp} x2={xExp} y1={2} y2={h-2} stroke={_c.fg3} strokeWidth="1" strokeDasharray="2 2" />
      <circle cx={xHit} cy={h/2} r="3" fill={hit > expected ? _c.green : _c.red} />
    </svg>
  );
}

// ============================================================
// BIAS MULTIPLES — small multiples grid
// ============================================================
function BiasMultiples() {
  const panels = [
    { key: 'by_spread',    title: 'By favorite size',  rows: DIAGNOSTICS.bias.by_spread },
    { key: 'by_edge',      title: 'By edge bucket',    rows: DIAGNOSTICS.bias.by_edge },
    { key: 'by_total',     title: 'By total range',    rows: DIAGNOSTICS.bias.by_total },
    { key: 'by_home_away', title: 'Home vs away fav',  rows: DIAGNOSTICS.bias.by_home_away },
    { key: 'by_dome',      title: 'Dome vs outdoor',   rows: DIAGNOSTICS.bias.by_dome },
    { key: 'by_lineup',    title: 'Lineup status',     rows: DIAGNOSTICS.bias.by_lineup },
  ];
  return (
    <div className="bet-section">
      <div className="bet-section-head">
        <div>
          <h2>Model bias · small multiples</h2>
          <div className="bet-section-sub">
            <span>ML accuracy by slice · break-even 52.4% · dot size = sample · {DIAGNOSTICS.byMarket[0].bets + DIAGNOSTICS.byMarket[1].bets} games</span>
          </div>
        </div>
      </div>
      <div className="bias-grid">
        {panels.map(p => <BiasPanel key={p.key} title={p.title} rows={p.rows} />)}
      </div>
    </div>
  );
}

function BiasPanel({ title, rows }) {
  const vmin = 0.30, vmax = 0.80;
  const ref = 0.524;
  const w = 160, rowH = 16;
  const labelW = 78, valW = 36;
  const plotW = w - labelW - valW - 8;
  const xScale = (v) => labelW + ((v - vmin) / (vmax - vmin)) * plotW;
  const maxN = Math.max(...rows.map(r => r.n));
  const dotR = (n) => 2.5 + Math.sqrt(n / maxN) * 3;
  return (
    <div className="bias-panel">
      <div className="bias-title">{title}</div>
      <svg width={w} height={rows.length * rowH + 4} className="bias-svg">
        {rows.map((r, i) => {
          const y = i * rowH + rowH/2 + 2;
          const dotX = xScale(r.acc);
          const ok = r.acc >= ref;
          return (
            <g key={i}>
              <text x={0} y={y + 3} fontSize="10" fill={_c.fg1} fontFamily="var(--sb-font-body)">{r.slice}</text>
              <line x1={labelW} x2={labelW + plotW} y1={y} y2={y} stroke={_c.border} strokeWidth="1" />
              <line x1={xScale(ref)} x2={xScale(ref)} y1={y - rowH/2 + 2} y2={y + rowH/2 - 2}
                    stroke={_c.fg3} strokeWidth="1" strokeDasharray="2 2" opacity="0.6" />
              <circle cx={dotX} cy={y} r={dotR(r.n)} fill={ok ? _c.green : _c.red} opacity={0.85} />
              <text x={w - 2} y={y + 3} fontSize="10" fontFamily="var(--sb-font-mono)" fill={ok ? _c.green : _c.red} textAnchor="end" style={{ fontVariantNumeric: 'tabular-nums' }}>
                {(r.acc*100).toFixed(0)}%
              </text>
            </g>
          );
        })}
      </svg>
    </div>
  );
}

// ============================================================
// AUDIT TRAIL — recent settled bets
// ============================================================
function AuditTrail() {
  const total = AUDIT_TRAIL.reduce((a,b) => a + b.units, 0);
  const wins = AUDIT_TRAIL.filter(b => b.result === 'W').length;
  const totalClv = AUDIT_TRAIL.reduce((a,b) => a + b.clv, 0);
  return (
    <div className="bet-section">
      <div className="bet-section-head">
        <div>
          <h2>Audit trail <span style={{ fontWeight: 400, color: _c.fg3, fontSize: 12 }}>· last 10 settled bets</span></h2>
          <div className="bet-section-sub">
            <span>{wins}-{AUDIT_TRAIL.length - wins}</span><span>·</span>
            <span style={{ color: total >= 0 ? _c.green : _c.red, fontWeight: 600 }}>{_fmt.units(total)}</span><span>·</span>
            <span style={{ color: totalClv >= 0 ? _c.green : _c.red }}>avg CLV {(totalClv/AUDIT_TRAIL.length*100 >= 0 ? '+' : '')}{(totalClv/AUDIT_TRAIL.length*100).toFixed(2)}pp</span>
          </div>
        </div>
      </div>
      <table className="audit-table">
        <thead>
          <tr>
            <th>Date</th>
            <th>Pick</th>
            <th>Book</th>
            <th className="num">Price @ bet</th>
            <th className="num">Closed</th>
            <th className="num">CLV</th>
            <th className="num">Model%</th>
            <th>Result</th>
            <th className="num">Units</th>
          </tr>
        </thead>
        <tbody>
          {AUDIT_TRAIL.map((r, i) => {
            const movePos = r.clv > 0;
            return (
              <tr key={i}>
                <td className="dim">{r.date}</td>
                <td>{r.pick}</td>
                <td className="dim">{r.book}</td>
                <td className="num">{_fmt.odds(r.priceAtBet)}</td>
                <td className="num dim">{_fmt.odds(r.closingPrice)}</td>
                <td className="num" style={{ color: movePos ? _c.green : _c.red }}>
                  {r.clv >= 0 ? '+' : ''}{(r.clv*100).toFixed(1)}pp
                </td>
                <td className="num">{(r.modelP*100).toFixed(1)}%</td>
                <td>
                  <span style={{
                    fontFamily: 'var(--sb-font-mono)', fontSize: 10, fontWeight: 700,
                    color: r.result === 'W' ? _c.green : _c.red,
                  }}>{r.result}</span>
                </td>
                <td className="num" style={{ color: r.units >= 0 ? _c.green : _c.red, fontWeight: 600 }}>
                  {_fmt.units(r.units)}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

// ============================================================
// APP
// ============================================================
function BettingApp() {
  const [onlyPlayable, setOnlyPlayable] = useState(true);
  const [hideLeans, setHideLeans] = useState(false);
  const [minEdge, setMinEdge] = useState(0.03);

  const bets = useMemo(() => buildBets(), []);
  const gameRows = useMemo(() => buildGameRows(bets), [bets]);

  return (
    <div className="bet-app">
      <Header />
      <ExecBetsTable
        bets={bets}
        onlyPlayable={onlyPlayable}
        hideLeans={hideLeans}
        minEdge={minEdge}
        setOnlyPlayable={setOnlyPlayable}
        setHideLeans={setHideLeans}
        setMinEdge={setMinEdge}
      />
      <GameBoard rows={gameRows} />
      <DiagStrip />
      <BiasMultiples />
      <AuditTrail />
      <div className="bet-footer">
        Model projections for informational purposes only. Past performance does not guarantee future results. Gamble responsibly.
      </div>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<BettingApp />);

Object.assign(window, { BettingApp, ExecBetsTable, GameBoard, DiagStrip, BiasMultiples, AuditTrail });
