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>
  );
}
