// theclaudetrader — main App component

const { useState, useEffect, useMemo } = React;

// Tweaks defaults
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "fundName": "theclaudetrader",
  "accent": "rust",
  "density": "comfortable",
  "showBenchmark": true
}/*EDITMODE-END*/;

const ACCENTS = {
  rust:   { light: "#C6513A", dark: "#E88A74", soft: "#F3DCD4", softDark: "#3A2822", label: "Rust" },
  ink:    { light: "#1A1A1A", dark: "#F5F2EA", soft: "#E5E1D8", softDark: "#2D2C26", label: "Ink" },
  forest: { light: "#2E6A4E", dark: "#7BC19C", soft: "#DCEDE2", softDark: "#1E3A2A", label: "Forest" },
  cobalt: { light: "#2B4A8B", dark: "#8AA7DA", soft: "#DCE4F2", softDark: "#1C2940", label: "Cobalt" },
};

function applyAccent(accent, theme) {
  const c = ACCENTS[accent] || ACCENTS.rust;
  const root = document.documentElement;
  if (theme === "dark") {
    root.style.setProperty("--accent", c.dark);
    root.style.setProperty("--accent-soft", c.softDark);
  } else {
    root.style.setProperty("--accent", c.light);
    root.style.setProperty("--accent-soft", c.soft);
  }
}

function Nav({ fund, theme, setTheme, tab, setTab }) {
  const tabStyle = (active) => ({
    fontSize: 13,
    padding: "6px 12px",
    borderRadius: 6,
    fontWeight: active ? 500 : 400,
    background: active ? "var(--bg-card)" : "transparent",
    color: active ? "var(--ink)" : "var(--ink-3)",
    border: active ? "1px solid var(--line)" : "1px solid transparent",
    cursor: "pointer",
  });
  return (
    <nav style={{
      position: "sticky", top: 0, zIndex: 50,
      background: "color-mix(in oklab, var(--bg) 92%, transparent)",
      backdropFilter: "saturate(180%) blur(10px)",
      WebkitBackdropFilter: "saturate(180%) blur(10px)",
      borderBottom: "1px solid var(--line)",
    }}>
      <div className="container" style={{ display: "flex", alignItems: "center", justifyContent: "space-between", height: 62 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 18 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            <svg width="22" height="22" viewBox="0 0 24 24" aria-hidden>
              <path d="M6 3h4l2.5 7L15 3h4l-6 18h-2L6 3z" fill="var(--accent)" />
            </svg>
            <span style={{ fontFamily: "var(--serif)", fontSize: 20, letterSpacing: "-0.01em" }}>{fund.name}</span>
          </div>
          <div style={{ display: "flex", gap: 4, marginLeft: 8 }}>
            <button style={tabStyle(tab === "main")} onClick={() => setTab("main")}>LLM Fund</button>
            <button style={tabStyle(tab === "etf")} onClick={() => setTab("etf")}>ETF 50/50</button>
            <button style={tabStyle(tab === "cpo")} onClick={() => setTab("cpo")}>Serenity Portfolio</button>
            <button style={tabStyle(tab === "mirror")} onClick={() => setTab("mirror")}>Live Fund</button>
            <button style={tabStyle(tab === "verify")} onClick={() => setTab("verify")}>Verify</button>
          </div>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 22 }}>
          <div className="mono" style={{ fontSize: 11.5, color: "var(--ink-3)", display: "flex", alignItems: "center", gap: 8 }}>
            <span className="pulse" /> Live · updated {fmtTime(fund.last_updated)}
          </div>
          {tab === "main" && (
            <>
              <a href="#performance" className="hide-mobile" style={{ fontSize: 13, color: "var(--ink-2)" }}>Performance</a>
              <a href="#holdings" className="hide-mobile" style={{ fontSize: 13, color: "var(--ink-2)" }}>Holdings</a>
              <a href="#activity" className="hide-mobile" style={{ fontSize: 13, color: "var(--ink-2)" }}>Activity</a>
              <a href="#daily-logs" className="hide-mobile" style={{ fontSize: 13, color: "var(--ink-2)" }}>Journal</a>
              <a href="#methodology" className="hide-mobile" style={{ fontSize: 13, color: "var(--ink-2)" }}>Method</a>
            </>
          )}
          <button
            className="btn-ghost"
            onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
            aria-label="Toggle theme"
            style={{ padding: "5px 10px" }}
          >
            {theme === "dark" ? "☾" : "☀"}
          </button>
        </div>
      </div>
    </nav>
  );
}


// ---------- ETF 60/30/10 view ----------
// Pure-script sister fund: 60% QQQM / 30% SMH / 10% XLK on a separate Alpaca paper
// account. Data comes from web/data/etf_portfolio.json which scripts/etf_run.py
// snapshots every ~30 minutes. No LLM, no signal feeds — strict allocation discipline.

// VerifyView — the public audit trail. Fetches /api/reconciliation and shows the
// decision→order→fill→P&L table (including cancels/rejects and decisions never
// acted on), plus how to verify the Bitcoin-anchored OpenTimestamps proofs.
function VerifyView() {
  const [rec, setRec] = useState(null);
  const [error, setError] = useState(null);
  useEffect(() => {
    fetch(`/api/reconciliation?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(setRec)
      .catch((e) => setError(e.message));
  }, []);

  if (error) return <div style={{ padding: 40, fontFamily: "var(--mono)" }}>Failed to load audit trail: {error}</div>;
  if (!rec) return <div style={{ height: "60vh", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontFamily: "var(--mono)" }}>Loading audit trail…</div>;

  const s = rec.summary || {};
  const bs = s.by_status || {};
  const link = s.decision_link_coverage || {};
  const errs = s.model_errors_14d || {};
  const today = new Date().toISOString().slice(0, 10);
  const pnl = (v) => v == null ? "" : (v >= 0 ? "+" : "") + "$" + Math.abs(v).toLocaleString(undefined, { maximumFractionDigits: 0 });
  const card = { border: "1px solid var(--line)", borderRadius: 10, padding: "14px 16px", background: "var(--bg-card)" };
  const stat = (label, val, sub) => (
    <div style={card}>
      <div style={{ fontSize: 11, color: "var(--ink-3)", textTransform: "uppercase", letterSpacing: "0.04em" }}>{label}</div>
      <div style={{ fontSize: 22, fontFamily: "var(--serif)", marginTop: 4 }}>{val}</div>
      {sub && <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 2 }}>{sub}</div>}
    </div>
  );

  return (
    <section style={{ paddingTop: 40, paddingBottom: 40 }}>
      <div className="container">
        <div className="eyebrow" style={{ marginBottom: 10 }}>Independently verifiable</div>
        <h2 className="h-section" style={{ marginBottom: 8 }}>The audit trail — including the trades we'd rather you didn't see.</h2>
        <p className="muted" style={{ maxWidth: 760, marginBottom: 24, fontSize: 14 }}>
          Every trade is linked to the AI decision behind it, the Alpaca order id, the broker's fill
          time, and realized P&amp;L. We deliberately show losers, cancelled and dead orders, model
          errors, and decisions the AI made but never acted on. Each day's record is hashed and
          anchored to the Bitcoin blockchain (OpenTimestamps) so it can't be backdated. Generated
          from the database every publish — not hand-authored.
        </p>

        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: 12, marginBottom: 22 }}>
          {stat("Trades on record", s.trades_total ?? "—", `${(bs.FILLED || 0) + (bs.BACKFILL || 0)} filled`)}
          {stat("Cancelled / dead", (bs.CANCELLED || 0) + (bs.ORDER_GONE || 0), "kept, not hidden")}
          {stat("Recommended, not placed", s.recommended_not_placed ?? "—", `${s.placed ?? "—"} were placed`)}
          {stat("Realized P&L", pnl(s.realized_pnl_to_date), "FIFO, to date")}
          {stat("Decision links", `${link.fk || 0} FK`, `${link.matched || 0} matched · ${link.none || 0} backfill`)}
          {stat("Model errors (14d)", `${errs.failed_runs || 0} / ${errs.structured_output_retries || 0}`, "failed runs / retries")}
        </div>

        <div style={{ ...card, marginBottom: 24 }}>
          <div style={{ fontFamily: "var(--serif)", fontSize: 18, marginBottom: 8 }}>Verify it yourself — free, no account</div>
          <p className="muted" style={{ fontSize: 13, marginBottom: 10 }}>
            The day's audit record is anchored to Bitcoin via OpenTimestamps (we pay nothing; you need
            no wallet). Confirm it existed and wasn't edited:
          </p>
          <pre style={{ background: "var(--bg)", border: "1px solid var(--line)", borderRadius: 8, padding: "12px 14px", fontSize: 12, overflowX: "auto", fontFamily: "var(--mono)" }}>{`pip install opentimestamps-client
curl -O /data/audit/${today}.json
curl -O /data/audit/${today}.json.ots
ots verify ${today}.json.ots`}</pre>
          <div style={{ display: "flex", gap: 14, flexWrap: "wrap", fontSize: 12.5, marginTop: 8 }}>
            <a href={`/data/audit/${today}.json`} target="_blank" style={{ color: "var(--accent)" }}>audit record (JSON)</a>
            <a href={`/data/audit/${today}.json.ots`} target="_blank" style={{ color: "var(--accent)" }}>Bitcoin proof (.ots)</a>
            <a href="/api/verify" target="_blank" style={{ color: "var(--accent)" }}>/api/verify</a>
            <a href="/data/broker_activity.json" target="_blank" style={{ color: "var(--accent)" }}>broker-sourced fills</a>
            <a href="/api/reconciliation" target="_blank" style={{ color: "var(--accent)" }}>full reconciliation JSON</a>
          </div>
          <p className="muted" style={{ fontSize: 11.5, marginTop: 10 }}>
            OpenTimestamps proves existence-by-time, not correctness, and is forward-only from launch.
            A "pending" result just means Bitcoin confirmation (a few hours) hasn't landed yet.
          </p>
        </div>

        <div className="card" style={{ padding: "8px 0 4px", overflowX: "auto" }}>
          <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12.5 }}>
            <thead>
              <tr style={{ textAlign: "left", color: "var(--ink-3)", fontSize: 11, textTransform: "uppercase", letterSpacing: "0.04em" }}>
                <th style={{ padding: "8px 12px" }}>Decision time</th>
                <th style={{ padding: "8px 12px" }}>Signal</th>
                <th style={{ padding: "8px 12px" }}>Ticker</th>
                <th style={{ padding: "8px 12px" }}>Side</th>
                <th style={{ padding: "8px 12px" }}>Qty</th>
                <th style={{ padding: "8px 12px" }}>Price</th>
                <th style={{ padding: "8px 12px" }}>Status</th>
                <th style={{ padding: "8px 12px" }}>P&L</th>
                <th style={{ padding: "8px 12px" }}>Alpaca order</th>
              </tr>
            </thead>
            <tbody>
              {(rec.rows || []).slice(0, 60).map((r) => (
                <tr key={r.trade_id} style={{ borderTop: "1px solid var(--line)" }}>
                  <td className="tnum" style={{ padding: "7px 12px", color: "var(--ink-3)" }}>{r.decision_time ? r.decision_time.replace("T", " ").slice(0, 16) : "—"}</td>
                  <td style={{ padding: "7px 12px" }}>{r.signal || "—"}</td>
                  <td style={{ padding: "7px 12px", fontWeight: 600 }}>{r.ticker}</td>
                  <td style={{ padding: "7px 12px", color: r.side === "BUY" ? "var(--pos, #2e7d32)" : "var(--neg, #b3462d)" }}>{r.side}</td>
                  <td className="tnum" style={{ padding: "7px 12px" }}>{r.qty || "—"}</td>
                  <td className="tnum" style={{ padding: "7px 12px" }}>{r.price ? "$" + r.price : "—"}</td>
                  <td style={{ padding: "7px 12px", fontSize: 11 }}>{r.status}</td>
                  <td className="tnum" style={{ padding: "7px 12px", color: (r.realized_pnl || 0) >= 0 ? "var(--pos, #2e7d32)" : "var(--neg, #b3462d)" }}>{r.realized_pnl != null ? pnl(r.realized_pnl) : ""}</td>
                  <td className="tnum" style={{ padding: "7px 12px", color: "var(--ink-3)", fontSize: 10.5 }} title={r.order_id || ""}>{r.order_id ? r.order_id.slice(0, 8) + "…" : "—"}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        <p className="muted" style={{ fontSize: 11.5, marginTop: 10 }}>
          Showing the most recent {Math.min((rec.rows || []).length, 60)} of {s.trades_total ?? "—"} trades.
          Decision link: <strong>FK</strong> = hard foreign-key to the decision row; <strong>matched</strong> =
          best-effort by ticker+date; <strong>backfill</strong> = reconciliation row with no stored decision
          (older/manual/orphan fills). Full set at <a href="/api/reconciliation" style={{ color: "var(--accent)" }}>/api/reconciliation</a>.
        </p>
      </div>
    </section>
  );
}

function MirrorView() {
  // Live real-money fund (paper replica retired 2026-06-18). Reads the static
  // live_portfolio.json that Alice publishes (live keys never touch Vercel/this
  // Mac). Metrics are deposit-aware (patch_live_deposits.py): return is on TOTAL
  // contributed capital, and the SPY-instead invests each deposit on its day.
  const [lf, setLf] = useState(null);
  const [err, setErr] = useState(null);
  useEffect(() => {
    fetch(`data/live_portfolio.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(setLf)
      .catch((e) => setErr(e.message));
  }, []);

  if (err) return <div style={{ padding: 40, fontFamily: "var(--mono)" }}>Failed to load Live fund: {err}</div>;
  if (!lf) return <div style={{ height: "60vh", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontFamily: "var(--mono)" }}>Loading Live fund…</div>;

  const cur = lf.current || {};
  const m = lf.metrics || {};
  const positions = lf.positions || [];
  const orders = lf.orders || [];
  const deposits = m.deposits || [];
  const contributed = m.total_contributed ?? m.starting_capital ?? 94;
  const nav = cur.nav ?? contributed;
  const ret = m.return_since_inception ?? (contributed > 0 ? nav / contributed - 1 : 0);
  const spyInstead = m.spy_invested_value;
  const alpha = m.alpha_vs_spy_pp;
  const ready = cur && cur.nav != null && lf.status !== "awaiting_setup";
  const hist = [...(lf.history || [])].sort((a, b) => String(a.date).localeCompare(String(b.date)));
  const perfPoints = hist.map((h) => ({ date: h.date, value: h.nav, benchmark: null }));

  if (!ready) {
    return (
      <section style={{ paddingTop: 60 }}>
        <div className="container">
          <h1 className="h-display">The <em style={{ color: "var(--accent)" }}>live</em> fund.</h1>
          <p className="lead" style={{ marginTop: 16 }}>Runs on a separate machine — real-money keys never touch this site. Data appears once it publishes its first snapshot.</p>
        </div>
      </section>
    );
  }

  return (
    <div>
      <section style={{ paddingTop: 60, paddingBottom: 40 }}>
        <div className="container">
          <div style={{ marginBottom: 22 }}>
            <div className="eyebrow" style={{ marginBottom: 10 }}>Real money · forward-only · weight-matched</div>
            <h1 className="h-display" style={{ textWrap: "balance" }}>
              The <em style={{ fontStyle: "italic", color: "var(--accent)" }}>live</em> fund.
            </h1>
            <p className="lead" style={{ marginTop: 16 }}>
              A <strong>real-money</strong> account that mirrors the LLM fund in real time, scaled to its own capital — each LLM
              trade replicated as the <em>same % of NAV</em> it was for the LLM fund. It copies trades <strong>forward from its
              own start</strong> (no back-fill), runs <strong>no debates or discipline of its own</strong>, and mirrors
              <strong> only the LLM fund</strong>. Returns are measured against <strong>total deposited capital</strong>, and the
              S&amp;P benchmark invests each deposit on the day it landed.
            </p>
          </div>

          <div style={{
            display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
            border: "1px solid var(--line)", borderRadius: 12, overflow: "hidden",
            background: "var(--bg-card)", marginTop: 24,
          }}>
            <HeroStat label="Net asset value" value={fmtUSD(nav, { decimals: 2 })} sub={`from ${fmtUSD(contributed, { decimals: 0 })} contributed`} />
            <HeroStat label="Return on contributed" value={<span className={ret >= 0 ? "pos" : "neg"}>{ret >= 0 ? "+" : ""}{(ret * 100).toFixed(2)}%</span>} sub={`${deposits.length} deposit${deposits.length === 1 ? "" : "s"} totalling ${fmtUSD(contributed, { decimals: 0 })}`} />
            <HeroStat label="Cash" value={fmtUSD(cur.cash ?? 0, { decimals: 2 })} sub={`deployed ${(((cur.deployed_pct ?? 0)) * 100).toFixed(0)}%`} />
            <HeroStat label="Positions" value={`${positions.length}`} sub="held names" />
            {spyInstead != null && (
              <HeroStat
                label="Same deposits in S&P 500"
                value={fmtUSD(spyInstead, { decimals: 2 })}
                sub={alpha != null ? `alpha ${alpha >= 0 ? "+" : ""}${alpha.toFixed(2)}pp (each deposit bought SPY on its day)` : "deposit-weighted"}
              />
            )}
            {m.cash_drag_pnl != null && (
              <HeroStat
                label="Cost of cash discipline"
                value={<span className={m.cash_drag_pnl >= 0 ? "pos" : "neg"}>{m.cash_drag_pnl >= 0 ? "+" : ""}{fmtUSD(m.cash_drag_pnl, { decimals: 2 })}</span>}
                sub="NAV vs same deposits in SPY"
              />
            )}
            {m.realized_pnl != null && (
              <HeroStat
                label="Realized P&L"
                value={<span className={m.realized_pnl >= 0 ? "pos" : "neg"}>{m.realized_pnl >= 0 ? "+" : ""}{fmtUSD(m.realized_pnl, { decimals: 2 })}</span>}
                sub={m.tax_owed_now != null ? `Tax owed ${fmtUSD(m.tax_owed_now, { decimals: 2 })} (40% on realized gains)` : "FIFO over filled lots"}
              />
            )}
            {m.after_tax_alpha_now_pp != null && (
              <HeroStat
                label="Alpha after tax"
                value={<span className={m.after_tax_alpha_now_pp >= 0 ? "pos" : "neg"}>{m.after_tax_alpha_now_pp >= 0 ? "+" : ""}{m.after_tax_alpha_now_pp.toFixed(2)}pp</span>}
                sub="vs same deposits in SPY, after tax"
              />
            )}
          </div>
          {cur.as_of && <div className="mono" style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 10 }}>updated {String(cur.as_of).slice(0, 16).replace("T", " ")}</div>}
        </div>
      </section>

      {m.sharpe_ratio != null || m.total_trades != null ? <MetricsStrip metrics={m} /> : null}

      <section style={{ paddingTop: 0, paddingBottom: 24 }}>
        <div className="container">
          <div className="card" style={{ padding: "20px 24px", maxWidth: 560 }}>
            <div className="eyebrow" style={{ marginBottom: 8 }}>Holdings <span style={{ color: "var(--accent)" }}>(real money)</span></div>
            {positions.length === 0 ? (
              <div style={{ color: "var(--ink-3)", fontSize: 13, fontFamily: "var(--mono)" }}>none yet</div>
            ) : (
              <div style={{ fontFamily: "var(--mono)", fontSize: 13 }}>
                {positions.map((pp) => {
                  const pnl = (pp.unrealized_plpc ?? pp.unrealized_pnl_pct ?? 0) * 100;
                  return (
                    <div key={pp.ticker} style={{ display: "flex", justifyContent: "space-between", padding: "6px 0", borderBottom: "1px solid var(--line)" }}>
                      <span>{pp.ticker}</span>
                      <span>{fmtUSD(pp.market_value ?? 0, { decimals: 2 })} <span className={pnl >= 0 ? "pos" : "neg"}>{pnl >= 0 ? "+" : ""}{pnl.toFixed(1)}%</span></span>
                    </div>
                  );
                })}
              </div>
            )}
            <div className="eyebrow" style={{ margin: "18px 0 6px" }}>Pending orders</div>
            {(!orders || orders.length === 0) ? (
              <div style={{ color: "var(--ink-3)", fontSize: 13, fontFamily: "var(--mono)" }}>none</div>
            ) : (
              <div style={{ fontFamily: "var(--mono)", fontSize: 13 }}>
                {orders.map((o, i) => (
                  <div key={i} style={{ display: "flex", justifyContent: "space-between", padding: "5px 0", color: "var(--ink-2)" }}>
                    <span>{(o.side || "").toUpperCase()} {o.ticker || o.symbol}</span>
                    <span>{o.notional ? fmtUSD(o.notional, { decimals: 0 }) : (o.qty ?? "")}</span>
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>
      </section>

      {perfPoints.length >= 2 && (
        <section style={{ paddingTop: 10, paddingBottom: 40 }}>
          <div className="container">
            <div style={{ marginBottom: 14 }}>
              <div className="eyebrow" style={{ marginBottom: 6 }}>Performance</div>
              <h2 className="h-section">NAV</h2>
            </div>
            <div className="card" style={{ padding: "28px 32px" }}>
              <PerformanceChart points={perfPoints} showBenchmark={false} fund="live" liveValue={nav} />
            </div>
          </div>
        </section>
      )}
    </div>
  );
}

function EtfView() {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [live, setLive] = useState(null);
  useEffect(() => {
    fetch(`data/etf_portfolio.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(setData)
      .catch((e) => setError(e.message));
  }, []);
  // Live Alpaca overlay (same mechanism as the LLM tab) — keeps NAV fresh
  // between nightly snapshots. Polls /api/live?fund=etf every 30s.
  useEffect(() => {
    let cancelled = false;
    const poll = async () => {
      try {
        const r = await fetch("/api/live?fund=etf", { cache: "no-store" });
        if (r.ok) { const b = await r.json(); if (!cancelled && b.nav) setLive(b); }
      } catch (_) { /* keep last snapshot */ }
    };
    poll();
    const id = setInterval(poll, 30000);
    return () => { cancelled = true; clearInterval(id); };
  }, []);

  if (error) return <div style={{ padding: 40, fontFamily: "var(--mono)" }}>Failed to load ETF data: {error}</div>;
  if (!data) return <div style={{ height: "60vh", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontFamily: "var(--mono)" }}>Loading ETF portfolio…</div>;

  const { current, history = [], strategy } = data;
  const sorted = [...history].sort((a, b) => a.date.localeCompare(b.date));
  const first = sorted[0];
  const last = sorted[sorted.length - 1] || current;
  const startNav = first ? first.nav : (current ? current.nav : 5000);
  const currNav = live?.nav ?? current.nav;
  const retSinceStart = (currNav - startNav) / startNav;

  // Drawdown over history
  let peak = -Infinity, maxDd = 0;
  for (const h of sorted) {
    peak = Math.max(peak, h.nav);
    const dd = (h.nav - peak) / peak;
    if (dd < maxDd) maxDd = dd;
  }

  const targetWeights = strategy?.weights_target || { QQQ: 0.5, SMH: 0.5 };
  const actualWeights = current.weights_actual || {};

  // PerformanceChart points: NAV value + SPY benchmark rebased to start NAV.
  const etfSpyInception = data.strategy_meta?.spy_inception_close ?? data.metrics?.spy_inception_close ?? null;
  const etfPerfPoints = sorted.map((h) => {
    let benchmark = null;
    if (etfSpyInception && h.spy_close) benchmark = startNav * (h.spy_close / etfSpyInception);
    return { date: h.date, value: h.nav, benchmark };
  });
  const etfHasPerSnapSpy = etfPerfPoints.some((p, i) => p.benchmark != null && i < etfPerfPoints.length - 1);
  if (!etfHasPerSnapSpy && data.metrics?.benchmark_spy_return_since_inception != null) {
    const spyRet = data.metrics.benchmark_spy_return_since_inception;
    etfPerfPoints.forEach((p, i) => {
      const frac = etfPerfPoints.length > 1 ? i / (etfPerfPoints.length - 1) : 1;
      p.benchmark = startNav * (1 + spyRet * frac);
    });
  }

  return (
    <div>
      <section style={{ paddingTop: 60, paddingBottom: 40 }}>
        <div className="container">
          <div style={{ marginBottom: 22 }}>
            <div className="eyebrow" style={{ marginBottom: 10 }}>Control group · sister fund</div>
            <h1 className="h-display" style={{ textWrap: "balance" }}>
              The <em style={{ fontStyle: "italic", color: "var(--accent)" }}>boring</em> portfolio.
            </h1>
            <p className="lead" style={{ marginTop: 16 }}>
              A separate $5,000 Alpaca paper account running <strong>50% SMH / 40% QQQ / 10% WTAI</strong> with a 5pp drift
              rebalance gate. The 10% WTAI sleeve (WisdomTree Artificial Intelligence &amp; Innovation Fund) broadens AI
              exposure beyond pure semis/Nasdaq into the wider AI software + application layer. Evolved from
              60/30/10 QQQM/SMH/XLK → 50/50 QQQ/SMH → this mix.
              No LLM, no signal feeds, no debates — pure allocation discipline.
              The LLM portfolio's alpha (or lack thereof) measured against this one is the real edge test.
            </p>
          </div>

          {/* Allocation time-slider donut */}
          <div style={{
            border: "1px solid var(--line)",
            borderRadius: 12,
            background: "var(--bg-card)",
            padding: "24px 28px",
            marginTop: 24,
          }}>
            {sorted.length >= 2 ? (
              <TimeSliderDonut series={buildEtfAllocationSeries(sorted)} />
            ) : (
              <AllocationDonut
                slices={buildEtfAllocationSlices(current)}
                total={current.nav}
                subTotal="hover for breakdown"
              />
            )}
          </div>

          <div style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
            border: "1px solid var(--line)",
            borderRadius: 12,
            overflow: "hidden",
            background: "var(--bg-card)",
            marginTop: 24,
          }}>
            <HeroStat label="Net asset value" value={fmtUSD(currNav, { decimals: 0 })} sub={`from $${startNav.toFixed(0)} seed`} />
            <HeroStat label="Return since inception" value={<span className={retSinceStart >= 0 ? "pos" : "neg"}>{retSinceStart >= 0 ? "+" : ""}{(retSinceStart * 100).toFixed(2)}%</span>} sub={data.metrics?.benchmark_spy_return_since_inception != null ? `S&P 500: ${(data.metrics.benchmark_spy_return_since_inception * 100).toFixed(2)}%` : `over ${sorted.length} snapshots`} />
            {data.metrics?.alpha_vs_spy_pp != null && (
              <HeroStat
                label="Alpha vs S&P 500"
                value={<span className={data.metrics.alpha_vs_spy_pp >= 0 ? "pos" : "neg"}>{data.metrics.alpha_vs_spy_pp >= 0 ? "+" : ""}{data.metrics.alpha_vs_spy_pp.toFixed(2)}pp</span>}
                sub={`vs 100% SPY ${fmtUSD(data.metrics.counterfactual_spy_nav || 0, { decimals: 0 })}`}
              />
            )}
            <HeroStat label="Max drawdown" value={<span className={maxDd >= 0 ? "pos" : "neg"}>{(maxDd * 100).toFixed(2)}%</span>} sub="peak-to-trough" />
            <HeroStat label="Drift now" value={`${(current.drift_max_pp || 0).toFixed(2)}pp`} sub={`threshold ${strategy?.drift_threshold_pp || 5}pp`} />
          </div>
        </div>
      </section>

      {/* Performance chart — NAV vs SPY, same component as the LLM fund */}
      {etfPerfPoints.length >= 2 && (
        <section style={{ paddingTop: 10, paddingBottom: 20 }}>
          <div className="container">
            <div style={{ marginBottom: 14 }}>
              <div className="eyebrow" style={{ marginBottom: 6 }}>Performance</div>
              <h2 className="h-section">NAV vs S&amp;P 500</h2>
            </div>
            <div className="card" style={{ padding: "28px 32px" }}>
              <PerformanceChart points={etfPerfPoints} showBenchmark={true} fund="etf" liveValue={live?.nav ?? null} />
            </div>
          </div>
        </section>
      )}

      <section style={{ paddingTop: 20, paddingBottom: 40 }}>
        <div className="container">
          <div style={{ marginBottom: 18 }}>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Allocation</div>
            <h2 className="h-section">Target vs actual</h2>
          </div>
          <div className="card" style={{ padding: "20px 28px" }}>
            <table style={{ width: "100%", borderCollapse: "collapse", fontVariantNumeric: "tabular-nums" }}>
              <thead>
                <tr style={{ borderBottom: "1px solid var(--line)" }}>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Ticker</th>
                  <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Target</th>
                  <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Actual</th>
                  <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Drift</th>
                </tr>
              </thead>
              <tbody>
                {Object.keys(targetWeights).map((t) => {
                  const tgt = targetWeights[t] * 100;
                  const act = (actualWeights[t] || 0) * 100;
                  const drift = act - tgt;
                  return (
                    <tr key={t} style={{ borderBottom: "1px solid var(--line)" }}>
                      <td style={{ padding: "12px 0", fontWeight: 500 }}>{t}</td>
                      <td style={{ padding: "12px 0", textAlign: "right" }}>{tgt.toFixed(0)}%</td>
                      <td style={{ padding: "12px 0", textAlign: "right" }}>{act.toFixed(2)}%</td>
                      <td style={{ padding: "12px 0", textAlign: "right" }} className={Math.abs(drift) > 5 ? "neg" : ""}>{drift >= 0 ? "+" : ""}{drift.toFixed(2)}pp</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            <p className="muted" style={{ marginTop: 14, fontSize: 12.5 }}>
              {strategy?.rebalance_rule || "Rebalance when any leg drifts >5pp"} · daemon fires every 30 min · trades only during market hours when drift exceeds threshold
            </p>
          </div>
        </div>
      </section>

      <section style={{ paddingTop: 20, paddingBottom: 40 }}>
        <div className="container">
          <div style={{ marginBottom: 18 }}>
            <div className="eyebrow" style={{ marginBottom: 6 }}>NAV history</div>
            <h2 className="h-section">Daily snapshots</h2>
          </div>
          <div className="card" style={{ padding: "8px 28px 16px" }}>
            <table style={{ width: "100%", borderCollapse: "collapse", fontVariantNumeric: "tabular-nums" }}>
              <thead>
                <tr style={{ borderBottom: "1px solid var(--line)" }}>
                  <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Date</th>
                  <th style={{ textAlign: "right", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>NAV</th>
                  <th style={{ textAlign: "right", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Δ from start</th>
                  <th style={{ textAlign: "right", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>QQQ close</th>
                  <th style={{ textAlign: "right", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Drift</th>
                </tr>
              </thead>
              <tbody>
                {[...sorted].reverse().map((h) => {
                  const ret = (h.nav - startNav) / startNav;
                  return (
                    <tr key={h.date} style={{ borderBottom: "1px solid var(--line)" }}>
                      <td style={{ padding: "10px 0", fontFamily: "var(--mono)", fontSize: 13 }}>{h.date}</td>
                      <td style={{ padding: "10px 0", textAlign: "right" }}>{fmtUSD(h.nav, { decimals: 2 })}</td>
                      <td style={{ padding: "10px 0", textAlign: "right" }} className={ret >= 0 ? "pos" : "neg"}>{ret >= 0 ? "+" : ""}{(ret * 100).toFixed(2)}%</td>
                      <td style={{ padding: "10px 0", textAlign: "right", color: "var(--ink-3)" }}>{h.benchmark_qqq_price ? `$${h.benchmark_qqq_price.toFixed(2)}` : "—"}</td>
                      <td style={{ padding: "10px 0", textAlign: "right", color: "var(--ink-3)" }}>{(h.drift_max_pp || 0).toFixed(2)}pp</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            {sorted.length === 0 && (
              <p className="muted" style={{ padding: 20, textAlign: "center" }}>No history yet — daemon writes a snapshot every 30 min during market hours.</p>
            )}
          </div>
        </div>
      </section>

      <section style={{ paddingTop: 20, paddingBottom: 80 }}>
        <div className="container">
          <div className="card" style={{ padding: "24px 32px", background: "var(--bg-card)" }}>
            <div className="eyebrow" style={{ marginBottom: 10 }}>Why this exists</div>
            <p style={{ fontSize: 15, lineHeight: 1.6 }}>
              The LLM-driven portfolio (other tab) makes daily allocation decisions using a multi-agent debate
              over its signal layer. This sister fund makes <strong>zero discretionary decisions</strong> — it
              buys QQQM/SMH/XLK at the listed weights and only rebalances when drift exceeds 5pp.
              If the LLM fund's NAV doesn't materially beat this one over time, the debate framework isn't adding alpha.
              That's a real question this site answers in public, day by day.
            </p>
          </div>
        </div>
      </section>
    </div>
  );
}


// ---------- CPO Thematic view ----------
// Third paper-trading book. TradingAgents-debate-gated thematic basket of
// Co-Packaged Optics / AI-infra names with Aschenbrenner power-layer overlay.

function CpoView() {
  const [data, setData] = useState(null);
  const [serenity, setSerenity] = useState(null);
  const [pulse, setPulse] = useState(null);
  const [error, setError] = useState(null);
  const [expanded, setExpanded] = useState(null);
  const [live, setLive] = useState(null);
  useEffect(() => {
    fetch(`data/cpo_portfolio.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(setData)
      .catch((e) => setError(e.message));
    fetch(`data/serenity_feed.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : null))
      .then(setSerenity)
      .catch(() => setSerenity(null));
    fetch(`data/serenity_pulse.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => (r.ok ? r.json() : null))
      .then(setPulse)
      .catch(() => setPulse(null));
  }, []);
  // Live Alpaca overlay — keeps Serenity NAV fresh between cpo_run fires.
  useEffect(() => {
    let cancelled = false;
    const poll = async () => {
      try {
        const r = await fetch("/api/live?fund=cpo", { cache: "no-store" });
        if (r.ok) { const b = await r.json(); if (!cancelled && b.nav) setLive(b); }
      } catch (_) { /* keep last snapshot */ }
    };
    poll();
    const id = setInterval(poll, 30000);
    return () => { cancelled = true; clearInterval(id); };
  }, []);

  if (error) return <div style={{ padding: 40, fontFamily: "var(--mono)" }}>Failed to load Serenity Portfolio data: {error}</div>;
  if (!data) return <div style={{ height: "60vh", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontFamily: "var(--mono)" }}>Loading Serenity Portfolio…</div>;

  const { current = {}, history = [], strategy = {}, positions = [], debate_history = {}, metrics = {} } = data;
  const sorted = [...history].sort((a, b) => a.date.localeCompare(b.date));
  const startNav = sorted[0]?.nav ?? current.nav ?? 100000;
  const currNav = live?.nav ?? current.nav ?? 100000;
  const retSinceStart = startNav > 0 ? (currNav - startNav) / startNav : 0;

  let peak = -Infinity, maxDd = 0;
  for (const h of sorted) {
    peak = Math.max(peak, h.nav);
    const dd = (h.nav - peak) / peak;
    if (dd < maxDd) maxDd = dd;
  }

  // Build PerformanceChart points: {date, value (NAV), benchmark (SPY rebased
  // to the fund's starting NAV)}. spy_inception_close anchors the rebase so
  // the SPY line starts at the same dollar value as the fund.
  const spyInception = data.strategy_meta?.spy_inception_close
    ?? metrics?.spy_inception_close ?? null;
  const cpoPerfPoints = sorted.map((h) => {
    let benchmark = null;
    if (spyInception && h.spy_close) {
      benchmark = startNav * (h.spy_close / spyInception);
    } else if (spyInception && metrics?.counterfactual_spy_nav && h === sorted[sorted.length - 1]) {
      benchmark = metrics.counterfactual_spy_nav;
    }
    return { date: h.date, value: h.nav, benchmark };
  });
  // If we don't have per-snapshot SPY closes, synthesize a 2-point SPY line
  // (inception → now) so the benchmark still renders as a straight reference.
  const hasPerSnapSpy = cpoPerfPoints.some((p) => p.benchmark != null && p !== cpoPerfPoints[cpoPerfPoints.length - 1]);
  if (!hasPerSnapSpy && metrics?.benchmark_spy_return_since_inception != null) {
    const spyRet = metrics.benchmark_spy_return_since_inception;
    cpoPerfPoints.forEach((p, i) => {
      const frac = cpoPerfPoints.length > 1 ? i / (cpoPerfPoints.length - 1) : 1;
      p.benchmark = startNav * (1 + spyRet * frac);
    });
  }

  return (
    <div>
      <section style={{ paddingTop: 60, paddingBottom: 40 }}>
        <div className="container">
          <div style={{ marginBottom: 22 }}>
            <div className="eyebrow" style={{ marginBottom: 10 }}>Signal-driven · conviction-weighted · debate-gated</div>
            <h1 className="h-display" style={{ textWrap: "balance" }}>
              The <em style={{ fontStyle: "italic", color: "var(--accent)" }}>Serenity</em> book.
            </h1>
            <p className="lead" style={{ marginTop: 16 }}>
              A separate Alpaca paper account that fronts-runs the order flow of <strong>@aleabitoreddit</strong> (Serenity) —
              the photonics / AI-supply-chain analyst whose calls (NBIS, AXTI, AAOI, SIVE) have run 3–15× off the bottom.
              The pipeline scrapes her live timeline, parses every <span style={{ fontFamily: "var(--mono)" }}>$TICKER</span>,
              and scores conviction from mention frequency, recency, and tone.
            </p>
            <p className="lead" style={{ marginTop: 12 }}>
              Each name is then run through a <strong>Serenity-tuned TradingAgents debate</strong> — a multi-agent bull/bear
              book that ingests her conviction context alongside price action, AV news sentiment, and StockTwits retail flow.
              Position sizing is a function of <em>both</em> her conviction and the debate verdict:
              <span style={{ fontFamily: "var(--mono)", fontSize: 13.5 }}> OVERWEIGHT/BUY</span> → full size,
              <span style={{ fontFamily: "var(--mono)", fontSize: 13.5 }}> high-conviction starter</span> → 5% NAV tranche,
              <span style={{ fontFamily: "var(--mono)", fontSize: 13.5 }}> thesis-tail</span> → tiny 0.5% lotto on names the
              tape hasn't confirmed yet. We <em>risk-filter</em> her, not replicate her: chase the structurally durable,
              fade the blow-up-prone.
            </p>
            <p className="lead" style={{ marginTop: 12, color: "var(--ink-3)", fontSize: 14.5 }}>
              Exits are symmetric — hard −12% stop, 50% trim into binary earnings prints, 25% trim when she turns bearish on
              a held name, and a daily re-debate sweep that cuts positions the agents flip to SELL. Non-US names she pushes
              (SIVE, LPK, SOI, IQE) get debated for the analyst read but aren't tradeable on Alpaca. Fully autonomous:
              scrape → debate → size → fill, fired on a schedule and on every laptop wake.
            </p>
          </div>

          {/* Allocation donut with time slider */}
          <div style={{
            border: "1px solid var(--line)",
            borderRadius: 12,
            background: "var(--bg-card)",
            padding: "24px 28px",
            marginTop: 24,
          }}>
            {sorted.length >= 2 ? (
              <TimeSliderDonut series={withLivePoint(buildCpoAllocationSeries(sorted, positions), live)} />
            ) : (
              <AllocationDonut
                slices={buildCpoCurrentSlices(current, positions)}
                total={currNav}
                subTotal="hover for breakdown"
              />
            )}
          </div>

          <div style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
            border: "1px solid var(--line)",
            borderRadius: 12,
            overflow: "hidden",
            background: "var(--bg-card)",
            marginTop: 24,
          }}>
            <HeroStat label="Net asset value" value={fmtUSD(currNav, { decimals: 0 })} sub={`from $${startNav.toFixed(0)} seed`} />
            <HeroStat
              label="Return since inception"
              value={<span className={retSinceStart >= 0 ? "pos" : "neg"}>{retSinceStart >= 0 ? "+" : ""}{(retSinceStart * 100).toFixed(2)}%</span>}
              sub={metrics?.benchmark_spy_return_since_inception != null ? `S&P 500: ${(metrics.benchmark_spy_return_since_inception * 100).toFixed(2)}%` : `over ${sorted.length} snapshots`}
            />
            {metrics?.alpha_vs_spy_pp != null && (
              <HeroStat
                label="Alpha vs S&P 500"
                value={<span className={metrics.alpha_vs_spy_pp >= 0 ? "pos" : "neg"}>{metrics.alpha_vs_spy_pp >= 0 ? "+" : ""}{metrics.alpha_vs_spy_pp.toFixed(2)}pp</span>}
                sub={`vs 100% SPY ${fmtUSD(metrics.counterfactual_spy_nav || 0, { decimals: 0 })}`}
              />
            )}
            <HeroStat label="Max drawdown" value={<span className={maxDd >= 0 ? "pos" : "neg"}>{(maxDd * 100).toFixed(2)}%</span>} sub="peak-to-trough" />
            <HeroStat label="Deployed" value={`${((current.deployed_pct || 0) * 100).toFixed(1)}%`} sub={`${positions.length} of ${(strategy?.universe || []).length} confirmed`} />
          </div>
        </div>
      </section>

      {/* Performance chart — NAV vs SPY, same component as the LLM fund */}
      {cpoPerfPoints.length >= 2 && (
        <section style={{ paddingTop: 10, paddingBottom: 20 }}>
          <div className="container">
            <div style={{ marginBottom: 14 }}>
              <div className="eyebrow" style={{ marginBottom: 6 }}>Performance</div>
              <h2 className="h-section">NAV vs S&amp;P 500</h2>
            </div>
            <div className="card" style={{ padding: "28px 32px" }}>
              <PerformanceChart points={cpoPerfPoints} showBenchmark={true} fund="cpo" liveValue={live?.nav ?? null} />
            </div>
          </div>
        </section>
      )}

      {/* Positions table */}
      <section style={{ paddingTop: 20, paddingBottom: 40 }}>
        <div className="container">
          <div style={{ marginBottom: 18 }}>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Holdings</div>
            <h2 className="h-section">Confirmed positions</h2>
          </div>
          <div className="card" style={{ padding: "20px 28px" }}>
            {positions.length === 0 ? (
              <p className="muted" style={{ padding: 20, textAlign: "center" }}>
                No positions yet — initial TradingAgents debate may still be running or hasn't been triggered yet.
              </p>
            ) : (
              <table style={{ width: "100%", borderCollapse: "collapse", fontVariantNumeric: "tabular-nums" }}>
                <thead>
                  <tr style={{ borderBottom: "1px solid var(--line)" }}>
                    <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Ticker</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Qty</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Avg cost</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Last</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>MV</th>
                    <th style={{ textAlign: "right", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>PnL</th>
                  </tr>
                </thead>
                <tbody>
                  {positions
                    .slice()
                    .sort((a, b) => (b.market_value || 0) - (a.market_value || 0))
                    .map((p) => (
                      <tr key={p.ticker} style={{ borderBottom: "1px solid var(--line)" }}>
                        <td style={{ padding: "12px 0", fontWeight: 500 }}>{p.ticker}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }}>{(p.qty || 0).toFixed(4)}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }}>${(p.avg_cost || 0).toFixed(2)}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }}>${(p.last_price || 0).toFixed(2)}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }}>{fmtUSD(p.market_value || 0, { decimals: 0 })}</td>
                        <td style={{ padding: "12px 0", textAlign: "right" }} className={(p.unrealized_pnl_pct || 0) >= 0 ? "pos" : "neg"}>
                          {(p.unrealized_pnl_pct || 0) >= 0 ? "+" : ""}{((p.unrealized_pnl_pct || 0) * 100).toFixed(2)}%
                        </td>
                      </tr>
                    ))}
                </tbody>
              </table>
            )}
          </div>
        </div>
      </section>

      {/* TradingAgents debate transcripts */}
      <section style={{ paddingTop: 20, paddingBottom: 60 }}>
        <div className="container">
          <div style={{ marginBottom: 18 }}>
            <div className="eyebrow" style={{ marginBottom: 6 }}>Debate transcripts</div>
            <h2 className="h-section">TradingAgents per-name decisions</h2>
          </div>
          <div className="card" style={{ padding: "8px 24px 16px" }}>
            {Object.keys(debate_history).length === 0 ? (
              <p className="muted" style={{ padding: 20, textAlign: "center" }}>
                Debate hasn't run yet. Will populate after first daemon fire.
              </p>
            ) : (
              <table style={{ width: "100%", borderCollapse: "collapse", fontVariantNumeric: "tabular-nums" }}>
                <thead>
                  <tr style={{ borderBottom: "1px solid var(--line)" }}>
                    <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Ticker</th>
                    <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Signal</th>
                    <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Date</th>
                    <th style={{ textAlign: "left", padding: "10px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Rationale (click to expand)</th>
                  </tr>
                </thead>
                <tbody>
                  {Object.entries(debate_history)
                    .sort((a, b) => (a[1].date || "").localeCompare(b[1].date || ""))
                    .reverse()
                    .map(([ticker, rec]) => {
                      const isOpen = expanded === ticker;
                      const sig = (rec.signal || "—").toUpperCase();
                      const isBuy = sig === "BUY" || sig === "OVERWEIGHT";
                      const isSell = sig === "SELL" || sig === "UNDERWEIGHT";
                      return (
                        <tr key={ticker} onClick={() => setExpanded(isOpen ? null : ticker)}
                            style={{ borderBottom: "1px solid var(--line)", cursor: "pointer", verticalAlign: "top" }}>
                          <td style={{ padding: "10px 0", fontWeight: 500 }}>{ticker}</td>
                          <td style={{ padding: "10px 0" }} className={isBuy ? "pos" : isSell ? "neg" : ""}>{sig}</td>
                          <td style={{ padding: "10px 0", color: "var(--ink-3)", fontFamily: "var(--mono)", fontSize: 12 }}>{(rec.date || "").slice(0, 10)}</td>
                          <td style={{ padding: "10px 0", fontSize: 13, color: "var(--ink-2)", maxWidth: 540, whiteSpace: isOpen ? "pre-wrap" : "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                            {rec.error ? <em style={{ color: "var(--neg, #c0392b)" }}>error: {rec.error}</em> : (rec.decision_excerpt || "—")}
                          </td>
                        </tr>
                      );
                    })}
                </tbody>
              </table>
            )}
          </div>
          <p className="muted" style={{ fontSize: 12.5, marginTop: 14 }}>
            Click any row to expand the full debate excerpt. Re-debate fires every Monday on all 11 candidates.
          </p>
        </div>
      </section>

      {/* Serenity Pulse — top mentions + 30d heatmap + tweet stream */}
      <SerenityPulse pulse={pulse} />

      {/* Serenity feed (today's actions table) */}
      <SerenityFeed serenity={serenity} />

      <section style={{ paddingTop: 0, paddingBottom: 80 }}>
        <div className="container">
          <div className="card" style={{ padding: "24px 32px", background: "var(--bg-card)" }}>
            <div className="eyebrow" style={{ marginBottom: 10 }}>Aschenbrenner overlay</div>
            <p style={{ fontSize: 15, lineHeight: 1.6 }}>
              The universe extends beyond pure-CPO names to include Leopold Aschenbrenner's Situational Awareness LP top-5
              holdings — <strong>BE</strong> (Bloom Energy, his #2), <strong>CORZ</strong> (Core Scientific),
              <strong> CRWV</strong> (CoreWeave) — under the thesis that AI scale is power-constrained, not chip-constrained.
              <strong> COHR</strong> and <strong>LITE</strong> are also in his fund. The TradingAgents debate ultimately decides
              which names earn an allocation; the universe is wider than pure CPO to let bull/bear arguments span the broader
              AI-infrastructure thesis.
            </p>
          </div>
        </div>
      </section>
    </div>
  );
}

// ---------- Serenity Pulse ----------
// 3-layer view of Serenity's tweet activity:
//   1. Top mentions last 14d (bars + our latest debate verdict)
//   2. 30-day heatmap (rows=tickers, cols=days, cell color=mention count,
//      overlay badge when we debated that ticker that day)
//   3. Tweet stream with inline ticker chips colored by debate verdict
//
// Click a ticker chip anywhere → filters all three rows to that ticker.
// Data source: web/data/serenity_pulse.json (built by scripts/build_serenity_pulse.py).
function SerenityPulse({ pulse }) {
  const [selected, setSelected] = useState(null); // ticker filter (null = all)
  if (!pulse) return null;
  const { top_tickers = [], heatmap = [], stream = [], days = [], stats = {} } = pulse;
  const filtered_stream = selected
    ? stream.filter((t) => t.tickers.some((tk) => tk.ticker === selected))
    : stream;

  // Color helpers
  const signalColor = (sig) => {
    const s = (sig || "").toUpperCase();
    if (s === "BUY" || s === "OVERWEIGHT") return "var(--pos, #2da465)";
    if (s === "SELL" || s === "UNDERWEIGHT") return "var(--neg, #d04848)";
    if (s === "HOLD") return "var(--ink-3, #888)";
    return "var(--line, #444)";
  };
  const signalAbbrev = (sig) => {
    const s = (sig || "").toUpperCase();
    if (s === "OVERWEIGHT") return "OW";
    if (s === "UNDERWEIGHT") return "UW";
    return s || "";
  };
  // Heatmap cell intensity: 0 → background, 1+ → progressively bolder
  const cellColor = (count) => {
    if (!count) return "rgba(120,140,170,0.06)";
    if (count === 1) return "rgba(80,140,255,0.22)";
    if (count === 2) return "rgba(80,140,255,0.42)";
    if (count === 3) return "rgba(80,140,255,0.62)";
    if (count === 4) return "rgba(80,140,255,0.82)";
    return "rgba(80,140,255,1)";
  };

  // Pill rendering for ticker chip
  const Chip = ({ ticker, signal, action, non_us, onClick, dim }) => {
    const isSelected = selected === ticker;
    const baseColor = non_us ? "var(--ink-3)" : signalColor(signal);
    return (
      <span
        onClick={onClick}
        title={
          non_us
            ? `${ticker} — non-US (not Alpaca-tradeable)`
            : signal
            ? `${ticker} — last debate: ${signal}${action ? ` (${action})` : ""}`
            : `${ticker} — no debate yet`
        }
        style={{
          display: "inline-flex",
          alignItems: "center",
          gap: 4,
          padding: "2px 8px",
          borderRadius: 999,
          fontSize: 11.5,
          fontFamily: "var(--mono)",
          fontWeight: 500,
          border: `1px solid ${baseColor}`,
          color: dim ? "var(--ink-3)" : baseColor,
          background: isSelected ? `${baseColor}22` : "transparent",
          cursor: onClick ? "pointer" : "default",
          opacity: non_us ? 0.55 : 1,
          marginRight: 5,
          marginBottom: 4,
          transition: "background 120ms",
        }}
      >
        ${ticker}
        {signal && !non_us && (
          <span style={{ fontSize: 9.5, opacity: 0.85 }}>{signalAbbrev(signal)}</span>
        )}
      </span>
    );
  };

  return (
    <section style={{ paddingTop: 0, paddingBottom: 50 }}>
      <div className="container">
        <div style={{ marginBottom: 18 }}>
          <div className="eyebrow" style={{ marginBottom: 6 }}>Serenity Pulse</div>
          <h2 className="h-section">What @aleabitoreddit is pushing</h2>
          <p className="lead" style={{ marginTop: 8 }}>
            Top mentions (14d window), a {pulse.window_days_heatmap}-day mention heatmap, and the raw tweet
            stream — each ticker chip colored by our most recent TradingAgents debate verdict.
            Click any ticker to filter the stream.
            <span style={{ color: "var(--ink-3)", marginLeft: 12, fontFamily: "var(--mono)", fontSize: 12 }}>
              {stats.tweets_total} tweets · {stats.debates_total} debates · {stats.tickers_tracked} tickers tracked
            </span>
          </p>
          {selected && (
            <div style={{ marginTop: 12 }}>
              <span style={{ fontSize: 13, marginRight: 10 }}>Filtering by:</span>
              <Chip ticker={selected} signal={top_tickers.find((t) => t.ticker === selected)?.latest_signal}
                    non_us={top_tickers.find((t) => t.ticker === selected)?.non_us}
                    onClick={() => setSelected(null)} />
              <button onClick={() => setSelected(null)}
                style={{ background: "transparent", border: "none", color: "var(--ink-3)", cursor: "pointer", fontSize: 12, textDecoration: "underline" }}>
                clear filter
              </button>
            </div>
          )}
        </div>

        {/* === Row 1: Top mentions bar list === */}
        {top_tickers.length > 0 && (
          <div className="card" style={{ padding: "20px 28px", marginBottom: 18 }}>
            <h3 style={{ marginTop: 0, marginBottom: 14, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
              Most mentioned (last {pulse.window_days_top_mentions} days)
            </h3>
            <div>
              {top_tickers.map((t) => {
                const maxCount = Math.max(...top_tickers.map((x) => x.mentions_14d));
                const pct = (t.mentions_14d / maxCount) * 100;
                const color = t.non_us ? "var(--ink-3)" : signalColor(t.latest_signal);
                return (
                  <div key={t.ticker}
                       onClick={() => setSelected(selected === t.ticker ? null : t.ticker)}
                       style={{
                         display: "grid",
                         gridTemplateColumns: "70px 1fr 90px 100px",
                         alignItems: "center",
                         gap: 12,
                         padding: "6px 0",
                         cursor: "pointer",
                         opacity: t.non_us ? 0.7 : 1,
                         background: selected === t.ticker ? "rgba(80,140,255,0.06)" : "transparent",
                         borderRadius: 4,
                       }}>
                    <div style={{ fontFamily: "var(--mono)", fontWeight: 600, fontSize: 13 }}>
                      ${t.ticker}
                    </div>
                    <div style={{ position: "relative", height: 18, background: "rgba(120,140,170,0.08)", borderRadius: 3 }}>
                      <div style={{
                        position: "absolute", left: 0, top: 0, bottom: 0, width: `${pct}%`,
                        background: color, opacity: 0.55, borderRadius: 3,
                      }} />
                    </div>
                    <div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--ink-3)" }}>
                      {t.mentions_14d}× mentions
                    </div>
                    <div style={{ fontFamily: "var(--mono)", fontSize: 11, color, fontWeight: 500 }}>
                      {t.non_us ? "non-US" : t.latest_signal ? `→ ${t.latest_signal}` : "—"}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        )}

        {/* === Row 2: 30-day heatmap === */}
        {heatmap.length > 0 && (
          <div className="card" style={{ padding: "20px 28px", marginBottom: 18, overflowX: "auto" }}>
            <h3 style={{ marginTop: 0, marginBottom: 14, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
              {pulse.window_days_heatmap}-day activity heatmap
            </h3>
            <table style={{ borderCollapse: "collapse", fontFamily: "var(--mono)", fontSize: 10 }}>
              <thead>
                <tr>
                  <th style={{ position: "sticky", left: 0, background: "var(--bg-card, #1a1a1f)", padding: "0 8px 8px 0", textAlign: "left", color: "var(--ink-3)", fontWeight: 400, minWidth: 60 }}>Ticker</th>
                  {days.map((d) => {
                    // Show every 5th day label
                    const dayNum = parseInt(d.slice(-2), 10);
                    const showLabel = dayNum % 5 === 0 || d === days[days.length - 1];
                    return (
                      <th key={d} style={{ padding: "0 1px 8px", color: "var(--ink-3)", fontWeight: 400, fontSize: 9, minWidth: 16, textAlign: "center" }}>
                        {showLabel ? d.slice(5) : ""}
                      </th>
                    );
                  })}
                </tr>
              </thead>
              <tbody>
                {heatmap.map((row) => {
                  const isSelected = selected === row.ticker;
                  // Highlight ticker label cell green if the latest debate
                  // verdict is BUY/OVERWEIGHT. Non-buys stay neutral so the
                  // eye is drawn to actionable names.
                  const isBuy = !row.non_us && row.latest_signal &&
                                ["BUY", "OVERWEIGHT"].includes(row.latest_signal.toUpperCase());
                  const labelBg = isBuy
                    ? "rgba(45,164,101,0.22)"
                    : "var(--bg-card, #1a1a1f)";
                  return (
                    <tr key={row.ticker} style={{ opacity: selected && selected !== row.ticker ? 0.35 : 1, transition: "opacity 120ms" }}>
                      <td
                        onClick={() => setSelected(isSelected ? null : row.ticker)}
                        style={{
                          position: "sticky", left: 0, background: labelBg,
                          padding: "4px 8px 4px 8px", fontWeight: 600, fontSize: 11,
                          cursor: "pointer",
                          color: row.non_us ? "var(--ink-3)" : (isBuy ? "var(--pos, #2da465)" : "inherit"),
                          textDecoration: isSelected ? "underline" : "none",
                          borderLeft: isBuy ? "3px solid var(--pos, #2da465)" : "3px solid transparent",
                        }}>
                        ${row.ticker}
                      </td>
                      {row.cells.map((cell) => (
                        <td key={cell.day}
                            title={cell.count > 0
                              ? `${row.ticker} on ${cell.day}: ${cell.count} mention${cell.count > 1 ? "s" : ""}${cell.debate_signal ? ` · we debated → ${cell.debate_signal}` : ""}`
                              : ""}
                            style={{
                              padding: 0, minWidth: 16, height: 16,
                              background: cellColor(cell.count),
                              border: "1px solid rgba(0,0,0,0.15)",
                              position: "relative",
                            }}>
                          {cell.debate_signal && (
                            <span style={{
                              position: "absolute", inset: 0, display: "flex",
                              alignItems: "center", justifyContent: "center",
                              color: signalColor(cell.debate_signal), fontSize: 10, fontWeight: 700,
                            }}>•</span>
                          )}
                        </td>
                      ))}
                    </tr>
                  );
                })}
              </tbody>
            </table>
            <p style={{ fontSize: 11, color: "var(--ink-3)", marginTop: 12, fontFamily: "var(--mono)" }}>
              Cell intensity = mention count that day. Dot inside cell = we ran a TradingAgents debate
              on that day (color = signal: <span style={{ color: signalColor("BUY") }}>● BUY/OW</span> ·
              <span style={{ color: signalColor("HOLD") }}> ● HOLD</span> ·
              <span style={{ color: signalColor("SELL") }}> ● SELL/UW</span>).
            </p>
          </div>
        )}

        {/* === Row 3: Tweet stream === */}
        {filtered_stream.length > 0 && (
          <div className="card" style={{ padding: "20px 28px" }}>
            <h3 style={{ marginTop: 0, marginBottom: 14, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
              {selected ? `Tweets mentioning $${selected}` : "Recent tweet stream"}
              <span style={{ marginLeft: 10, color: "var(--ink-3)", fontWeight: 400 }}>
                ({filtered_stream.length} tweet{filtered_stream.length !== 1 ? "s" : ""})
              </span>
            </h3>
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              {filtered_stream.map((t) => {
                const dt = new Date(t.posted_at);
                const hoursAgo = (Date.now() - dt.getTime()) / 3.6e6;
                const ageLabel = hoursAgo < 1
                  ? `${Math.round(hoursAgo * 60)}m ago`
                  : hoursAgo < 24
                  ? `${Math.round(hoursAgo)}h ago`
                  : `${Math.round(hoursAgo / 24)}d ago`;
                return (
                  <div key={t.id} style={{
                    padding: "12px 16px",
                    borderLeft: "2px solid var(--line, #333)",
                    background: "rgba(120,140,170,0.04)",
                    borderRadius: "0 4px 4px 0",
                  }}>
                    <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6, fontSize: 11.5, color: "var(--ink-3)", fontFamily: "var(--mono)" }}>
                      <span style={{ fontWeight: 500 }}>@{t.handle}</span>
                      <span>·</span>
                      <span>{ageLabel}</span>
                      <span style={{ marginLeft: "auto", fontSize: 10 }}>{dt.toISOString().slice(0, 16).replace("T", " ")}</span>
                    </div>
                    <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.55, whiteSpace: "pre-wrap" }}>
                      {t.text}
                    </p>
                    {t.tickers.length > 0 && (
                      <div style={{ marginTop: 8 }}>
                        {t.tickers.map((tk, i) => (
                          <Chip key={`${tk.ticker}-${i}`}
                                ticker={tk.ticker}
                                signal={tk.signal}
                                action={tk.action}
                                non_us={tk.non_us}
                                onClick={() => setSelected(selected === tk.ticker ? null : tk.ticker)} />
                        ))}
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        )}
      </div>
    </section>
  );
}

// ---------- Serenity / external-signal feed ----------
// Renders the daily Serenity (@aleabitoreddit) + endorsed-handle scrape:
// recent tweets with ticker tags, debate outcomes per ticker, and the
// system's action (buy / skip / deferred).
function SerenityFeed({ serenity }) {
  if (!serenity) return null;
  const run = serenity.latest_run || {};
  const tweets = serenity.recent_tweets || [];
  const actions = run.actions || [];

  return (
    <section style={{ paddingTop: 0, paddingBottom: 40 }}>
      <div className="container">
        <div style={{ marginBottom: 18 }}>
          <div className="eyebrow" style={{ marginBottom: 6 }}>External signal feed</div>
          <h2 className="h-section">Serenity + endorsed analysts</h2>
          <p className="lead" style={{ marginTop: 8 }}>
            Daily scrape of @aleabitoreddit (Serenity — the original WSB / AI-Semi supply chain analyst) plus accounts
            she's endorsed. Tickers mentioned across the feed get filtered to US-tradeable names, deduped against current
            holdings, and the top {2} new candidates run through a TradingAgents debate.
            Only BUY/OVERWEIGHT confirmed names get bought at 10% NAV. The rest is research, not signal.
          </p>
        </div>

        {/* Actions this run */}
        {actions.length > 0 && (
          <div className="card" style={{ padding: "20px 28px", marginBottom: 18 }}>
            <h3 style={{ marginTop: 0, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
              Today's debate outcomes
            </h3>
            <table style={{ width: "100%", borderCollapse: "collapse", fontVariantNumeric: "tabular-nums" }}>
              <thead>
                <tr style={{ borderBottom: "1px solid var(--line)" }}>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Ticker</th>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Mentions</th>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>TA signal</th>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Action</th>
                  <th style={{ textAlign: "left", padding: "8px 0", fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}>Notional</th>
                </tr>
              </thead>
              <tbody>
                {actions.map((a) => {
                  const sig = (a.signal || "").toUpperCase();
                  const isBuy = sig === "BUY" || sig === "OVERWEIGHT";
                  const isSell = sig === "SELL" || sig === "UNDERWEIGHT";
                  return (
                    <tr key={a.ticker} style={{ borderBottom: "1px solid var(--line)" }}>
                      <td style={{ padding: "10px 0", fontWeight: 500 }}>{a.ticker}</td>
                      <td style={{ padding: "10px 0", color: "var(--ink-3)" }}>{a.mention_count}×</td>
                      <td style={{ padding: "10px 0" }} className={isBuy ? "pos" : isSell ? "neg" : ""}>{sig || "—"}</td>
                      <td style={{ padding: "10px 0" }}>{a.action || "—"}</td>
                      <td style={{ padding: "10px 0", color: "var(--ink-3)" }}>{a.notional ? `$${a.notional.toFixed(0)}` : "—"}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            {run.candidates_deferred?.length > 0 && (
              <p className="muted" style={{ marginTop: 12, fontSize: 12.5 }}>
                Deferred to tomorrow (over daily cap): <strong>{run.candidates_deferred.join(", ")}</strong>
              </p>
            )}
          </div>
        )}

        {/* Recent tweets */}
        <div className="card" style={{ padding: "20px 28px" }}>
          <h3 style={{ marginTop: 0, fontSize: 14, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
            Recent tweets ({tweets.length})
          </h3>
          {tweets.length === 0 ? (
            <p className="muted" style={{ padding: 16, textAlign: "center" }}>
              No tweets pulled yet — first daemon run hasn't completed.
            </p>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
              {tweets.slice(0, 12).map((t, i) => (
                <div key={i} style={{ paddingBottom: 14, borderBottom: "1px solid var(--line)" }}>
                  <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6 }}>
                    <span style={{ fontWeight: 500, fontSize: 13 }}>@{t.handle}</span>
                    <span className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>{t.date}</span>
                    {t.tickers?.map((sym) => (
                      <span key={sym} className="mono" style={{
                        fontSize: 11, padding: "2px 6px", borderRadius: 3,
                        background: "var(--bg-card)", border: "1px solid var(--line)",
                        color: "var(--accent)",
                      }}>${sym}</span>
                    ))}
                  </div>
                  <div style={{ fontSize: 13.5, lineHeight: 1.5, color: "var(--ink-2)" }}>
                    {t.text}
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* New shoutouts to consider adding to seed list */}
        {run.new_mentioned_handles?.length > 0 && (
          <p className="muted" style={{ fontSize: 12.5, marginTop: 14 }}>
            New @-mentions surfaced in today's feed (potential additions to watch list — review manually):{" "}
            {run.new_mentioned_handles.slice(0, 15).map((h) => (
              <span key={h} className="mono" style={{ marginRight: 8 }}>@{h}</span>
            ))}
          </p>
        )}
      </div>
    </section>
  );
}

function buildCpoAllocationSeries(history, currentPositions) {
  return history.map(h => {
    const equity = h.nav || 0;
    const cash = h.cash || 0;
    const weights = h.weights || {};
    return {
      date: h.date,
      nav: equity,
      cash,
      positions: Object.entries(weights).map(([ticker, w]) => ({ ticker, value: w * equity })),
    };
  });
}

function buildCpoCurrentSlices(current, positions) {
  const slices = [];
  if ((current.cash || 0) > 0.5) {
    slices.push({ label: "CASH", value: current.cash, color: stableTickerColor("CASH") });
  }
  (positions || []).forEach((p) => {
    if (!p.market_value || p.market_value < 0.5) return;
    slices.push({ label: p.ticker, value: p.market_value, color: stableTickerColor(p.ticker) });
  });
  return slices;
}


function Hero({ data }) {
  const { fund, summary } = data;
  const daysLive = Math.max(0, Math.floor(
    (new Date(fund.last_updated) - new Date(fund.inception_date)) / (24 * 3600 * 1000)
  ));
  // Smart age string — days / months / years depending on scale.
  // Avoids the "0.0 years" banner awkwardness when the fund is new.
  let ageLabel;
  if (daysLive < 1) {
    ageLabel = "less than a day";
  } else if (daysLive === 1) {
    ageLabel = "1 day";
  } else if (daysLive < 60) {
    ageLabel = `${daysLive} days`;
  } else if (daysLive < 730) {
    const months = Math.round(daysLive / 30.4);
    ageLabel = months === 1 ? "1 month" : `${months} months`;
  } else {
    const years = (daysLive / 365.25).toFixed(1);
    ageLabel = `${years} years`;
  }

  const animReturn = useCountUp(summary.return_since_inception * 100, 1400);
  const animNav = useCountUp(summary.nav, 1400);

  const outperf = summary.return_since_inception - summary.benchmark_return_since_inception;

  return (
    <section style={{ paddingTop: 88, paddingBottom: 60 }}>
      <div className="container">
        <div style={{ display: "grid", gridTemplateColumns: "1fr", gap: 48 }}>
          <div>
            <div className="eyebrow" style={{ marginBottom: 22 }}>
              An open experiment · live since {fmtDate(fund.inception_date, { style: "long" })}
            </div>
            <h1 className="h-display" style={{ textWrap: "balance" }}>
              What happens when <em style={{ fontStyle: "italic", color: "var(--accent)" }}>Claude</em> + academic{" "}
              <em style={{ fontStyle: "italic", color: "var(--accent)" }}>multi-agent</em> research<br />
              run a paper portfolio for {ageLabel}?
            </h1>
            <p className="lead" style={{ marginTop: 22 }}>
              {fund.name} is a transparent <strong>paper-trading</strong> experiment built on{" "}
              <a href="https://github.com/TauricResearch/TradingAgents" target="_blank" rel="noopener" style={{ color: "var(--accent)" }}>TradingAgents</a>{" "}
              — an academic multi-agent LLM research framework (<a href="https://arxiv.org/abs/2412.20138" target="_blank" rel="noopener" style={{ color: "var(--accent)" }}>arXiv:2412.20138</a>,
              Tauric Research). The reasoning model is GPT-5.5 via OpenAI's Codex CLI.
              Every trade, every thesis, every source — published live. Not real money, not advice.
            </p>
          </div>

          {/* Allocation time-slider donut */}
          <div style={{
            border: "1px solid var(--line)",
            borderRadius: 12,
            background: "var(--bg-card)",
            padding: "24px 28px",
          }}>
            {data.allocation_series?.points?.length ? (
              <TimeSliderDonut series={data.allocation_series.points} />
            ) : (
              <AllocationDonut
                slices={buildLlmAllocationSlices(data.holdings, summary.nav - (summary.positions_value || 0))}
                total={summary.nav}
                subTotal="hover for breakdown"
              />
            )}
          </div>

          {/* Hero stats */}
          <div style={{
            display: "grid",
            gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
            border: "1px solid var(--line)",
            borderRadius: 12,
            overflow: "hidden",
            background: "var(--bg-card)",
          }}>
            <HeroStat
              label="Net asset value"
              value={fmtUSD(animNav, { decimals: 0 })}
              sub={`from ${fmtUSD(summary.starting_capital, { decimals: 0 })} seed`}
            />
            <HeroStat
              label="Return since inception"
              value={
                <span className={summary.return_since_inception >= 0 ? "pos" : "neg"}>
                  {animReturn >= 0 ? "+" : ""}{animReturn.toFixed(2)}%
                </span>
              }
              sub={`S&P 500: ${fmtPct(summary.benchmark_return_since_inception, { decimals: 2 })}`}
            />
            <HeroStat
              label="Alpha vs benchmark"
              value={
                <span className={outperf >= 0 ? "pos" : "neg"}>
                  {fmtPct(outperf, { decimals: 2 })}
                </span>
              }
              sub="cumulative, gross of fees"
            />
            <HeroStat
              label="Year-to-date"
              value={
                <span className={summary.return_ytd >= 0 ? "pos" : "neg"}>
                  {fmtPct(summary.return_ytd, { decimals: 2 })}
                </span>
              }
              sub={`today ${fmtPct(summary.return_1d, { decimals: 2 })}`}
            />
            {summary.return_on_deployed != null && (
              <HeroStat
                label="Return on deployed"
                value={
                  <span className={summary.return_on_deployed >= 0 ? "pos" : "neg"}>
                    {fmtPct(summary.return_on_deployed, { decimals: 2 })}
                  </span>
                }
                sub={`stock-picking edge · positions ${fmtUSD(summary.positions_value, { decimals: 0 })} vs cost ${fmtUSD(summary.positions_cost_basis, { decimals: 0 })}`}
              />
            )}
            {summary.cash_drag_pnl != null && (
              <HeroStat
                label="Cost of cash discipline"
                value={
                  <span className={summary.cash_drag_pnl >= 0 ? "pos" : "neg"}>
                    {summary.cash_drag_pnl >= 0 ? "+" : ""}{fmtUSD(summary.cash_drag_pnl, { decimals: 0 })}
                  </span>
                }
                sub={`vs 100% SPY since inception (${fmtUSD(summary.counterfactual_spy_nav, { decimals: 0 })})${summary.cash_drag_pnl_after_tax != null ? ` · after tax: ${summary.cash_drag_pnl_after_tax >= 0 ? "+" : ""}${fmtUSD(summary.cash_drag_pnl_after_tax, { decimals: 0 })} (SPY held = untaxed)` : ""}`}
              />
            )}
            {summary.tax?.after_tax_alpha_now_pp != null && (
              <HeroStat
                label="Alpha (after tax to date)"
                value={
                  <span className={summary.tax.after_tax_alpha_now_pp >= 0 ? "pos" : "neg"}>
                    {summary.tax.after_tax_alpha_now_pp >= 0 ? "+" : ""}{summary.tax.after_tax_alpha_now_pp.toFixed(2)}pp
                  </span>
                }
                sub={`Tax owed to date: ${fmtUSD(summary.tax.tax_owed_now, { decimals: 0 })} (40% on net realized gains; losses don't offset other income).`}
              />
            )}
            {summary.tax && (
              <HeroStat
                label="Realized P&L"
                value={
                  <span className={summary.tax.realized_net >= 0 ? "pos" : "neg"}>
                    {summary.tax.realized_net >= 0 ? "+" : ""}{fmtUSD(summary.tax.realized_net, { decimals: 0 })}
                  </span>
                }
                sub={`${summary.tax.lots_realized} closed lots. Unrealized: ${fmtUSD(summary.tax.unrealized_net || 0, { decimals: 0 })} across ${summary.tax.lots_open} open lots.`}
              />
            )}
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- AllocationDonut ----------
// Interactive SVG donut showing cash + per-position allocation. Hover any
// slice → center label updates with ticker, dollar value, and weight.
// Click on a slice (no-op currently — could deep-link to positions in future).
//
// Props:
//   slices: [{label, value, color}]  (value in $, will be normalized to %)
//   total: $ total to render in center default state
//   subTotal: optional sub-line shown below total
function AllocationDonut({ slices, total, subTotal }) {
  const [hover, setHover] = React.useState(null);
  // Layout
  const W = 360, H = 360, R_OUTER = 140, R_INNER = 92, CX = W/2, CY = H/2;
  const totalVal = slices.reduce((s, x) => s + Math.max(0, x.value || 0), 0) || 1;

  // Build arc paths
  let angle = -Math.PI / 2;   // start at 12 o'clock
  const arcs = slices.map((s, i) => {
    const frac = Math.max(0, s.value || 0) / totalVal;
    const a1 = angle;
    const a2 = angle + frac * 2 * Math.PI;
    angle = a2;
    const x1o = CX + R_OUTER * Math.cos(a1), y1o = CY + R_OUTER * Math.sin(a1);
    const x2o = CX + R_OUTER * Math.cos(a2), y2o = CY + R_OUTER * Math.sin(a2);
    const x1i = CX + R_INNER * Math.cos(a2), y1i = CY + R_INNER * Math.sin(a2);
    const x2i = CX + R_INNER * Math.cos(a1), y2i = CY + R_INNER * Math.sin(a1);
    const large = (a2 - a1) > Math.PI ? 1 : 0;
    // Skip tiny / zero slices (would render as empty path)
    if (frac < 0.0005) return { d: "", frac, label: s.label, value: s.value, color: s.color };
    const d = [
      `M ${x1o.toFixed(2)} ${y1o.toFixed(2)}`,
      `A ${R_OUTER} ${R_OUTER} 0 ${large} 1 ${x2o.toFixed(2)} ${y2o.toFixed(2)}`,
      `L ${x1i.toFixed(2)} ${y1i.toFixed(2)}`,
      `A ${R_INNER} ${R_INNER} 0 ${large} 0 ${x2i.toFixed(2)} ${y2i.toFixed(2)}`,
      "Z",
    ].join(" ");
    // Midpoint for label-line targeting (not used currently but kept for future)
    const aMid = (a1 + a2) / 2;
    const midX = CX + ((R_OUTER + R_INNER) / 2) * Math.cos(aMid);
    const midY = CY + ((R_OUTER + R_INNER) / 2) * Math.sin(aMid);
    return { d, frac, label: s.label, value: s.value, color: s.color, midX, midY };
  });

  const focused = hover != null ? slices[hover] : null;
  const focusedFrac = hover != null ? arcs[hover].frac : null;

  // Format helper
  const fmt = (n) => n >= 1000 ? `$${(n/1000).toFixed(1)}K` : `$${n.toFixed(0)}`;

  return (
    <div style={{ position: "relative", display: "flex", alignItems: "center", gap: 32, flexWrap: "wrap" }}>
      <svg viewBox={`0 0 ${W} ${H}`} style={{ width: W, maxWidth: "100%", height: "auto" }}>
        {arcs.map((arc, i) => (
          arc.d ? (
            <path
              key={i}
              d={arc.d}
              fill={arc.color}
              stroke="var(--bg)"
              strokeWidth="2"
              style={{
                cursor: "pointer",
                opacity: hover != null && hover !== i ? 0.35 : 1,
                transform: hover === i ? `translate(${(arcs[i].midX - CX) * 0.04}px, ${(arcs[i].midY - CY) * 0.04}px)` : "none",
                transformOrigin: `${CX}px ${CY}px`,
                transition: "opacity 180ms ease, transform 180ms ease",
              }}
              onMouseEnter={() => setHover(i)}
              onMouseLeave={() => setHover(null)}
            />
          ) : null
        ))}
        {/* Center text — defaults to total NAV; updates on hover */}
        <g pointerEvents="none">
          <text x={CX} y={CY - 12} textAnchor="middle"
                fontSize="13" fontFamily="var(--mono)"
                fill="var(--ink-3)">
            {focused ? focused.label : "Total NAV"}
          </text>
          <text x={CX} y={CY + 14} textAnchor="middle"
                fontSize="26" fontFamily="var(--serif)"
                fill="var(--ink)">
            {focused ? fmt(focused.value) : fmt(total)}
          </text>
          <text x={CX} y={CY + 36} textAnchor="middle"
                fontSize="12" fontFamily="var(--mono)"
                fill={focused ? focused.color : "var(--ink-3)"}>
            {focused ? `${(focusedFrac * 100).toFixed(1)}% of book` : (subTotal || "")}
          </text>
        </g>
      </svg>

      {/* Legend on the right */}
      <div style={{ display: "flex", flexDirection: "column", gap: 8, minWidth: 180 }}>
        {slices.map((s, i) => {
          const pct = (Math.max(0, s.value || 0) / totalVal) * 100;
          if (pct < 0.05) return null;
          const isHover = hover === i;
          return (
            <div
              key={i}
              onMouseEnter={() => setHover(i)}
              onMouseLeave={() => setHover(null)}
              style={{
                display: "flex", alignItems: "center", gap: 10,
                cursor: "pointer",
                opacity: hover != null && !isHover ? 0.4 : 1,
                fontFamily: "var(--mono)", fontSize: 12,
                transition: "opacity 180ms ease",
              }}>
              <span style={{
                display: "inline-block", width: 12, height: 12,
                borderRadius: 3, background: s.color,
                outline: isHover ? "2px solid var(--ink-2)" : "none",
                outlineOffset: 2,
              }} />
              <span style={{ flex: 1, color: "var(--ink-2)" }}>{s.label}</span>
              <span className="tnum" style={{ color: "var(--ink-3)" }}>{pct.toFixed(1)}%</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// Stable palette — first slice is CASH (gray), then position colors cycle.
const ALLOCATION_PALETTE = [
  "#A8B3BD", // cash (neutral gray)
  "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
  "#8c564b", "#e377c2", "#17becf", "#bcbd22", "#7f7f7f",
];

// Stable color assignment per ticker so a position keeps the same color
// across snapshots (otherwise hover-tracking jumps colors as slices reorder).
function stableTickerColor(ticker, _seen = {}) {
  if (ticker === "CASH") return ALLOCATION_PALETTE[0];
  if (!stableTickerColor._map) stableTickerColor._map = new Map();
  const m = stableTickerColor._map;
  if (!m.has(ticker)) m.set(ticker, ALLOCATION_PALETTE[1 + (m.size % (ALLOCATION_PALETTE.length - 1))]);
  return m.get(ticker);
}

// Append (or replace today's) a live "now" point to an allocation series so the
// time-slider donut's default/latest slice + total match the live hero NAV/cash
// (otherwise the donut shows the stale snapshot while the hero shows live).
function withLivePoint(points, live) {
  if (!live || !live.nav) return points || [];
  const liveDate = (live.as_of || "").slice(0, 10) || "now";
  const lp = {
    date: liveDate, nav: live.nav, cash: live.cash || 0,
    positions: (live.positions || []).map((p) => ({ ticker: p.ticker, value: p.market_value })),
  };
  const out = (points || []).slice();
  if (out.length && out[out.length - 1].date === lp.date) out[out.length - 1] = lp;
  else out.push(lp);
  return out;
}

function slicesFromSeriesPoint(point) {
  const slices = [];
  if ((point.cash || 0) > 0.5) {
    slices.push({ label: "CASH", value: point.cash, color: stableTickerColor("CASH") });
  }
  (point.positions || []).forEach((p) => {
    if (!p.value || p.value < 0.5) return;
    slices.push({ label: p.ticker, value: p.value, color: stableTickerColor(p.ticker) });
  });
  return slices;
}

// ---------- TimeSliderDonut ----------
// Day-wise slider over an allocation_series. As the slider moves, the donut's
// slices animate via CSS transitions (paths re-render but with stroke-dash
// transitions for the radial extents). New positions appear and old ones fade.
//
// Props:
//   series: [{date, cash, positions:[{ticker, value}], nav}]
function TimeSliderDonut({ series }) {
  // Pre-seed color map so every ticker we'll ever see has a stable color
  React.useMemo(() => {
    series.forEach(p => (p.positions || []).forEach(x => stableTickerColor(x.ticker)));
  }, [series]);

  const [idx, setIdx] = React.useState(series.length - 1);
  const [playing, setPlaying] = React.useState(false);

  React.useEffect(() => {
    if (!playing) return;
    const tid = setInterval(() => {
      setIdx(i => {
        if (i >= series.length - 1) { setPlaying(false); return i; }
        return i + 1;
      });
    }, 380);
    return () => clearInterval(tid);
  }, [playing, series.length]);

  if (!series || series.length === 0) return null;
  const point = series[idx] || series[series.length - 1];
  const slices = slicesFromSeriesPoint(point);

  // Date formatting
  const dStr = (() => {
    try {
      const d = new Date(point.date);
      return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
    } catch { return point.date; }
  })();

  return (
    <div>
      <AllocationDonut
        slices={slices}
        total={point.nav}
        subTotal={`${dStr} · hover any slice`}
      />
      <div style={{ marginTop: 20, padding: "0 12px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 14, marginBottom: 8 }}>
          <button
            onClick={() => { if (idx >= series.length - 1) setIdx(0); setPlaying(p => !p); }}
            className="btn-ghost"
            style={{ padding: "6px 14px", fontFamily: "var(--mono)", fontSize: 12 }}>
            {playing ? "❚❚ pause" : "▶ play"}
          </button>
          <div className="mono" style={{ fontSize: 11, color: "var(--ink-3)", flex: 1 }}>
            {dStr} · day {idx + 1} of {series.length} · NAV {fmtUSD(point.nav, { decimals: 0 })}
          </div>
        </div>
        <input
          type="range"
          min={0}
          max={series.length - 1}
          value={idx}
          onChange={(e) => { setPlaying(false); setIdx(parseInt(e.target.value, 10)); }}
          style={{ width: "100%", accentColor: "var(--accent)" }}
        />
        <div style={{ display: "flex", justifyContent: "space-between", marginTop: 4 }}>
          <span className="mono" style={{ fontSize: 10, color: "var(--ink-3)" }}>{series[0]?.date}</span>
          <span className="mono" style={{ fontSize: 10, color: "var(--ink-3)" }}>{series[series.length - 1]?.date}</span>
        </div>
      </div>
    </div>
  );
}

// Build an allocation-series-shaped array from the ETF history (which only
// stores weights, not per-ticker absolute values).
function buildEtfAllocationSeries(history) {
  if (!history || !history.length) return [];
  return history.map(h => {
    const equity = h.nav || 0;
    const cash = h.cash || 0;
    const weights = h.weights || {};
    return {
      date: h.date,
      nav: equity,
      cash,
      positions: Object.entries(weights).map(([ticker, w]) => ({
        ticker, value: (w * equity),
      })),
    };
  });
}

function buildLlmAllocationSlices(holdings, cash) {
  const slices = [{ label: "CASH", value: cash, color: ALLOCATION_PALETTE[0] }];
  const positions = (holdings || []).filter(h => h.ticker !== "CASH" && h.ticker !== "Cash");
  positions
    .sort((a, b) => (b.market_value || 0) - (a.market_value || 0))
    .forEach((h, i) => {
      slices.push({
        label: h.ticker,
        value: h.market_value || (h.shares ? h.shares * (h.last_price || h.avg_cost || 0) : 0),
        color: ALLOCATION_PALETTE[1 + (i % (ALLOCATION_PALETTE.length - 1))],
      });
    });
  return slices;
}

function buildEtfAllocationSlices(current) {
  // ETF doesn't have a holdings array on the JSON; reconstruct from weights_actual
  const equity = current.equity || current.nav || 0;
  const weights = current.weights_actual || {};
  const cash = current.cash || 0;
  const slices = [];
  if (cash > 1) slices.push({ label: "CASH", value: cash, color: ALLOCATION_PALETTE[0] });
  Object.entries(weights)
    .sort((a, b) => b[1] - a[1])
    .forEach(([ticker, w], i) => {
      slices.push({
        label: ticker,
        value: w * equity,
        color: ALLOCATION_PALETTE[1 + (i % (ALLOCATION_PALETTE.length - 1))],
      });
    });
  return slices;
}

function HeroStat({ label, value, sub }) {
  return (
    <div style={{ padding: "24px 28px", borderRight: "1px solid var(--line)", borderBottom: "1px solid var(--line)" }}>
      <div className="micro" style={{ marginBottom: 12 }}>{label}</div>
      <div className="serif tnum" style={{ fontSize: 34, lineHeight: 1 }}>{value}</div>
      <div className="muted tnum" style={{ fontSize: 12, marginTop: 8 }}>{sub}</div>
    </div>
  );
}

function MetricsStrip({ metrics }) {
  const items = [
    { label: "Sharpe ratio", value: fmtNum(metrics.sharpe_ratio, 2), sub: "risk-adjusted return" },
    { label: "Max drawdown", value: <span className="neg">{fmtPct(metrics.max_drawdown, { decimals: 2 })}</span>, sub: fmtDate(metrics.max_drawdown_date, { style: "long" }) },
    { label: "Volatility (ann.)", value: fmtPct(metrics.volatility_annualized, { decimals: 1, signed: false }), sub: `Beta ${fmtNum(metrics.beta_vs_benchmark, 2)}` },
    { label: "Win rate", value: fmtPct(metrics.win_rate, { decimals: 0, signed: false }), sub: `${metrics.winning_trades}W · ${metrics.losing_trades}L` },
    { label: "Trades placed", value: metrics.total_trades, sub: `avg hold ${fmtNum(metrics.avg_holding_days, 1)}d` },
  ];
  return (
    <section className="tight" id="risk">
      <div className="container">
        <div style={{
          display: "grid",
          gridTemplateColumns: "repeat(auto-fit, minmax(170px, 1fr))",
          border: "1px solid var(--line)",
          borderRadius: 12,
          background: "var(--bg-card)",
          overflow: "hidden",
        }}>
          {items.map((it, i) => (
            <div key={i} style={{ padding: "22px 24px", borderRight: i < items.length - 1 ? "1px solid var(--line)" : "none" }}>
              <div className="micro" style={{ marginBottom: 10 }}>{it.label}</div>
              <div className="serif tnum" style={{ fontSize: 26, lineHeight: 1 }}>{it.value}</div>
              <div className="muted tnum" style={{ fontSize: 12, marginTop: 6 }}>{it.sub}</div>
            </div>
          ))}
        </div>
        <p className="muted" style={{ fontSize: 12.5, marginTop: 14, maxWidth: "62ch" }}>
          Transparency note: the {metrics.losing_trades} losing trades shown above are a feature, not a bug.
          A portfolio manager — human or AI — that never loses is either lying or not trying.
        </p>
      </div>
    </section>
  );
}

function Methodology() {
  const cards = [
    {
      n: "01",
      title: "Reads the market",
      body: "Each morning Claude reviews overnight news, earnings, macro data, and its own position journal. No proprietary feeds — everything is public information, the same material any retail investor has access to.",
    },
    {
      n: "02",
      title: "Forms a thesis",
      body: "Before any trade, Claude writes down what it believes and why. Positions are sized against a risk budget with per-position, sector, and basket caps plus a cash floor — specific thresholds are proprietary.",
    },
    {
      n: "03",
      title: "Executes and journals",
      body: "Orders route to a real brokerage at prevailing market prices. Every fill, every rationale, every change of mind — appended to the public log within minutes.",
    },
    {
      n: "04",
      title: "Reviews honestly",
      body: "Weekly self-critique. If a thesis broke, the position exits — even at a loss. The log shows both the wins and the exits, without editing.",
    },
  ];
  return (
    <section id="methodology">
      <div className="container">
        <div className="eyebrow" style={{ marginBottom: 14 }}>Method</div>
        <h2 className="h-section">How Claude manages capital.</h2>
        <p className="lead" style={{ marginBottom: 40 }}>
          A deliberately simple process, because simple processes are the ones you can actually audit.
        </p>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", gap: 0, border: "1px solid var(--line)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
          {cards.map((c, i) => (
            <div key={c.n} style={{ padding: "28px 28px 32px", borderRight: "1px solid var(--line)", borderBottom: "1px solid var(--line)" }}>
              <div className="mono" style={{ fontSize: 12, color: "var(--accent)", marginBottom: 18 }}>{c.n}</div>
              <div style={{ fontFamily: "var(--serif)", fontSize: 22, marginBottom: 10 }}>{c.title}</div>
              <div style={{ fontSize: 14.5, color: "var(--ink-2)", lineHeight: 1.6 }}>{c.body}</div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

function Disclaimer({ text }) {
  return (
    <section className="tight">
      <div className="container">
        <div className="disclaimer-block">
          <div className="micro" style={{ color: "var(--accent)", marginBottom: 10 }}>Important disclosures</div>
          <p style={{ margin: 0 }}>{text}</p>
        </div>
      </div>
    </section>
  );
}

// "Ask an AI" — no API integration: the site publishes AI-readable markdown
// sections under /ai/ (indexed by /llms.txt), so any assistant with browsing
// can answer questions about it. This row just hands visitors the prompt.
const ASK_AI_PROMPT =
  "Read https://theclaudetrader.vercel.app/llms.txt and the /ai/*.md section pages it links " +
  "(especially /ai/verify.md), then answer my questions about this AI-run trading experiment " +
  "(funds, performance, holdings, recent decisions). It is built to be independently verifiable: " +
  "a per-trade reconciliation joining each AI decision to its Alpaca order, broker fill time and " +
  "realized P&L — including losers, cancelled and rejected orders, and decisions never acted on — " +
  "at /api/reconciliation; order ids and broker-stamped fill times pulled straight from Alpaca at " +
  "/data/broker_activity.json; and each day's record hashed and anchored to the Bitcoin blockchain " +
  "via OpenTimestamps (free/anonymous — anyone can run `ots verify`). Also tell me concretely how " +
  "I can verify its claims myself and what its limitations are.";

function AskAIRow() {
  const [copied, setCopied] = useState(false);
  const [gem, setGem] = useState(false);
  const copy = () => {
    try {
      navigator.clipboard.writeText(ASK_AI_PROMPT);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch (e) { /* clipboard unavailable — links still work */ }
  };
  // Gemini's consumer app has no ?q= prefill (a link just lands on the home
  // page), so copy the prompt to the clipboard first, then open it — one paste.
  const openGemini = () => {
    try { navigator.clipboard.writeText(ASK_AI_PROMPT); } catch (e) {}
    setGem(true);
    setTimeout(() => setGem(false), 4000);
    window.open("https://gemini.google.com/app", "_blank", "noopener");
  };
  const linkStyle = {
    color: "var(--accent, #C6513A)", textDecoration: "none", border: "1px solid var(--line)",
    borderRadius: 6, padding: "3px 10px", fontSize: 12, cursor: "pointer", background: "none",
  };
  const q = encodeURIComponent(ASK_AI_PROMPT);
  return (
    <div className="container" style={{ display: "flex", alignItems: "center", flexWrap: "wrap", gap: 10, fontSize: 13, color: "var(--ink-3)", paddingBottom: 18 }}>
      <span>Ask an AI to audit this site (it'll explain how to verify us):</span>
      <button style={linkStyle} onClick={copy}>{copied ? "Copied ✓" : "Copy prompt"}</button>
      <a style={linkStyle} href={`https://chatgpt.com/?q=${q}`} target="_blank" rel="noopener">Open in ChatGPT</a>
      <a style={linkStyle} href={`https://claude.ai/new?q=${q}`} target="_blank" rel="noopener">Open in Claude</a>
      <button style={linkStyle} onClick={openGemini} title="Copies the prompt, then opens Gemini — just paste (Gemini has no prefill link)">
        {gem ? "Prompt copied — paste in Gemini" : "Open in Gemini"}
      </button>
      <a style={linkStyle} href={`https://www.perplexity.ai/search?q=${q}`} target="_blank" rel="noopener">Open in Perplexity</a>
      <a style={{ ...linkStyle, border: "none", padding: 0 }} href="/ai/verify.md" target="_blank">how to verify</a>
    </div>
  );
}

function Footer({ fund }) {
  return (
    <footer style={{ padding: "40px 0 60px", borderTop: "1px solid var(--line)" }}>
      <div className="container" style={{ padding: "8px 0 20px" }}><Signup /></div>
      <div className="container" style={{ display: "flex", gap: 20, justifyContent: "center", flexWrap: "wrap", fontSize: 13, padding: "0 0 32px" }}>
        <a href="https://x.com/theclaudetrader" target="_blank" rel="noopener noreferrer" style={{ color: "var(--ink-3)" }}>𝕏 @theclaudetrader</a>
        <a href="https://instagram.com/theclaudetrader" target="_blank" rel="noopener noreferrer" style={{ color: "var(--ink-3)" }}>Instagram @theclaudetrader</a>
      </div>
      <AskAIRow />
      <div className="container" style={{ display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 16, fontSize: 13, color: "var(--ink-3)" }}>
        <div>© {new Date().getFullYear()} {fund.name}. A research project, not a fund.</div>
        <div className="mono" style={{ fontSize: 12 }}>
          Data refreshed {fmtDateTime(fund.last_updated)}
        </div>
      </div>
    </footer>
  );
}

function TweaksPanel({ values, onChange, theme }) {
  const [editMode, setEditMode] = useState(false);

  useEffect(() => {
    const handler = (e) => {
      if (!e.data || typeof e.data !== "object") return;
      if (e.data.type === "__activate_edit_mode") setEditMode(true);
      if (e.data.type === "__deactivate_edit_mode") setEditMode(false);
    };
    window.addEventListener("message", handler);
    window.parent.postMessage({ type: "__edit_mode_available" }, "*");
    return () => window.removeEventListener("message", handler);
  }, []);

  if (!editMode) return null;

  const update = (k, v) => {
    onChange({ ...values, [k]: v });
    window.parent.postMessage({ type: "__edit_mode_set_keys", edits: { [k]: v } }, "*");
  };

  return (
    <div className="tweaks">
      <h4>Tweaks</h4>
      <div className="tweak-row">
        <span>Accent</span>
        <div className="swatch-row">
          {Object.entries(ACCENTS).map(([id, c]) => (
            <button
              key={id}
              className={cx("swatch", values.accent === id && "active")}
              style={{ background: theme === "dark" ? c.dark : c.light }}
              onClick={() => update("accent", id)}
              title={c.label}
              aria-label={c.label}
            />
          ))}
        </div>
      </div>
      <div className="tweak-row">
        <span>Benchmark line</span>
        <button className="btn-ghost" onClick={() => update("showBenchmark", !values.showBenchmark)}>
          {values.showBenchmark ? "ON" : "OFF"}
        </button>
      </div>
      <div className="tweak-row">
        <span>Fund name</span>
        <input
          value={values.fundName}
          onChange={(e) => update("fundName", e.target.value)}
          style={{
            background: "var(--bg)", border: "1px solid var(--line)", color: "var(--ink)",
            borderRadius: 6, padding: "4px 8px", fontSize: 12, fontFamily: "var(--mono)", width: 130,
          }}
        />
      </div>
    </div>
  );
}

function App() {
  const [data, setData] = useState(null);
  const [live, setLive] = useState(null);
  const [error, setError] = useState(null);
  const [theme, setTheme] = useState(() => localStorage.getItem("sc-theme") || "light");
  const [tweaks, setTweaks] = useState(TWEAK_DEFAULTS);
  // Tab selector: "main" (LLM portfolio) or "etf" (60/30/10 sister fund).
  // Persists across reloads via localStorage so back-button works naturally.
  const [tab, setTab] = useState(() => localStorage.getItem("sc-tab") || "main");
  useEffect(() => { localStorage.setItem("sc-tab", tab); }, [tab]);
  // Shared timeframe state so the NAV chart and Alpha chart below it stay in sync.
  // Initialized to "1D" matching PerformanceChart's default; AlphaChart skips 1D
  // and clamps to "1M" since intraday SPY benchmark isn't reliable.
  const [chartTf, setChartTf] = useState("1D");

  // Load static data (holdings metadata, thesis text, activity feed, perf chart).
  // cache-bust with a timestamp so stale browser cache can't keep showing old theses.
  useEffect(() => {
    fetch(`data/portfolio.json?t=${Date.now()}`, { cache: "no-store" })
      .then((r) => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
      .then(setData)
      .catch((e) => setError(e.message));
  }, []);

  // Live poll of /api/live — overlays NAV/cash/positions/last_price on top of static data.
  // 30s interval; edge-cached 15s so Alpaca only sees ~4 calls/min regardless of viewer count.
  useEffect(() => {
    let cancelled = false;
    const fetchLive = async () => {
      try {
        const r = await fetch("/api/live", { cache: "no-store" });
        if (!r.ok) return;
        const body = await r.json();
        if (!cancelled && !body.error) setLive(body);
      } catch {}
    };
    fetchLive();
    const id = setInterval(fetchLive, 30000);
    return () => { cancelled = true; clearInterval(id); };
  }, []);

  // Fetch SPY since inception so the Hero's "Alpha vs benchmark" and "S&P 500:" fields
  // reflect today's live SPY, not a stale portfolio_snapshots value. Poll every 2 min.
  const [benchLive, setBenchLive] = useState(null);
  useEffect(() => {
    let cancelled = false;
    const fetchBench = async () => {
      try {
        const r = await fetch("/api/history?period=ALL", { cache: "no-store" });
        if (!r.ok) return;
        const body = await r.json();
        const pts = (body.points || []).filter((p) => p.benchmark != null);
        if (pts.length >= 2 && !cancelled) {
          const first = pts[0].benchmark;
          const last = pts[pts.length - 1].benchmark;
          setBenchLive({ return_since_inception: last / first - 1, last_benchmark: last, first_benchmark: first });
        }
      } catch {}
    };
    fetchBench();
    const id = setInterval(fetchBench, 120000);
    return () => { cancelled = true; clearInterval(id); };
  }, []);

  // Theme
  useEffect(() => {
    document.documentElement.setAttribute("data-theme", theme);
    localStorage.setItem("sc-theme", theme);
    applyAccent(tweaks.accent, theme);
  }, [theme, tweaks.accent]);

  // Merge live Alpaca state over the static snapshot.
  // MUST be before any early return (rules of hooks).
  const merged = useMemo(() => {
    if (!data) return null;
    if (!live) return data;
    // Prefer the baked Alpaca-anchored benchmark (same SPY source + nearest-trading-day
    // inception as the other funds) over the live /api/history feed, which drifted ~1-2pp.
    const benchmarkReturn = data.summary.benchmark_return_since_inception
      ?? (benchLive ? benchLive.return_since_inception : null);
    // Recompute every NAV/benchmark-dependent hero tile from the SAME live NAV +
    // live benchmark, so they can't drift apart from the live NAV/alpha tiles
    // (which previously used live data while these stayed frozen at last publish).
    const seed = data.summary.starting_capital || 0;
    const counterfactualLive =
      seed && benchmarkReturn != null ? seed * (1 + benchmarkReturn) : data.summary.counterfactual_spy_nav;
    const cashDragLive =
      live.nav != null && counterfactualLive != null ? live.nav - counterfactualLive : data.summary.cash_drag_pnl;
    const livePos = live.positions || [];
    const livePosVal = livePos.reduce((a, p) => a + (p.market_value || 0), 0);
    const liveCost = livePos.reduce((a, p) => a + (p.avg_cost || 0) * (p.qty || 0), 0);
    const haveLivePos = livePos.length > 0 && liveCost > 0;
    const deployedLive = haveLivePos ? livePosVal / liveCost - 1 : data.summary.return_on_deployed;
    const taxOwed = data.summary.tax?.tax_owed_now || 0; // realized-based, doesn't move intraday
    const afterTaxAlphaLive =
      seed && benchmarkReturn != null
        ? ((live.nav - taxOwed) / seed - 1 - benchmarkReturn) * 100
        : data.summary.tax?.after_tax_alpha_now_pp;
    const unrealLive = haveLivePos ? livePosVal - liveCost : data.summary.tax?.unrealized_net;
    const cashDragAfterTaxLive =
      live.nav != null && counterfactualLive != null && data.summary.tax
        ? live.nav - taxOwed - counterfactualLive
        : data.summary.cash_drag_pnl_after_tax;
    return {
      ...data,
      fund: { ...data.fund, last_updated: live.as_of || data.fund.last_updated },
      summary: {
        ...data.summary,
        nav: live.nav,
        return_since_inception: live.return_since_inception,
        // inception is in the current year → YTD tracks since-inception
        return_ytd: live.return_since_inception,
        benchmark_return_since_inception: benchmarkReturn,
        return_on_deployed: deployedLive,
        positions_value: haveLivePos ? livePosVal : data.summary.positions_value,
        positions_cost_basis: haveLivePos ? liveCost : data.summary.positions_cost_basis,
        cash_drag_pnl: cashDragLive,
        cash_drag_pnl_after_tax: cashDragAfterTaxLive,
        counterfactual_spy_nav: counterfactualLive,
        tax: data.summary.tax
          ? { ...data.summary.tax, after_tax_alpha_now_pp: afterTaxAlphaLive, unrealized_net: unrealLive }
          : data.summary.tax,
      },
      allocation_series: {
        ...data.allocation_series,
        points: withLivePoint(data.allocation_series?.points, live),
      },
      // Rebuild the holdings list FROM the live Alpaca positions (not by patching
      // the static list): names bought since the last publish must appear, and
      // names sold since must disappear. Static entries only contribute metadata
      // (name/sector/thesis/opened_on) when they match a live ticker.
      holdings: (() => {
        if (!haveLivePos) {
          return data.holdings || [];
        }
        const staticByTicker = Object.fromEntries(
          (data.holdings || []).filter((h) => h.ticker !== "CASH").map((h) => [h.ticker, h])
        );
        const rows = livePos.map((lp) => {
          const h = staticByTicker[lp.ticker] || {
            ticker: lp.ticker,
            name: lp.ticker,
            sector: "—",
            opened_on: null,
            thesis: "New position — thesis appears after the next daily publish.",
          };
          return {
            ...h,
            qty: lp.qty ?? h.shares ?? h.qty,
            shares: lp.qty ?? h.shares,
            avg_cost: lp.avg_cost ?? h.avg_cost,
            last_price: lp.last_price,
            unrealized_pnl: lp.unrealized_plpc,
            weight: live.nav > 0 ? lp.market_value / live.nav : h.weight,
          };
        }).sort((a, b) => (b.weight || 0) - (a.weight || 0));
        const cashRow = (data.holdings || []).find((h) => h.ticker === "CASH");
        rows.push({
          ...(cashRow || { ticker: "CASH", name: "Cash", sector: "—", thesis: "Unallocated cash." }),
          weight: live.nav > 0 ? live.cash / live.nav : (cashRow ? cashRow.weight : 0),
        });
        return rows;
      })(),
    };
  }, [data, live, benchLive]);

  if (error) {
    return (
      <div style={{ padding: 40, fontFamily: "var(--mono)" }}>
        Failed to load portfolio data: {error}
      </div>
    );
  }
  if (!data || !merged) {
    return (
      <div style={{ height: "100vh", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--ink-3)", fontFamily: "var(--mono)", fontSize: 13 }}>
        Loading portfolio…
      </div>
    );
  }

  const activeData = { ...merged, fund: { ...merged.fund, name: tweaks.fundName || merged.fund.name } };

  return (
    <div>
      <Nav fund={activeData.fund} theme={theme} setTheme={setTheme} tab={tab} setTab={setTab} />
      {tab === "etf" ? (
        <>
          <EtfView />
          <Disclaimer text={data.disclaimer} />
          <Footer fund={activeData.fund} />
          <TweaksPanel values={tweaks} onChange={setTweaks} theme={theme} />
        </>
      ) : tab === "cpo" ? (
        <>
          <CpoView />
          <Disclaimer text={data.disclaimer} />
          <Footer fund={activeData.fund} />
          <TweaksPanel values={tweaks} onChange={setTweaks} theme={theme} />
        </>
      ) : tab === "mirror" ? (
        <>
          <MirrorView />
          <Disclaimer text={data.disclaimer} />
          <Footer fund={activeData.fund} />
          <TweaksPanel values={tweaks} onChange={setTweaks} theme={theme} />
        </>
      ) : tab === "verify" ? (
        <>
          <VerifyView />
          <Disclaimer text={data.disclaimer} />
          <Footer fund={activeData.fund} />
          <TweaksPanel values={tweaks} onChange={setTweaks} theme={theme} />
        </>
      ) : (<>
      <Hero data={activeData} />

      <section id="performance" style={{ paddingTop: 30 }}>
        <div className="container">
          <div className="card" style={{ padding: "28px 32px" }}>
            <PerformanceChart
              points={data.performance_series.points}
              showBenchmark={tweaks.showBenchmark}
              liveValue={live ? live.nav : null}
              onTfChange={setChartTf}
            />
            <AlphaChart
              points={data.performance_series.points}
              tf={chartTf === "1D" ? "1M" : chartTf}
              liveValue={live ? live.nav : null}
            />
          </div>
        </div>
      </section>

      <MetricsStrip metrics={data.metrics} />

      <section id="holdings">
        <div className="container">
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", flexWrap: "wrap", gap: 20, marginBottom: 22 }}>
            <div>
              <div className="eyebrow" style={{ marginBottom: 10 }}>The book</div>
              <h2 className="h-section">Every position, with Claude's own reasoning.</h2>
            </div>
            <div className="muted tnum" style={{ fontSize: 13 }}>
              {activeData.holdings.length} positions · <span style={{ color: "var(--ink)" }}>{fmtPct(activeData.holdings.find(h => h.ticker === "CASH")?.weight || 0, { decimals: 0, signed: false })}</span> cash
              {live && <span style={{ marginLeft: 10, color: "var(--accent)", fontSize: 11 }}>· live</span>}
            </div>
          </div>
          <div className="card" style={{ padding: "8px 28px 16px" }}>
            <HoldingsList holdings={activeData.holdings} />
          </div>
          <p className="muted" style={{ fontSize: 12.5, marginTop: 14 }}>
            Click any position to read the thesis.
          </p>
        </div>
      </section>

      <section id="activity">
        <div className="container">
          <div style={{ marginBottom: 22 }}>
            <div className="eyebrow" style={{ marginBottom: 10 }}>Activity log</div>
            <h2 className="h-section">The journal.</h2>
            <p className="lead">
              Trades and unfiltered thinking — in the same feed, same time, same tone.
              Published automatically, not curated after the fact.
            </p>
          </div>
          <div className="card" style={{ padding: "8px 32px 24px" }}>
            <ActivityFeed activity={data.activity} />
          </div>
        </div>
      </section>

      <Methodology />

      <Disclaimer text={data.disclaimer} />

      <Footer fund={activeData.fund} />

      <TweaksPanel values={tweaks} onChange={setTweaks} theme={theme} />
      </>)}
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
