/* studio.jsx — the Studio tab. An AI research + build workspace:
   - Market pulse (Yoshi's read of the day)
   - Analyze: a chart you DRAG across to ask "what happened here?"
   - Build: AI trade ideas that open the trade sheet
   - Automate: turn a market pattern into a standing rule
   Charts are deterministic (seeded) with seeded catalysts, so the
   drag-to-analyze read is real, not random. */

/* ---- 90 day labels, ending "today" --------------------------------------- */
const STUDIO_DATES = (() => {
  const out = [];
  const base = new Date(2026, 2, 3); // Mar 3 2026
  const mo = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  for (let i = 0; i < 90; i++) {
    const d = new Date(base.getTime() + i * 86400000);
    out.push(mo[d.getMonth()] + " " + d.getDate());
  }
  return out;
})();

/* ---- instruments + their seeded catalysts -------------------------------- */
const INSTRUMENTS = [
{ id: "port", label: "Portfolio", seed: 7, base: 372000, unit: "$",
  events: [
  { i: 12, title: "Fed held rates", note: "The pause sent risk assets higher across the board.", shock: 0.028 },
  { i: 36, title: "Tech pulled back", note: "Chipmakers led a broad sector selloff; your NVDA dipped.", shock: -0.045 },
  { i: 58, title: "Earnings beat", note: "Your two largest holdings beat estimates and guided up.", shock: 0.052 },
  { i: 79, title: "Crypto rotation", note: "BTC and SOL ran; the crypto sleeve carried the book.", shock: 0.03 }]
},
{ id: "spx", label: "S&P 500", seed: 21, base: 5180, unit: "",
  events: [
  { i: 18, title: "CPI came in cool", note: "Softer inflation lifted rate-cut odds and the index.", shock: 0.024 },
  { i: 44, title: "Geopolitical jitters", note: "A risk-off week dragged every sector lower.", shock: -0.032 },
  { i: 71, title: "Megacap earnings", note: "Big-tech beats pushed the index to a new high.", shock: 0.036 }]
},
{ id: "nvda", label: "NVDA", seed: 33, base: 118, unit: "$",
  events: [
  { i: 28, title: "Data-center guide raised", note: "Management's demand commentary spiked the stock.", shock: 0.07 },
  { i: 53, title: "Sector downgrade", note: "Analysts trimmed chip price targets; shares fell.", shock: -0.06 },
  { i: 76, title: "AI capex reaffirmed", note: "Hyperscaler spending headlines drove a recovery.", shock: 0.05 }]
},
{ id: "btc", label: "Bitcoin", seed: 44, base: 71000, unit: "$",
  events: [
  { i: 16, title: "ETF inflows surged", note: "An eighth straight inflow day pushed price up.", shock: 0.06 },
  { i: 49, title: "Leverage flush", note: "A fast liquidation cascade wiped out longs.", shock: -0.085 },
  { i: 81, title: "Supply narrative", note: "Halving momentum brought buyers back in.", shock: 0.05 }]
}];


function genSeries(inst) {
  const rnd = mulberry32(inst.seed);
  let v = inst.base;
  const arr = [];
  for (let i = 0; i < 90; i++) {
    v += (rnd() - 0.47) * inst.base * 0.011;
    const ev = inst.events.find((e) => e.i === i);
    if (ev) v *= 1 + ev.shock;
    arr.push(Math.max(v, inst.base * 0.4));
  }
  return arr;
}

const fmtVal = (n, unit) => {
  if (unit === "$") return "$" + (n >= 1000 ? Math.round(n).toLocaleString() : n.toFixed(2));
  return Math.round(n).toLocaleString();
};

/* ---- timeframe horizons for the analyze chart --------------------------- */
const STUDIO_RANGES = ["1D", "3M", "6M", "YTD", "1Y", "5Y", "All"];
const ST_LABEL = { "1D": "today", "3M": "past 3 months", "6M": "past 6 months", "YTD": "year to date", "1Y": "past year", "5Y": "past 5 years", "All": "all time" };
const ST_RANGE = {
  "1D":  { len: 40, vol: 0.0045, evN: 0, days: 1 },
  "3M":  { len: 64, vol: 0.013,  evN: 3, days: 91 },
  "6M":  { len: 60, vol: 0.015,  evN: 3, days: 182 },
  "YTD": { len: 58, vol: 0.014,  evN: 3, days: 152 },
  "1Y":  { len: 52, vol: 0.018,  evN: 4, days: 365 },
  "5Y":  { len: 60, vol: 0.030,  evN: 4, days: 1825 },
  "All": { len: 70, vol: 0.034,  evN: 4, days: 2550 },
};
const ST_MO = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function studioDates(range) {
  const m = ST_RANGE[range],len = m.len,out = [];
  const today = new Date(2026, 5, 1); // Jun 1 2026
  for (let i = 0; i < len; i++) {
    const f = len === 1 ? 1 : i / (len - 1);
    if (range === "1D") {
      const d = new Date(2026, 5, 1, 9, 30); d.setMinutes(d.getMinutes() + Math.round(390 * f));
      const hh = d.getHours(),mm = d.getMinutes(),ap = hh >= 12 ? "PM" : "AM",h12 = (hh + 11) % 12 + 1;
      out.push(h12 + ":" + String(mm).padStart(2, "0") + " " + ap);
    } else {
      const d = new Date(today.getTime() - m.days * (1 - f) * 86400000);
      out.push(range === "5Y" || range === "All" ? ST_MO[d.getMonth()] + " '" + String(d.getFullYear()).slice(2) : ST_MO[d.getMonth()] + " " + d.getDate());
    }
  }
  return out;
}
/* forward-looking date labels (today → horizon end) for cash-rate projections */
const PROJ_LABEL = { "1D": "rest of today", "3M": "next 3 months", "6M": "next 6 months", "YTD": "rest of the year", "1Y": "next year", "5Y": "next 5 years", "All": "the long run" };
function projDates(range) {
  const m = ST_RANGE[range], len = m.len, out = [];
  const today = new Date(2026, 5, 1); // Jun 1 2026
  for (let i = 0; i < len; i++) {
    const f = len === 1 ? 1 : i / (len - 1);
    if (range === "1D") {
      const d = new Date(2026, 5, 1, 9, 30); d.setMinutes(d.getMinutes() + Math.round(390 * f));
      const hh = d.getHours(), mm = d.getMinutes(), ap = hh >= 12 ? "PM" : "AM", h12 = (hh + 11) % 12 + 1;
      out.push(h12 + ":" + String(mm).padStart(2, "0") + " " + ap);
    } else {
      const d = new Date(today.getTime() + m.days * f * 86400000);
      out.push(range === "5Y" || range === "All" ? ST_MO[d.getMonth()] + " '" + String(d.getFullYear()).slice(2) : ST_MO[d.getMonth()] + " " + d.getDate());
    }
  }
  return out;
}
function studioSeries(inst, range) {
  const m = ST_RANGE[range];
  const canon = genSeries(inst),last = canon[canon.length - 1];
  const rnd = mulberry32(inst.seed * 7 + m.len * 13 + range.length);
  const events = inst.events.slice(0, m.evN).map((e, k) => ({ ...e, i: Math.round((k + 1) * (m.len - 1) / (m.evN + 1)) }));
  let v = 1; const raw = [];
  for (let i = 0; i < m.len; i++) {
    v += (rnd() - 0.47) * m.vol * 2 * v;
    const ev = events.find((e) => e.i === i); if (ev) v *= 1 + ev.shock;
    raw.push(Math.max(v, 0.2));
  }
  const factor = last / raw[raw.length - 1];
  return { data: raw.map((x) => x * factor), events, dates: studioDates(range) };
}

/* a clean trend series ending exactly at endVal — for non-instrument metrics
   (cash balance, a single loan / total debt). Scrubbable across the same
   horizons as the Investments chart. totalPct = total % change over the window. */
const CASH_TREND = { "1D": 0.05, "3M": 1.1, "6M": 2.1, "YTD": 1.7, "1Y": 3.9, "5Y": 22, "All": 37 };
const DEBT_TREND = { "1D": -0.02, "3M": -2.3, "6M": -4.4, "YTD": -3.4, "1Y": -7.6, "5Y": -24, "All": -31 };
function trendSeries(seed, range, endVal, totalPct) {
  const m = ST_RANGE[range], len = m.len;
  const startVal = endVal / (1 + totalPct / 100);
  const rnd = mulberry32(seed * 7 + m.len * 13 + range.length);
  const arr = [];
  for (let i = 0; i < len; i++) {
    const t = len === 1 ? 1 : i / (len - 1);
    let v = startVal + (endVal - startVal) * t;
    v += (rnd() - 0.5) * v * m.vol;
    arr.push(Math.max(0, v));
  }
  arr[len - 1] = endVal;
  return arr;
}

/* build a chartable instrument from a market row (used by search + the vs compare) */
function marketToInst(mk) {
  let s = 0;for (let i = 0; i < mk.id.length; i++) s = s * 31 + mk.id.charCodeAt(i) >>> 0;
  return { id: mk.id, label: mk.ticker, seed: s + 5, base: mk.last * 0.85, unit: "$", events: [
    { i: 22, title: "Catalyst", note: "A scheduled update moved the price.", shock: mk.dch >= 0 ? 0.05 : -0.045 },
    { i: 52, title: "Sector move", note: "The broader sector swung and carried it along.", shock: -0.03 },
    { i: 76, title: "Momentum", note: "Flows and headlines drove a recovery.", shock: 0.045 }] };
}

/* ============================================================
   The analyze chart — drag across it to select a window.
   ============================================================ */
const AnalyzeChart = ({ data, events, sel, live, ma, bands, ohlc, candle = false, cmp = null, cmpColor = "var(--alloc-cash)", color = "var(--chart-line)", view, area = true, log = false, projFrom = null, onDown, onMove, onUp }) => {
  const W = 350,H = window.__YOSHI_WEB ? 132 : 168,padY = 14;
  const svgRef = useRef(null);
  const [lo, hi] = view && view[1] > view[0] ? view : [0, data.length - 1];
  const norm = !!cmp;                       // compare mode: index both series to 0% at the window start
  const tx = (v) => log && !norm ? Math.log(v) : v;
  const pct = (arr, i) => (arr[i] / arr[lo] - 1) * 100;
  const vis = [];
  if (norm) {
    for (let i = lo; i <= hi; i++) { vis.push(pct(data, i)); vis.push(pct(cmp, i)); }
    vis.push(0);
  } else {
    for (let i = lo; i <= hi; i++) vis.push(tx(data[i]));
    // include band extents in the y-domain so envelopes aren't clipped
    if (bands) for (let i = lo; i <= hi; i++) { vis.push(tx(bands.up[i])); vis.push(tx(bands.low[i])); }
    // include candle wicks so highs/lows aren't clipped
    if (candle && ohlc) for (let i = lo; i <= hi; i++) { vis.push(tx(ohlc[i].h)); vis.push(tx(ohlc[i].l)); }
  }
  const min = Math.min(...vis),max = Math.max(...vis);
  const range = max - min || 1;
  const span = hi - lo || 1;
  const x = (i) => (i - lo) / span * W;
  const y = (v) => padY + (1 - (tx(v) - min) / range) * (H - padY * 2);
  const yPct = (v) => padY + (1 - (v - min) / range) * (H - padY * 2);
  const line = (arr) => { let d = ""; for (let i = lo; i <= hi; i++) d += `${i === lo ? "M" : "L"}${x(i).toFixed(2)},${y(arr[i]).toFixed(2)}`; return d; };
  const lineSeg = (arr, a, b) => { let d = ""; for (let i = a; i <= b; i++) d += `${i === a ? "M" : "L"}${x(i).toFixed(2)},${y(arr[i]).toFixed(2)}`; return d; };
  const linePct = (arr) => { let d = ""; for (let i = lo; i <= hi; i++) d += `${i === lo ? "M" : "L"}${x(i).toFixed(2)},${yPct(pct(arr, i)).toFixed(2)}`; return d; };
  const path = norm ? "" : line(data);
  const lastX = x(hi),lastY = norm ? 0 : y(data[hi]);

  const idxFrom = (clientX) => {
    const r = svgRef.current.getBoundingClientRect();
    const rel = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
    return lo + Math.round(rel * span);
  };
  const band = live || sel;
  const clamp = (i) => Math.max(lo, Math.min(hi, i));
  // web: a looping demo hint that shows the drag-to-analyze gesture until the
  // user tries it themselves (or a selection exists)
  const [demo, setDemo] = useState(() => !!window.__YOSHI_WEB);
  const showDemo = demo && !band;
  // web: pull the grab bar under the chart to make it taller — one shared
  // height across all Studio tabs (persisted)
  const [chartH, setChartH] = useState(() => { try { const v = window.__YOSHI_WEB ? parseInt(localStorage.getItem("yoshi_web_studio_charth") || "0", 10) : 0; return v >= 110 && v <= 480 ? v : null; } catch (e) { return null; } });
  const hDrag = useRef(null);
  return (
    <div style={{ position: "relative" }}>
    {showDemo && <style>{`
      @keyframes yo-chart-demo-band { 0% { left: 14%; width: 0; opacity: 0; } 12% { opacity: 0.13; } 55% { left: 14%; width: 34%; opacity: 0.13; } 78% { opacity: 0; } 100% { left: 14%; width: 34%; opacity: 0; } }
    `}</style>}
    <svg ref={svgRef} width="100%" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none"
    style={{ display: "block", overflow: "visible", touchAction: "none", cursor: "crosshair", height: chartH || undefined }}
    onPointerDown={(e) => {setDemo(false);try {e.currentTarget.setPointerCapture(e.pointerId);} catch (_) {}onDown(idxFrom(e.clientX));}}
    onPointerMove={(e) => onMove(idxFrom(e.clientX))}
    onPointerUp={(e) => onUp(idxFrom(e.clientX))}
    onPointerCancel={() => onUp(null)}>
      {!norm && <line x1="0" y1={H - padY} x2={W} y2={H - padY} stroke="var(--rule)" strokeWidth="1" strokeDasharray="2 3" vectorEffect="non-scaling-stroke" />}
      {/* selection band */}
      {band &&
      <g style={{ pointerEvents: "none" }}>
          <rect x={x(clamp(Math.min(band[0], band[1])))} y="0" width={Math.abs(x(clamp(band[1])) - x(clamp(band[0])))} height={H} fill="var(--accent)" opacity="0.12" />
          <line x1={x(clamp(band[0]))} y1="0" x2={x(clamp(band[0]))} y2={H} stroke="var(--accent)" strokeWidth="1" opacity="0.5" vectorEffect="non-scaling-stroke" />
          <line x1={x(clamp(band[1]))} y1="0" x2={x(clamp(band[1]))} y2={H} stroke="var(--accent)" strokeWidth="1" opacity="0.5" vectorEffect="non-scaling-stroke" />
        </g>
      }
      {/* compare mode: two lines indexed to 0% at the window start */}
      {norm &&
      <g style={{ pointerEvents: "none" }}>
          <line x1="0" y1={yPct(0)} x2={W} y2={yPct(0)} stroke="var(--rule-2)" strokeWidth="1" strokeDasharray="2 3" vectorEffect="non-scaling-stroke" />
          <path d={linePct(cmp)} fill="none" stroke={cmpColor} strokeWidth="1.5" strokeDasharray="4 3" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
          <path d={linePct(data)} fill="none" stroke="var(--chart-line)" strokeWidth="1.75" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
          <circle cx={x(hi)} cy={yPct(pct(data, hi))} r="3" fill="var(--chart-line)" />
          <circle cx={x(hi)} cy={yPct(pct(cmp, hi))} r="3" fill={cmpColor} />
        </g>
      }
      {!norm && <>
        {bands &&
        <g style={{ pointerEvents: "none" }}>
          <path d={`${line(bands.up)} ${(() => { let d = ""; for (let i = hi; i >= lo; i--) d += `L${x(i).toFixed(2)},${y(bands.low[i]).toFixed(2)}`; return d; })()} Z`} fill="var(--accent)" opacity="0.08" />
          <path d={line(bands.up)} fill="none" stroke="var(--accent)" strokeWidth="1" strokeDasharray="1 3" opacity="0.45" vectorEffect="non-scaling-stroke" />
          <path d={line(bands.low)} fill="none" stroke="var(--accent)" strokeWidth="1" strokeDasharray="1 3" opacity="0.45" vectorEffect="non-scaling-stroke" />
        </g>
        }
        {area && !candle && <path d={`${path} L${W},${H} L0,${H} Z`} fill={color} opacity="0.12" style={{ pointerEvents: "none" }} />}
        {!candle && (projFrom != null && projFrom > lo && projFrom < hi ?
        <g style={{ pointerEvents: "none" }}>
          <path d={lineSeg(data, lo, projFrom)} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
          <path d={lineSeg(data, projFrom, hi)} fill="none" stroke={color} strokeWidth="1.5" strokeDasharray="3 3" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
          <circle cx={x(projFrom)} cy={y(data[projFrom])} r="3" fill="var(--bg)" stroke={color} strokeWidth="1.4" />
        </g> :
        <path d={path} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" vectorEffect="non-scaling-stroke" style={{ pointerEvents: "none" }} />)}
        {/* candlesticks */}
        {candle && ohlc &&
        <g style={{ pointerEvents: "none" }}>
          {(() => { const w = Math.max(1.2, Math.min(7, W / (span + 1) * 0.62)); const out = [];
          for (let i = lo; i <= hi; i++) { const d = ohlc[i],up = d.c >= d.o,col = up ? "var(--chart-line)" : "var(--signal-neg)",cx = x(i),top = y(Math.max(d.o, d.c)),bot = y(Math.min(d.o, d.c));
            out.push(<g key={i}>
              <line x1={cx} y1={y(d.h)} x2={cx} y2={y(d.l)} stroke={col} strokeWidth="1" vectorEffect="non-scaling-stroke" />
              <rect x={cx - w / 2} y={top} width={w} height={Math.max(1, bot - top)} fill={col} opacity={up ? 0.85 : 0.9} />
            </g>);
          }
          return out; })()}
        </g>
        }
        {ma && <path d={(() => { let d = ""; for (let i = lo; i <= hi; i++) d += `${i === lo ? "M" : "L"}${x(i).toFixed(2)},${y(ma[i]).toFixed(2)}`; return d; })()} fill="none" stroke="var(--accent)" strokeWidth="1.2" strokeDasharray="3 3" opacity="0.85" vectorEffect="non-scaling-stroke" style={{ pointerEvents: "none" }} />}
        {events.filter((e) => e.i >= lo && e.i <= hi).map((e, i) => {
          const inSel = sel && e.i >= Math.min(sel[0], sel[1]) && e.i <= Math.max(sel[0], sel[1]);
          return (
            <g key={i} style={{ pointerEvents: "none" }}>
              <circle cx={x(e.i)} cy={y(data[e.i])} r={window.__YOSHI_WEB ? 1.7 : 3} fill={inSel ? color : "var(--bg)"} stroke={color} strokeWidth={window.__YOSHI_WEB ? 1 : 1.4} />
            </g>);
        })}
        {!candle && <g style={{ pointerEvents: "none" }}>{window.__YOSHI_WEB && <circle cx={lastX} cy={lastY} r="7" fill={color} opacity="0.16" />}<circle cx={lastX} cy={lastY} r={window.__YOSHI_WEB ? 2.6 : 3} fill={color} /></g>}
      </>}
    </svg>
    {window.__YOSHI_WEB &&
    <div title="Drag to resize the chart"
      onPointerDown={(e) => { const r = svgRef.current ? svgRef.current.getBoundingClientRect() : null; hDrag.current = { y: e.clientY, h: chartH || (r ? r.height : 132) }; try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {} }}
      onPointerMove={(e) => { const d = hDrag.current; if (!d) return; const h = Math.max(110, Math.min(480, Math.round(d.h + (e.clientY - d.y)))); setChartH(h); try { localStorage.setItem("yoshi_web_studio_charth", String(h)); } catch (_) {} }}
      onPointerUp={(e) => { hDrag.current = null; }}
      onPointerCancel={() => { hDrag.current = null; }}
      style={{ display: "flex", justifyContent: "center", padding: "5px 0 1px", cursor: "ns-resize", touchAction: "none" }}>
      <span style={{ width: 44, height: 4, borderRadius: 999, background: "var(--rule-2)" }} />
    </div>}
    {showDemo && <>
      <div style={{ position: "absolute", top: 0, bottom: 0, pointerEvents: "none", background: "var(--accent)", borderLeft: "1px solid var(--accent)", borderRight: "1px solid var(--accent)", animation: "yo-chart-demo-band 4.5s ease-in-out infinite" }} />
      <div style={{ position: "absolute", left: "50%", bottom: 4, transform: "translateX(-50%)", pointerEvents: "none", display: "flex", alignItems: "center", gap: 6, background: "color-mix(in srgb, var(--bg) 90%, transparent)", border: "1px solid var(--rule-2)", borderRadius: 999, padding: "4px 11px", whiteSpace: "nowrap" }}>
        <Icon name="bulb" size={12} color="var(--accent)" stroke={1.7} />
        <span style={{ fontFamily: "var(--f-display)", fontSize: 10.5, fontWeight: 600, color: "var(--ink-2)" }}>Drag across a period — Yoshi turns it into an idea</span>
      </div>
    </>}
    </div>);

};

/* ============================================================
   Studio
   ============================================================ */
const MiniSpark = ({ seedStr, up, w = 116, h = 30 }) => {
  let s = 0;for (let i = 0; i < seedStr.length; i++) s = s * 31 + seedStr.charCodeAt(i) >>> 0;
  const rnd = mulberry32(s + 9);
  const n = 18;
  let v = 0.5;const pts = [];
  for (let i = 0; i < n; i++) {v += (rnd() - 0.5) * 0.16 + (up ? 0.014 : -0.014);v = Math.max(0.08, Math.min(0.92, v));pts.push(v);}
  const path = pts.map((p, i) => `${i === 0 ? "M" : "L"}${(i / (n - 1) * w).toFixed(1)},${(h - p * h).toFixed(1)}`).join(" ");
  const col = up ? "var(--chart-line)" : "var(--signal-neg)";
  return (
    <svg width="100%" height={h} viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ display: "block" }}>
      <path d={`${path} L${w},${h} L0,${h} Z`} fill={col} opacity="0.1" />
      <path d={path} fill="none" stroke={col} strokeWidth="1.5" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
    </svg>);

};

const InvestmentsView = ({ nav, proposals = [], onReview }) => {
  const [instId, setInstId] = useState("port");
  const [extra, setExtra] = useState([]);
  const [searchOpen, setSearchOpen] = useState(false);
  const [q, setQ] = useState("");
  const [showMA, setShowMA] = useState(false);
  const [showBands, setShowBands] = useState(false);
  const [area, setArea] = useState(true);
  const [log, setLog] = useState(false);
  const [chartType, setChartType] = useState("line");
  const [range, setRange] = useState("3M");
  const [advOpen, setAdvOpen] = useState(false);
  const [allocView, setAllocView] = useState("Asset class");
  const [allocOpen, setAllocOpen] = useState(false);
  const [cmpId, setCmpId] = useState(null);   // compare-against instrument id (null = off)
  const [vsOpen, setVsOpen] = useState(false);
  const [vsQ, setVsQ] = useState("");
  const allInsts = [...INSTRUMENTS, ...extra];
  const inst = allInsts.find((x) => x.id === instId) || INSTRUMENTS[0];
  const built = useMemo(() => studioSeries(inst, range), [instId, range]);
  const data = built.data;
  const dates = built.dates;
  const instEvents = built.events;
  const ma = useMemo(() => {
    if (!showMA) return null;
    const win = 7;
    return data.map((_, i) => {const s = Math.max(0, i - win + 1);const seg = data.slice(s, i + 1);return seg.reduce((a, b) => a + b, 0) / seg.length;});
  }, [data, showMA]);
  const bands = useMemo(() => {
    if (!showBands) return null;
    const win = 20,up = [],low = [];
    for (let i = 0; i < data.length; i++) {
      const s = Math.max(0, i - win + 1),seg = data.slice(s, i + 1);
      const m = seg.reduce((a, b) => a + b, 0) / seg.length;
      const sd = Math.sqrt(seg.reduce((a, b) => a + (b - m) ** 2, 0) / seg.length);
      up.push(m + 2 * sd);low.push(m - 2 * sd);
    }
    return { up, low };
  }, [data, showBands]);
  // synthetic OHLC for the candlestick view (the series gives closes; derive plausible wicks)
  const ohlc = useMemo(() => {
    if (chartType !== "candle") return null;
    const span = (Math.max(...data) - Math.min(...data)) || 1;
    return data.map((c, i) => {
      const o = i > 0 ? data[i - 1] : c;
      const r1 = Math.abs(Math.sin((i + 1) * 12.9898)) % 1;
      const r2 = Math.abs(Math.sin((i + 1) * 78.233)) % 1;
      const ext = Math.max(Math.abs(c - o) * 0.6, span * 0.012);
      return { o, c, h: Math.max(o, c) + ext * (0.35 + r1), l: Math.min(o, c) - ext * (0.35 + r2) };
    });
  }, [data, chartType]);
  // the whole series is the selected horizon — chart renders it in full
  const view = useMemo(() => [0, data.length - 1], [data.length]);
  const addFromMarket = (mk) => {
    if (!allInsts.find((x) => x.id === mk.id)) setExtra((e) => [...e, marketToInst(mk)]);
    setInstId(mk.id);setSearchOpen(false);setQ("");
  };
  // the comparison series (indexed alongside the primary), if one is picked
  const cmpData = useMemo(() => {
    if (!cmpId || cmpId === instId) return null;
    const found = allInsts.find((x) => x.id === cmpId);
    const mk = MARKET.find((m) => m.id === cmpId);
    const ci = found || (mk ? marketToInst(mk) : null);
    if (!ci) return null;
    return { data: studioSeries(ci, range).data, label: ci.label, unit: ci.unit, id: ci.id };
  }, [cmpId, range, instId, extra]);
  const [sel, setSel] = useState(null); // committed [a,b]
  const [live, setLive] = useState(null); // during drag [a,b]
  const dragStart = useRef(null);

  const onDown = (i) => {dragStart.current = i;setLive([i, i]);};
  const onMove = (i) => {if (dragStart.current != null && i != null) setLive([dragStart.current, i]);};
  const onUp = (i) => {
    const a = dragStart.current;
    dragStart.current = null;setLive(null);
    if (a == null || i == null) return;
    const lo = Math.min(a, i),hi = Math.max(a, i);
    if (hi - lo < 3) {setSel(null);return;} // too short → clear
    setSel([lo, hi]);
  };
  // reset selection when instrument changes
  useEffect(() => {setSel(null);setLive(null);}, [instId, range]);

  const analysis = useMemo(() => {
    if (!sel) return null;
    const [a, b] = sel;
    const startV = data[a],endV = data[b];
    const pct = (endV / startV - 1) * 100;
    const evs = instEvents.filter((e) => e.i >= a && e.i <= b);
    return { a, b, pct, evs, startV, endV };
  }, [sel, instId, range]);

  const last = data[data.length - 1];
  const dayPct = (last / data[data.length - 8] - 1) * 100;

  // movers for the pulse card
  const movers = useMemo(() => [...MARKET].sort((p, q) => q.dch - p.dch), []);
  const up = movers.slice(0, 2),down = movers.slice(-2).reverse();
  // watchlist mixes gainers and losers, with a loser up front so it isn't all green
  const watch = useMemo(() => {
    const n = movers.length;
    return [movers[0], movers[n - 1], movers[1], movers[2], movers[3], movers[n - 2]];
  }, [movers]);
  // Top holdings, resolved to tradeable market instruments and de-duped by
  // ticker, so paper / linked positions (e.g. "pp-tsla") map to the real
  // market id ("tsla") instead of an id the trade sheet can't open.
  const topHoldings = useMemo(() => {
    const byId = {};
    for (const h of [...HOLDINGS].sort((a, b) => b.value - a.value)) {
      const m = MARKET.find((x) => x.ticker === h.ticker);
      if (!m) continue;
      if (!byId[m.id]) byId[m.id] = { id: m.id, ticker: m.ticker, name: m.name, dch: m.dch, value: 0 };
      byId[m.id].value += h.value;
    }
    return Object.values(byId).sort((a, b) => b.value - a.value).slice(0, 5);
  }, []);
  const ALLOC_VIEWS = {
    "Asset class": [
      ["US equity", 48, "var(--accent-4)"],
      ["Intl equity", 14, "var(--alloc-cash)"],
      ["Crypto", 12, "var(--accent)"],
      ["Bonds & Treasuries", 9, "var(--accent-2)"],
      ["Cash", 17, "var(--rule-2)"],
    ],
    "Industry": [
      ["Technology", 37, "var(--accent-4)"],
      ["Financials", 15, "var(--alloc-cash)"],
      ["Healthcare", 12, "var(--accent-2)"],
      ["Consumer", 11, "var(--accent)"],
      ["Industrials & Energy", 8, "var(--alloc-invest)"],
      ["Other & Cash", 17, "var(--rule-2)"],
    ],
    "Geography": [
      ["United States", 71, "var(--accent-4)"],
      ["Europe", 12, "var(--alloc-cash)"],
      ["Asia-Pacific", 10, "var(--accent-2)"],
      ["Emerging markets", 7, "var(--rule-2)"],
    ],
  };
  const ALLOC = ALLOC_VIEWS[allocView];

  return (
    <>
        {/* ---- ANALYZE (the hero) — first ---- */}
        <section style={{ padding: "16px 18px 6px" }}>

          {searchOpen &&
        <div style={{ marginTop: 12 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "9px 12px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 10 }}>
                <Icon name="search" size={15} color="var(--ink-3)" stroke={1.5} />
                <input autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search a ticker to analyze…" style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13.5 }} />
                <button className="press" onClick={() => {setSearchOpen(false);setQ("");}} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", padding: 0 }}><Icon name="close" size={15} /></button>
              </div>
              <div style={{ marginTop: 6 }}>
                {MARKET.filter((m) => (m.ticker + " " + m.name).toLowerCase().includes(q.trim().toLowerCase())).slice(0, 6).map((m) =>
            <button key={m.id} className="press" onClick={() => addFromMarket(m)} style={{ width: "100%", textAlign: "left", cursor: "pointer", display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 10, alignItems: "center", background: "none", border: "none", borderBottom: "1px solid var(--rule)", padding: "10px 2px" }}>
                    <span style={{ fontFamily: "var(--f-mono)", fontSize: 12.5, fontWeight: 600, color: "var(--accent)" }}>{m.ticker}</span>
                    <span style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{m.name}</span>
                    <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, color: m.dch >= 0 ? "var(--accent-pos)" : "var(--signal-neg)" }}>{m.dch >= 0 ? "+" : "−"}{Math.abs(m.dch).toFixed(2)}%</span>
                  </button>
            )}
              </div>
            </div>
        }

          {/* instrument selector + tools */}
          <div style={{ display: "flex", gap: 6, marginTop: 12, overflowX: "auto", alignItems: "center" }} className="hcar">
            {allInsts.map((o) => {
            const on = o.id === instId;
            return (
              <button key={o.id} className="press" onClick={() => setInstId(o.id)} style={{
                flex: "none", padding: "6px 12px", borderRadius: 999, cursor: "pointer",
                background: on ? "var(--ink)" : "color-mix(in srgb, var(--ink) 8%, var(--bg-2))", color: on ? "var(--bg)" : "var(--ink-2)",
                border: "none",
                fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600
              }}>{o.label}</button>);

          })}
          </div>

          <div style={{ display: "flex", flexDirection: window.__YOSHI_WEB ? "column-reverse" : "column" }}>
          <div>
          {/* chart tools — compare + indicators */}
          <div style={{ marginTop: 12 }}>
            <div style={{ display: "flex", gap: 8, alignItems: "center", justifyContent: "flex-start" }}>
              <button className="press" onClick={() => setVsOpen((o) => !o)} aria-label="Compare" title="Compare against another asset" style={window.__YOSHI_WEB
                ? { display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 9px", borderRadius: 8, background: cmpData ? "color-mix(in srgb, var(--accent) 14%, transparent)" : "none", border: "1px solid " + (cmpData || vsOpen ? "var(--accent)" : "var(--rule-2)"), cursor: "pointer", color: cmpData || vsOpen ? "var(--accent)" : "var(--ink-2)" }
                : { display: "inline-flex", alignItems: "center", gap: 5, padding: "4px 2px", background: "none", border: "none", cursor: "pointer", color: cmpData || vsOpen ? "var(--accent)" : "var(--ink-3)" }}>
                <Icon name="swap" size={window.__YOSHI_WEB ? 15 : 18} stroke={1.6} />
                {window.__YOSHI_WEB && <span style={{ fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600 }}>Compare</span>}
              </button>
            {(() => { const n = (showMA ? 1 : 0) + (showBands ? 1 : 0) + (chartType === "candle" ? 1 : 0); const active = advOpen || n > 0;
            return (
              <button className="press" onClick={() => setAdvOpen((o) => !o)} aria-label="Indicators" title="Indicators" style={window.__YOSHI_WEB
                ? { display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 9px", borderRadius: 8, background: "none", border: "1px solid " + (active ? "var(--accent)" : "var(--rule-2)"), cursor: "pointer", color: active ? "var(--accent)" : "var(--ink-2)" }
                : { display: "inline-flex", alignItems: "center", gap: 4, padding: "4px 2px", background: "none", border: "none", cursor: "pointer", color: active ? "var(--accent)" : "var(--ink-3)" }}>
                <Icon name="bulb" size={window.__YOSHI_WEB ? 15 : 18} stroke={1.7} />
                {window.__YOSHI_WEB && <span style={{ fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600 }}>Indicators</span>}
                {n > 0 && <span style={{ fontFamily: "var(--f-mono)", fontSize: 11, fontWeight: 600, color: "var(--accent)" }}>{n}</span>}
              </button>);
            })()}
            </div>
          </div>

          {/* vs compare picker */}
          {vsOpen &&
          <div style={{ marginTop: 8, border: "1px solid var(--rule-2)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)", maxHeight: 274, display: "flex", flexDirection: "column" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "9px 13px", borderBottom: "1px solid var(--rule)", flex: "none" }}>
              <Icon name="search" size={14} color="var(--ink-3)" stroke={1.6} />
              <input autoFocus value={vsQ} onChange={(e) => setVsQ(e.target.value)} placeholder={`Compare ${inst.label} against…`}
                style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13 }} />
              {vsQ && <button className="press" onClick={() => setVsQ("")} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", padding: 0, cursor: "pointer" }}><Icon name="close" size={14} /></button>}
            </div>
            <div style={{ overflowY: "auto" }}>
            {(() => {
              const base = [...allInsts.filter((o) => o.id !== instId).map((o) => ({ id: o.id, label: o.label, sub: "" })),
                ...["vti", "voo", "spy", "qqq", "amd", "tsla"].map((id) => MARKET.find((m) => m.id === id)).filter((m) => m && m.id !== instId && !allInsts.find((a) => a.id === m.id)).map((m) => ({ id: m.id, label: m.ticker, sub: m.name }))];
              const qq = vsQ.trim().toLowerCase();
              // typing widens the search to the whole market, not just the shortlist
              const pool = qq
                ? [...base, ...MARKET.filter((m) => m.id !== instId && !base.find((b) => b.id === m.id)).map((m) => ({ id: m.id, label: m.ticker, sub: m.name }))]
                    .filter((o) => (o.label + " " + o.sub).toLowerCase().includes(qq))
                : base;
              if (!pool.length) return <div style={{ padding: "14px 13px", fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-3)" }}>No matches for “{vsQ.trim()}”.</div>;
              return pool.map((o, i) => {
              const on = cmpId === o.id;
              return (
                <button key={o.id} className="press" onClick={() => { setCmpId(on ? null : o.id); setVsOpen(false); setVsQ(""); }} style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "9px 13px", background: on ? "var(--bg-2)" : "none", border: "none", borderTop: i ? "1px solid var(--rule)" : "none", cursor: "pointer", textAlign: "left" }}>
                  <span style={{ width: 9, height: 9, borderRadius: 2, background: on ? "var(--alloc-cash)" : "transparent", border: on ? "none" : "1px solid var(--rule-2)", flex: "none" }} />
                  <span style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600 }}>{o.label}</span>
                  {o.sub && <span style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{o.sub}</span>}
                  {on && <span style={{ marginLeft: "auto", fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, color: "var(--ink-3)" }}>Clear</span>}
                </button>);
              });
            })()}
            </div>
          </div>
          }

          {advOpen &&
          <div style={{ marginTop: 8, border: "1px solid var(--rule-2)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
            {[
            ["Moving average", "7-day trend line", showMA, () => setShowMA((v) => !v)],
            ["Bollinger bands", "20-period SMA, ±2σ envelope", showBands, () => setShowBands((v) => !v)],
            ["Candlesticks", "Open / high / low / close bars", chartType === "candle", () => setChartType((t) => t === "candle" ? "line" : "candle")]].
            map(([t, d, on, fn], i) =>
            <button key={t} className="press" onClick={fn} style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "10px 13px", background: "none", border: "none", borderTop: i ? "1px solid var(--rule)" : "none", cursor: "pointer", textAlign: "left" }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, fontWeight: 600, color: "var(--ink)" }}>{t}</div>
                  <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 3 }}>{d}</div>
                </div>
                <span style={{ width: 34, height: 20, borderRadius: 999, background: on ? "var(--accent)" : "var(--rule-2)", position: "relative", flex: "none", transition: "background .15s" }}>
                  <span style={{ position: "absolute", top: 2, left: on ? 16 : 2, width: 16, height: 16, borderRadius: 999, background: "var(--bg)", transition: "left .15s", boxShadow: "0 1px 2px rgba(0,0,0,0.2)" }} />
                </span>
              </button>
            )}
          </div>
          }
          </div>
          {(() => {
            const pe = (arr) => (arr[arr.length - 1] / arr[0] - 1) * 100;
            const idxFmt = (v) => v.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
            const Series = ({ color, dash, label, num, unit, isPort, chg, big, hideVal }) => {
              const isIdx = unit === "" && !isPort;
              const valStr = isIdx ? idxFmt(num) : usd(num, 2);
              const perf = num - num / (1 + chg / 100);
              const perfStr = (perf >= 0 ? "+" : "−") + (isIdx ? Math.abs(perf).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : usd(Math.abs(perf), 2));
              return (
                <div style={{ display: "flex", alignItems: "flex-start", gap: 8 }}>
                  {!hideVal &&
                  <span style={{ display: "flex", flexDirection: "column", alignItems: "flex-start" }}>
                    <span style={{ fontFamily: "var(--f-mono)", fontSize: big ? 17 : 13.5, fontWeight: 500, color: "var(--ink)", fontVariantNumeric: "tabular-nums", letterSpacing: "-0.01em" }}>{valStr}</span>
                    <span style={{ fontFamily: "var(--f-mono)", fontSize: 11.5, color: chg >= 0 ? "var(--accent-pos)" : "var(--signal-neg)", marginTop: 2 }}>{perfStr} ({Math.abs(chg).toFixed(1)}%)</span>
                  </span>}
                  <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 7, marginTop: big ? 3 : 2 }}>
                    <span style={{ width: 16, height: 0, borderTop: `2px ${dash ? "dashed" : "solid"} ${color}`, flex: "none" }} />
                    <span style={{ fontFamily: "var(--f-display)", fontSize: big ? 13 : 12, fontWeight: 600 }}>{label}</span>
                  </span>
                </div>
              );
            };
            return (
              <div style={{ marginTop: 12, display: "flex", flexDirection: "column" }}>
                <Series color="var(--chart-line)" label={inst.label} num={inst.id === "port" ? INVEST_TOTAL + CRYPTO_TOTAL : last} unit={inst.unit} isPort={inst.id === "port"} chg={pe(data)} big />
                {cmpData && <div style={{ marginTop: -18 }}><Series color="var(--alloc-cash)" dash label={cmpData.label} num={cmpData.data[cmpData.data.length - 1]} unit={cmpData.unit} isPort={cmpData.id === "port"} chg={pe(cmpData.data)} hideVal /></div>}
              </div>);
          })()}
          </div>

          <div style={{ marginTop: 14 }}>
            <AnalyzeChart data={data} events={instEvents} sel={sel} live={live} ma={ma} bands={bands} ohlc={ohlc} candle={chartType === "candle"} cmp={cmpData ? cmpData.data : null} cmpColor="var(--alloc-cash)" view={view} area={area} log={log} onDown={onDown} onMove={onMove} onUp={onUp} />
          </div>

          {/* timeframe — below the chart */}
          <div style={{ marginTop: 12 }}>
            <div style={{ display: "flex", gap: 2 }}>
              {STUDIO_RANGES.map((k) => {
                const on = range === k;
                return <button key={k} className="press" onClick={() => setRange(k)} style={{ flex: 1, padding: "5px 0", borderRadius: 8, background: on ? "var(--bg-2)" : "transparent", color: on ? "var(--ink)" : "var(--ink-3)", border: "none", fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: on ? 700 : 600, cursor: "pointer", textAlign: "center" }}>{k}</button>;
              })}
            </div>
          </div>

          {/* analysis result OR hint */}
          {analysis ?
        <div style={{ marginTop: 14, border: "1px solid var(--accent)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)", position: "relative" }}>
              <div style={{ position: "absolute", top: 0, left: 0, right: 0, height: 2, background: "var(--accent)" }} />
              <div style={{ padding: "13px 14px" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                  <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", color: "var(--ink-3)" }}>{dates[analysis.a]} – {dates[analysis.b]}</span>
                  <button className="press" onClick={() => setSel(null)} style={{ marginLeft: "auto", background: "none", border: "none", color: "var(--ink-3)", display: "flex", cursor: "pointer" }}><Icon name="close" size={15} /></button>
                </div>
                <p style={{ fontFamily: "var(--f-display)", fontSize: 14, lineHeight: 1.5, margin: "9px 0 0", color: "var(--ink)" }}>
                  Over this window {inst.label} {analysis.pct >= 0 ? "rose" : "fell"} <span style={{ fontFamily: "var(--f-mono)", color: analysis.pct >= 0 ? "var(--accent-pos)" : "var(--signal-neg)" }}>{analysis.pct >= 0 ? "+" : "−"}{Math.abs(analysis.pct).toFixed(1)}%</span>.{" "}
                  {analysis.evs.length ? `${analysis.evs.length} catalyst${analysis.evs.length > 1 ? "s" : ""} stood out:` : "A quiet stretch with no major catalysts, mostly just drift."}
                </p>
                {analysis.evs.map((e, i) =>
            <div key={i} style={{ display: "flex", gap: 9, marginTop: 11, paddingTop: 11, borderTop: "1px dashed var(--rule)" }}>
                    <span style={{ width: 5, height: 5, borderRadius: 999, background: e.shock >= 0 ? "var(--accent-pos)" : "var(--signal-neg)", marginTop: 5, flex: "none" }} />
                    <div>
                      <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, fontWeight: 600 }}>{e.title} <span style={{ color: "var(--ink-3)", fontWeight: 400 }}>· {dates[e.i]}</span></div>
                      <div style={{ fontFamily: "var(--f-display)", fontSize: 12, color: "var(--ink-2)", marginTop: 2, lineHeight: 1.45 }}>{e.note}</div>
                    </div>
                  </div>
            )}
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginTop: 14 }}>
                  <Btn onClick={() => nav.sheet({ type: "trade", id: instId === "spx" || instId === "port" ? "vti" : instId })} style={{ padding: "10px", fontSize: 12.5 }}>Build a trade</Btn>
                  <Btn kind="ghost" onClick={() => draftThread(nav, `Rule from ${inst.label}`, `Make a rule from ${inst.label} ${dates[analysis.a]}–${dates[analysis.b]}`, `Got it. I can turn this pattern into a standing rule, like "buy ${inst.label} when it drops more than 4% in a week, within your caps." Want me to set that up?`)} style={{ padding: "10px", fontSize: 12.5 }}>Draft an automation</Btn>
                </div>
              </div>
            </div> :
        null
        }
        </section>

        {/* ---- WATCHLIST (cool cards) ---- */}
        {/* ---- TODAY'S READ + TOP HOLDINGS ---- */}
        <section style={{ padding: "6px 0 8px", marginTop: 2 }}>
          <StudioReadCard nav={nav}
            text={<>Your investments are <span style={{ fontWeight: 700, color: DAY_PCT >= 0 ? "var(--accent-pos)" : "var(--signal-neg)" }}>{DAY_PCT >= 0 ? "up" : "down"} {pct(DAY_PCT)}</span> today, led by NVDA. It's grown to ~18% of your book, concentrated enough to consider trimming. And ~$15k of idle cash could be earning about 5.1%.</>}
            q={["What's going on in my portfolio today?", `Quick read: your investments are ${DAY_PCT >= 0 ? "up" : "down"} ${pct(DAY_PCT)} today, led by NVDA. NVDA has grown to about 18% of your book, concentrated enough that trimming a slice into a broad fund would lower single-name risk. Separately, ~$15,000 of cash is sitting idle and could earn about 5.1% in short-term Treasuries. Want me to draft either move for you to approve?`]} />

          {/* Top holdings + Allocation — two columns on web, stacked on mobile */}
          <div style={window.__YOSHI_WEB ? { display: "grid", gridTemplateColumns: "1fr 1fr", alignItems: "start", marginTop: 12, borderTop: "1px solid var(--rule)" } : undefined}>
          <div>
          <button className="press" onClick={() => nav.push({ type: "account", acct: window.ALL_HOLDINGS_ACCT || window.ACCOUNTS.brokerage })} style={{ width: "100%", background: "none", border: "none", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "20px 18px 12px", cursor: "pointer", textAlign: "left" }}>
            <Eyebrow>Top holdings</Eyebrow>
            <Icon name="back" size={16} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
          </button>
          <div style={{ padding: "0 18px 2px" }}>
            {topHoldings.map((w, i) => {
              const dayAbs = w.value * w.dch / 100;
              const up = w.dch >= 0;
              return (
                <button key={w.id} className="press" onClick={() => nav.sheet({ type: "trade", id: w.id })} style={{ width: "100%", display: "grid", gridTemplateColumns: "1fr auto", gap: 12, alignItems: "center", background: "none", border: "none", borderBottom: i === topHoldings.length - 1 ? "none" : "1px solid var(--rule)", padding: "11px 0", cursor: "pointer", textAlign: "left" }}>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 700, letterSpacing: "-0.01em" }}>{w.ticker}</div>
                    <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{w.name}</div>
                  </div>
                  <div style={{ textAlign: "right", whiteSpace: "nowrap" }}>
                    <div style={{ fontFamily: "var(--f-mono)", fontSize: 13, fontVariantNumeric: "tabular-nums", color: "var(--ink)" }}>{usd(w.value, 2)}</div>
                    <div style={{ fontFamily: "var(--f-mono)", fontSize: 10.5, fontVariantNumeric: "tabular-nums", marginTop: 3, color: up ? "var(--accent-pos)" : "var(--signal-neg)" }}>{up ? "+" : "−"}{usd(Math.abs(dayAbs), 2)} ({Math.abs(w.dch).toFixed(1)}%)</div>
                  </div>
                </button>
              );
            })}
          </div>
          </div>

        {/* ---- ASSET ALLOCATION ---- */}
        <div style={window.__YOSHI_WEB ? { borderLeft: "1px solid var(--rule)", padding: "20px 18px 6px", alignSelf: "stretch", height: "100%", boxSizing: "border-box" } : { padding: "22px 18px 6px", marginTop: 10, borderTop: "1px solid var(--rule)" }}>
          <Eyebrow style={{ margin: 0 }}>Portfolio allocation</Eyebrow>
          <div style={{ display: "flex", gap: 7, marginTop: 10, flexWrap: "wrap" }}>
            {Object.keys(ALLOC_VIEWS).map((k) => {
              const on = k === allocView;
              return (
                <button key={k} className="press" onClick={() => setAllocView(k)} style={{ flex: "none", padding: "7px 13px", borderRadius: 999, cursor: "pointer",
                  background: on ? (window.__YOSHI_WEB ? "var(--ink)" : "var(--accent)") : "color-mix(in srgb, var(--ink) 8%, var(--bg-2))",
                  border: "none",
                  color: on ? (window.__YOSHI_WEB ? "var(--bg)" : "var(--accent-ink)") : "var(--ink-2)",
                  fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600 }}>{k}</button>);
            })}
          </div>
          <div style={{ marginTop: 12 }}>
            {ALLOC.map(([name, p, col]) =>
            <div key={name} style={{ marginBottom: 10 }}>
                <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
                  <span style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600 }}>{name}</span>
                  <span style={{ marginLeft: "auto", fontFamily: "var(--f-mono)", fontSize: 12, color: "var(--ink-2)", fontVariantNumeric: "tabular-nums" }}>{p}%</span>
                </div>
                <div style={{ height: 5, background: "var(--bg-2)", marginTop: 5 }}>
                  <div style={{ width: p + "%", height: "100%", background: col }} />
                </div>
              </div>
            )}
          </div>
        </div>
        </div>
        </section>

        {/* TAP TO ACT + BUILD — on web these live in the right "Act" pillar (StudioActPanel). */}
        {!window.__YOSHI_WEB && <StudioActPanel view="investments" nav={nav} proposals={proposals} onReview={onReview} />}
    </>);

};

/* "Draft an automation" — on web it forks its own thread in the left chat
   pillar (like Needs-you reviews); on mobile it drops into the main chat. */
const draftThread = (nav, title, q, a) => window.__YOSHI_WEB ? nav.sheet({ type: "draft", title, q, a }) : nav.ask(q, a);

/* ---- a fresh Yoshi chat composer (used in Set a rule) -------------------- */
const RuleComposer = ({ nav, prompts }) => {
  const [draft, setDraft] = useState("");
  const ask = (text) => {
    const t = (text != null ? text : draft).trim();
    if (!t) return;
    nav.ask(t.charAt(0).toUpperCase() + t.slice(1), `Sure. I'll set up "${t}" as a standing automation within your caps and show you the first run before it executes. Want it on?`);
  };
  return (
    <div style={{ marginTop: 13, display: "flex", flexDirection: "column" }}>
      <div style={{ order: window.__YOSHI_WEB ? 2 : 1 }}>
        <YoshiComposer value={draft} onChange={setDraft} onSend={() => ask()} placeholder="Talk to Yoshi…"
          onAttach={(doc) => nav.ask(`I attached ${doc.name}.`, attachReplyFor(doc))} />
      </div>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: window.__YOSHI_WEB ? 0 : 11, marginBottom: window.__YOSHI_WEB ? 11 : 0, order: window.__YOSHI_WEB ? 1 : 2 }}>
        {prompts.map((p) =>
        <button key={p} className="press" onClick={() => setDraft(p)} style={{ padding: "8px 12px", borderRadius: 999, cursor: "pointer", textAlign: "left", background: "transparent", border: "1px solid var(--rule-2)", fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 500, color: "var(--ink-2)" }}>{p}</button>
        )}
      </div>
    </div>);

};

/* ---- News · headlines (shared) ------------------------------------------- */
const MarketsNews = ({ nav, items, title = "News", sub = "" }) =>
<section style={{ padding: "22px 18px 6px", marginTop: 10, borderTop: "1px solid var(--rule)" }}>
    <span style={{ fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, letterSpacing: "-0.015em" }}>{title}</span>
    {sub && <p style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", margin: "5px 0 0", lineHeight: 1.5 }}>{sub}</p>}
    <div style={{ marginTop: 12 }}>
      {items.map(([src, head, time], i) =>
    <button key={i} className="press" onClick={() => nav.ask(`What does "${head}" mean for me?`, `The short version of "${head}" is that it's a market-level move, not something inside your accounts. Want me to pull the holdings it touches, or set an alert?`)} style={{
      width: "100%", textAlign: "left", cursor: "pointer", display: "grid", gridTemplateColumns: "1fr auto", gap: 10, alignItems: "start",
      background: "none", border: "none", borderBottom: i === items.length - 1 ? "none" : "1px solid var(--rule)", padding: "11px 0"
    }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 9.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", color: "var(--ink-3)" }}>{src}</div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 500, marginTop: 3, lineHeight: 1.35 }}>{head}</div>
          </div>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 10, color: "var(--ink-3)", whiteSpace: "nowrap", marginTop: 2 }}>{time}</span>
        </button>
    )}
    </div>
  </section>;


const StudioRead = ({ text }) =>
<section style={{ padding: "14px 18px 4px" }}>
    <Eyebrow color="var(--accent)">Today's read</Eyebrow>
    <p style={{ fontFamily: "var(--f-display)", fontSize: 14, lineHeight: 1.5, color: "var(--ink)", margin: "9px 0 0" }}>{text}</p>
  </section>;


const StudioTools = ({ nav, title, sub, items }) =>
<section style={{ padding: "22px 18px 26px", marginTop: 10, borderTop: "1px solid var(--rule)" }}>
    <span style={{ fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, letterSpacing: "-0.015em" }}>{title}</span>
    {sub && <p style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", margin: "5px 0 0", lineHeight: 1.5 }}>{sub}</p>}
    <div style={{ marginTop: 13, display: "flex", flexDirection: "column", gap: 9 }}>
      {items.map((it) =>
    <button key={it.line} className="press" onClick={() => nav.ask(it.q[0], it.q[1])} style={{
      width: "100%", textAlign: "left", cursor: "pointer", display: "grid", gridTemplateColumns: "1fr auto", gap: 12, alignItems: "center",
      background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: 12, padding: "12px 14px"
    }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 600 }}>{it.line}</div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", marginTop: 3 }}>{it.note}</div>
          </div>
          <Icon name="back" size={15} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
        </button>
    )}
    </div>
  </section>;

/* "Tap to act" — the pending "needs you" approvals that belong to this Studio
   tab's category. Each row taps straight into that proposal's review. */
const StudioNeedsYou = ({ proposals = [], cat, label, onReview, onRepin }) => {
  const items = proposals.filter((p) => p.cat === cat);
  if (!items.length) return null; // nothing pending — the box disappears entirely
  return (
    <section style={{ padding: "22px 18px 26px", marginTop: window.__YOSHI_WEB ? 0 : 10, borderTop: window.__YOSHI_WEB ? "none" : "1px solid var(--rule)" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <span style={{ fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, letterSpacing: "-0.015em" }}>Proposals</span>
        {onRepin && <button className="press" onClick={onRepin} aria-label="Pin to the side" title="Pin to the side" style={{ marginLeft: "auto", flex: "none", width: 28, height: 28, borderRadius: 999, background: "none", border: "none", display: "grid", placeItems: "center", color: "var(--ink-2)", cursor: "pointer", padding: 0 }}><Icon name="external" size={14} /></button>}
      </div>
      <p style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", margin: "5px 0 0", lineHeight: 1.5 }}>Pending approvals in {label.toLowerCase()}. Yoshi acts only after you approve.</p>
      <div style={{ marginTop: 13, display: "flex", flexDirection: "column", gap: 9 }}>
        {items.map((p) => {
          const yoshi = /^yoshi/i.test(p.agent);
          return (
            <button key={p.id} className="press" onClick={() => onReview(p.id)} style={{
              width: "100%", textAlign: "left", cursor: "pointer", display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 11, alignItems: "center",
              background: "var(--bg-card)", border: window.__YOSHI_WEB ? "1px solid var(--rule-2)" : "1px solid var(--accent)", borderRadius: 12, padding: "12px 14px"
            }}>
              <span style={{ width: 6, height: 6, borderRadius: 999, flex: "none", background: "var(--accent)" }} />
              <div style={{ minWidth: 0 }}>
                <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{p.title}</div>
                <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", marginTop: 3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{p.agent} · {p.settlesVerb || "settles"} {p.settles}</div>
              </div>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 8, whiteSpace: "nowrap", flex: "none" }}>
                <Money value={p.net} size={12.5} sign color={p.net >= 0 ? "var(--accent-pos)" : "var(--ink)"} dim="var(--ink-3)" />
                <Icon name="back" size={15} color="var(--ink-3)" style={{ transform: "scaleX(-1)" }} />
              </span>
            </button>);
        })}
      </div>
    </section>);

};

/* per-view "act" config — the Tap-to-act category + the Build-with-Yoshi prompts. */
const STUDIO_ACT = {
  investments: { cat: "investments", label: "Investments", sub: "Set up an automation in plain English.", prompts: ["Buy the dip when SPX drops 3% in a week", "Trim NVDA if it's up over 30% this quarter", "Sweep gains over $1k into Treasuries"] },
  cash: { cat: "cash", label: "Cash", sub: "Set up an automation in plain English.", prompts: ["Sweep idle Checking over $15k into Treasuries", "Keep $5k in Checking, move the rest to Savings", "Buy a 3-month T-bill every payday"] },
  debt: { cat: "debt", label: "Debt", sub: "Ask a question or set up an automation in plain English.", prompts: ["Should I refinance my mortgage?", "Pay both cards in full every month", "Put $400 extra toward the highest-APR card"] },
};

/* the accounts that feed each Studio tab — mirrors the Accounts tab (the SoT):
   Yoshi-held first, externally linked after. Values come from the same
   holdings/totals the Accounts list uses. */
const sumAcctIds = (ids) => ids.reduce((s, id) => { const h = HOLDINGS.find((x) => x.id === id); return s + (h ? h.value : 0); }, 0);
const studioAccountsFor = (view) => {
  const A = window.ACCOUNTS || {};
  if (view === "cash") return [
    { name: "Cash", sub: "Yoshi ••8841", value: CASH_TOTAL, acct: A.cash },
    { name: "Chase", sub: "Checking ••4417", value: 8210.32, external: true, acct: A.chase },
  ];
  if (view === "debt") return (LIABILITIES || []).map((d) => ({ name: d.name, sub: d.sub + " · " + d.apr, value: d.value, external: true }));
  return [
    { name: "Brokerage", sub: "Yoshi · Individual Taxable ••2208", value: A.brokerage ? sumAcctIds(A.brokerage.ids) : 0, acct: A.brokerage },
    { name: "Roth IRA", sub: "Yoshi · Retirement ••7731", value: A.roth ? sumAcctIds(A.roth.ids) : 0, acct: A.roth },
    { name: "Crypto", sub: "Yoshi · Spot ••6614", value: CRYPTO_TOTAL, acct: A.crypto },
    { name: "Schwab", sub: "Brokerage ••5520", value: A.schwab ? sumAcctIds(A.schwab.ids) : 0, external: true, acct: A.schwab },
    { name: "Coinbase", sub: "Crypto ••3097", value: A.coinbase ? sumAcctIds(A.coinbase.ids) : 0, external: true, acct: A.coinbase },
  ];
};

const StudioAcctRow = ({ r, last, nav }) => {
  const click = r.acct ? () => nav.push({ type: "account", acct: r.acct }) : undefined;
  return (
    <button className={click ? "press" : undefined} onClick={click} style={{ width: "100%", textAlign: "left", display: "flex", alignItems: "center", gap: 11, padding: "11px 13px", background: "none", border: "none", borderBottom: last ? "none" : "1px solid var(--rule)", cursor: click ? "pointer" : "default" }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
          <span style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.name}</span>
          {r.external && <span style={{ flex: "none", fontFamily: "var(--f-display)", fontSize: 8.5, fontWeight: 700, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--ink-3)", border: "1px solid var(--rule-2)", padding: "1px 5px", borderRadius: 6 }}>External</span>}
        </div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 10.5, color: "var(--ink-3)", marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.sub}</div>
      </div>
      <span style={{ flex: "none", fontFamily: "var(--f-mono)", fontSize: 12.5, fontVariantNumeric: "tabular-nums", color: r.external ? "var(--ink-2)" : "var(--ink)" }}>{usd(r.value, 2)}</span>
      {click && <Icon name="back" size={14} color="var(--ink-3)" style={{ flex: "none", transform: "scaleX(-1)" }} />}
    </button>
  );
};

/* The Studio "Act" surface — Tap to act (pending approvals) + Build with Yoshi.
   Inline (mobile) it returns the two sections; standalone (web) it wraps itself
   as the right-hand Studio pillar. */
const StudioActPanel = ({ nav, proposals = [], onReview, view, standalone, onRepin }) => {
  const cfg = STUDIO_ACT[view] || STUDIO_ACT.investments;
  const accts = studioAccountsFor(view);
  const hasNeedsYou = proposals.filter((p) => p.cat === cfg.cat).length > 0;
  const body = (
    <>
      <StudioNeedsYou proposals={proposals} cat={cfg.cat} label={cfg.label} onReview={onReview} onRepin={onRepin} />
      <section style={{ padding: "22px 18px 26px", marginTop: 10, borderTop: "1px solid var(--rule)" }}>
        {!window.__YOSHI_WEB && <>
        <span style={{ fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, letterSpacing: "-0.015em" }}>Build with Yoshi</span>
        <p style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", margin: "5px 0 0", lineHeight: 1.5 }}>{cfg.sub}</p>
        <RuleComposer nav={nav} prompts={cfg.prompts} />
        {view === "investments" && window.__YOSHI_WEB && nav.sheet &&
        <button className="press" onClick={() => nav.sheet({ type: "basket" })} style={{ width: "100%", textAlign: "left", marginTop: 14, padding: "13px 14px", background: "color-mix(in srgb, var(--accent) 9%, var(--bg-card))", border: "1px solid var(--accent)", borderRadius: 12, cursor: "pointer", position: "relative", overflow: "hidden" }}>
          <span style={{ position: "absolute", top: 0, left: 0, right: 0, height: 2, background: "var(--accent)" }} />
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 700, flex: 1 }}>Build a basket</span>
            <Icon name="back" size={15} color="var(--accent)" style={{ transform: "scaleX(-1)", flex: "none" }} />
          </div>
          <p style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-2)", margin: "5px 0 0", lineHeight: 1.5 }}>Describe a thesis. Yoshi composes the weights, backtests it against the S&amp;P, and drafts the buy.</p>
        </button>}
        </>}
        {accts.length > 0 &&
        <>
          <div style={{ marginTop: 22, display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, letterSpacing: "-0.015em" }}>{cfg.label === "Investments" ? "Investment" : cfg.label} accounts</span>
            {onRepin && !hasNeedsYou && <button className="press" onClick={onRepin} aria-label="Pin to the side" title="Pin to the side" style={{ marginLeft: "auto", flex: "none", width: 28, height: 28, borderRadius: 999, background: "none", border: "none", display: "grid", placeItems: "center", color: "var(--ink-2)", cursor: "pointer", padding: 0 }}><Icon name="external" size={14} /></button>}
          </div>
          <div style={{ marginTop: 9, border: "1px solid var(--rule-2)", borderRadius: 12, background: "var(--bg-card)", overflow: "hidden" }}>
            {accts.map((r, i) => <StudioAcctRow key={r.name} r={r} last={i === accts.length - 1} nav={nav} />)}
          </div>
        </>}
      </section>
    </>
  );
  if (!standalone) return body;
  return (
    <aside className="studio-act-col">
      <div className="scroll" style={{ paddingBottom: 20 }}>{body}</div>
    </aside>
  );
};

const StudioStat = ({ value, perfStr, perfPos = true, label, color = "var(--chart-line)", note }) =>
<div style={{ display: "flex", alignItems: "flex-start", gap: 8, marginTop: 12 }}>
    <span style={{ display: "flex", flexDirection: "column", alignItems: "flex-start" }}>
      <span style={{ fontFamily: "var(--f-mono)", fontSize: 17, fontWeight: 500, color: "var(--ink)", fontVariantNumeric: "tabular-nums", letterSpacing: "-0.01em" }}>{value}</span>
      <span style={{ fontFamily: "var(--f-mono)", fontSize: 11.5, color: perfPos ? "var(--accent-pos)" : "var(--signal-neg)", marginTop: 2 }}>{perfStr}{note && <span style={{ color: "var(--ink-3)" }}> · {note}</span>}</span>
    </span>
    {label && <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 7, marginTop: 3 }}>
      <span style={{ width: 16, height: 0, borderTop: `2px solid ${color}`, flex: "none" }} />
      <span style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600 }}>{label}</span>
    </span>}
  </div>;

const StudioReadCard = ({ nav, text, q, briefTitle = "Today's read" }) => {
  // mirrors the home "Today's read" card: bold header, a tappable bordered tile
  // that "writes" (blob loader) then resolves to the read with an as-of stamp.
  // Tapping opens the briefs hub on this read's detail (like the home digest).
  const [generating, setGenerating] = useState(true);
  useEffect(() => { const t = setTimeout(() => setGenerating(false), 1600); return () => clearTimeout(t); }, []);
  const [asOf] = useState(() => new Date().toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
  const openBrief = () => { if (!generating) nav.sheet({ type: "briefs", briefData: { id: "studio-read", kind: "insight", icon: "bulb", title: briefTitle, body: q[1], ask: q, when: "Last updated " + asOf } }); };
  return (
    <section style={{ padding: "20px 18px 6px" }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 14, fontWeight: 700, letterSpacing: "-0.01em", marginBottom: 8 }}>Today's read</div>
      <button className="press" onClick={openBrief} disabled={generating}
        style={{ width: "100%", textAlign: "left", background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 12, padding: "14px 16px 11px", cursor: generating ? "default" : "pointer" }}>
        {generating ?
        <>
          {window.BriefBlob ? <window.BriefBlob /> : null}
          <div style={{ textAlign: "center", marginTop: 4, fontFamily: "var(--f-mono)", fontSize: 10, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--ink-3)", animation: "yo-shimmer 1.6s ease-in-out infinite" }}>Powering up</div>
        </> :
        <p style={{ fontFamily: "var(--f-display)", fontSize: 13.5, lineHeight: 1.65, margin: 0, color: "var(--ink)" }}>{text}<span style={{ fontFamily: "var(--f-mono)", fontSize: 9.5, letterSpacing: "0.02em", color: "var(--ink-3)", whiteSpace: "nowrap" }}>{"\u2002"}· AS OF {asOf}</span></p>}
      </button>
    </section>);

};

const StudioNewsCard = ({ nav, text, q }) => {
  // Taps out to a brief (like the read card), rather than jumping into chat.
  const [asOf] = useState(() => new Date().toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }));
  const openBrief = () => nav.sheet({ type: "briefs", briefData: { id: "studio-news", kind: "insight", icon: "bulb", title: "News summary", body: text, ask: q, when: "Last updated " + asOf } });
  return (
    <section style={{ padding: "20px 18px 6px" }}>
      <Eyebrow>News summary</Eyebrow>
      <button className="press" onClick={openBrief} style={{ width: "100%", textAlign: "left", marginTop: 10, background: "var(--bg-card)", border: "1px solid var(--rule)", borderRadius: 12, padding: "14px", cursor: "pointer" }}>
        <p style={{ fontFamily: "var(--f-display)", fontSize: 13.5, lineHeight: 1.55, margin: 0, color: "var(--ink)" }}>{text}</p>
      </button>
    </section>);

};

const StudioBreakdown = ({ title, rows }) =>
<section style={{ padding: "20px 18px 6px" }}>
    <Eyebrow>{title}</Eyebrow>
    <div style={{ marginTop: 10, border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
      {rows.map((r, i) =>
    <div key={r.label} style={{ display: "grid", gridTemplateColumns: "auto 1fr auto", gap: 11, alignItems: "center", padding: "12px 14px", borderTop: i ? "1px solid var(--rule)" : "none" }}>
          <span style={{ width: 9, height: 9, background: r.color, flex: "none" }} />
          <div style={{ minWidth: 0 }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 13.5, fontWeight: 600 }}>{r.label}</div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", marginTop: 2 }}>{r.note}</div>
          </div>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: 13, fontWeight: 500, color: "var(--ink)", fontVariantNumeric: "tabular-nums" }}>{r.value}</span>
        </div>
    )}
    </div>
  </section>;

const CASH_NEWS = [
["Reuters", "Fed minutes point to one more rate cut this year", "2h"],
["WSJ", "Treasury yields ease as inflation cools", "5h"],
["Bloomberg", "Money-market funds top $6.5T as savers chase yield", "1d"]];

const DEBT_NEWS = [
["WSJ", "Credit-card APRs hold near record highs", "3h"],
["Reuters", "Mortgage rates dip for a third straight week", "6h"],
["Bloomberg", "Refinancing applications climb as rates ease", "1d"]];

const TAX_NEWS = [
["IRS", "2026 contribution limits rise for IRAs and 401(k)s", "1d"],
["WSJ", "Tax-loss harvesting window opens as some names slip", "4h"],
["Bloomberg", "Capital-gains rates unchanged for the year", "2d"]];


/* Treasury yield curve — today vs a month ago, across maturities */
const YIELD_CURVE = {
  mats: ["1M", "3M", "6M", "1Y", "2Y", "5Y", "10Y", "30Y"],
  now: [5.35, 5.30, 5.10, 4.80, 4.40, 4.25, 4.35, 4.55],
  prev: [5.41, 5.37, 5.22, 4.96, 4.56, 4.36, 4.41, 4.58]
};
const RateCurve = () => {
  const W = 350,H = 150,padL = 6,padR = 30,padT = 12,padB = 20;
  const all = [...YIELD_CURVE.now, ...YIELD_CURVE.prev];
  const min = Math.min(...all) - 0.12,max = Math.max(...all) + 0.12,range = max - min || 1;
  const n = YIELD_CURVE.mats.length;
  const x = (i) => padL + i / (n - 1) * (W - padL - padR);
  const y = (v) => padT + (1 - (v - min) / range) * (H - padT - padB);
  const line = (arr) => arr.map((v, i) => `${i ? "L" : "M"}${x(i).toFixed(1)},${y(v).toFixed(1)}`).join(" ");
  return (
    <section style={{ padding: "20px 18px 6px", marginTop: 10, borderTop: "1px solid var(--rule)" }}>
      <span style={{ fontFamily: "var(--f-display)", fontSize: 15, fontWeight: 600, letterSpacing: "-0.015em" }}>Treasury yield curve</span>
      <p style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)", margin: "5px 0 0", lineHeight: 1.5 }}>Where rates sit across maturities, today against a month ago.</p>
      <div style={{ marginTop: 14 }}>
        <svg width="100%" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" style={{ display: "block", overflow: "visible" }}>
          {[0, 0.5, 1].map((t) => { const v = min + range * t; return (
            <g key={t}>
              <line x1={padL} y1={y(v)} x2={W - padR} y2={y(v)} stroke="var(--rule)" strokeWidth="1" strokeDasharray="2 3" vectorEffect="non-scaling-stroke" />
              <text x={W - padR + 4} y={y(v) + 3} style={{ fontFamily: "var(--f-mono)", fontSize: "8.5px", fill: "var(--ink-3)" }}>{v.toFixed(1)}%</text>
            </g>); })}
          <path d={line(YIELD_CURVE.prev)} fill="none" stroke="var(--ink-3)" strokeWidth="1.4" strokeDasharray="4 3" vectorEffect="non-scaling-stroke" />
          <path d={line(YIELD_CURVE.now)} fill="none" stroke="var(--accent)" strokeWidth="1.85" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
          {YIELD_CURVE.now.map((v, i) => <circle key={i} cx={x(i)} cy={y(v)} r="2.4" fill="var(--accent)" />)}
          {YIELD_CURVE.mats.map((m, i) => <text key={m} x={x(i)} y={H - 4} textAnchor="middle" style={{ fontFamily: "var(--f-mono)", fontSize: "8.5px", fill: "var(--ink-3)" }}>{m}</text>)}
        </svg>
      </div>
      <div style={{ display: "flex", gap: 16, marginTop: 12, alignItems: "center" }}>
        {[["var(--accent)", "Today", false], ["var(--ink-3)", "A month ago", true]].map(([col, lab, dash]) =>
          <span key={lab} style={{ display: "inline-flex", alignItems: "center", gap: 7 }}>
            <span style={{ width: 16, height: 0, borderTop: `2px ${dash ? "dashed" : "solid"} ${col}` }} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600, color: "var(--ink-2)" }}>{lab}</span>
          </span>
        )}
      </div>
    </section>);

};

/* the customer's cash balance over 30 days, with optional rate-curve overlays */
const CASH_HIST = (() => {
  const n = 30, start = CASH_TOTAL - 3120, rnd = mulberry32(99);
  const raw = []; let v = start;
  for (let i = 0; i < n; i++) { v += 3120 / (n - 1) + (rnd() - 0.5) * 1300; raw.push(v); }
  const f = CASH_TOTAL / raw[n - 1];
  return raw.map((x) => x * f);
})();

const CashCurve = ({ nav }) => {
  const [ov, setOv] = useState(null);          // projection rate overlay: null | sav | tre | mm
  const [acct, setAcct] = useState(null);      // selected cash account: null (all) | account id
  const [projOpen, setProjOpen] = useState(false);
  const [searchOpen, setSearchOpen] = useState(false);
  const [q, setQ] = useState("");
  const RATES = { sav: ["Savings", 0.048, "4.80%"], tre: ["Treasuries", 0.051, "5.10%"], mm: ["Money market", 0.050, "5.00%"] };
  // the cash accounts, surfaced as the chart's primary selector pills
  // cash accounts: the Yoshi unified account + connected external accounts
  const CASH_ACCTS = [
    { id: "yoshi", label: "Yoshi cash", value: CASH_TOTAL },
    { id: "ext-chk", label: "Chase checking", value: 8210.32 },
    { id: "ext-sav", label: "Ally savings", value: 19300.00 },
  ];
  const ALL_CASH = CASH_ACCTS.reduce((s, a) => s + a.value, 0);
  const ACCTS = [{ id: null, label: "All cash", value: ALL_CASH }, ...CASH_ACCTS];
  const curAcct = ACCTS.find((a) => a.id === acct) || ACCTS[0];
  const base = curAcct.value;
  const baseFrac = base / CASH_TOTAL;
  const CASH_SEARCH = [
    { label: "Yoshi cash", sub: "Unified account · 4.80% APY", q: ["What's my Yoshi cash earning?", "Your Yoshi cash is the unified-account balance, earning 4.80% APY — roughly $330 a month and fully liquid."] },
    { label: "Chase checking", sub: "External · earning 0%", q: ["What about my Chase checking?", "Your connected Chase checking holds about $8,210 earning 0%. Moving it into 26-week Treasuries near 5.1% would earn roughly $420 a year and stay liquid. Want me to draft it?"] },
    { label: "Savings", sub: "Project at 4.80% APY", set: "sav" },
    { label: "Treasuries", sub: "Project at 5.10%", set: "tre" },
    { label: "Money market", sub: "~5.0% · compare", q: ["Compare money-market funds for my cash.", "Money-market funds pay about 5.0% right now — close to your Savings and just under short Treasuries. For cash you might need soon they're a fine fit. Want me to split your idle cash across them?"] },
    { label: "CD ladder", sub: "Lock higher rates in steps", q: ["Build me a CD ladder.", "A ladder spreads cash across 3-, 6-, and 12-month terms so some matures regularly while you lock in higher rates. How much should I ladder?"] },
  ];
  const pickSearch = (it) => { if (it.set) { setOv(it.set); } else if (nav) { nav.ask(it.q[0], it.q[1]); } setSearchOpen(false); setQ(""); };

  const [range, setRange] = useState("1Y");
  const [sel, setSel] = useState(null);
  const [live, setLive] = useState(null);
  const dragStart = useRef(null);

  const years = ST_RANGE[range].days / 365;
  const rate = ov ? RATES[ov][1] : 0.048;
  const projecting = !!ov;
  // historical balance up to today (solid), scaled to the selected account,
  // continuing into a dotted forward projection when a rate is on
  const histBuilt = useMemo(() => ({ data: trendSeries(99, range, CASH_TOTAL, CASH_TREND[range]).map((v) => v * baseFrac), dates: studioDates(range) }), [range, baseFrac]);
  const combined = useMemo(() => {
    if (!projecting) return { data: histBuilt.data, dates: histBuilt.dates, projFrom: null, projEnd: histBuilt.data[histBuilt.data.length - 1] };
    const len = ST_RANGE[range].len, months = years * 12, proj = [];
    for (let i = 0; i < len; i++) { const f = len === 1 ? 1 : i / (len - 1); proj.push(base * Math.pow(1 + rate / 12, months * f)); }
    return { data: [...histBuilt.data, ...proj.slice(1)], dates: [...histBuilt.dates, ...projDates(range).slice(1)], projFrom: histBuilt.data.length - 1, projEnd: proj[proj.length - 1] };
  }, [ov, range, histBuilt, base]);
  const data = combined.data, dates = combined.dates, n = data.length;
  const projFrom = combined.projFrom;
  const view = [0, n - 1];
  const lineColor = ov ? "var(--accent)" : "var(--chart-line)";
  const seriesLabel = projecting ? RATES[ov][0] : curAcct.label;
  const monthly = base * rate / 12;
  // headline reflects the projection's forward gain when a rate is on; otherwise the historical window
  const projEnd = combined.projEnd;
  const periodAbs = projecting ? projEnd - base : data[n - 1] - data[0];
  const periodPct = projecting ? (projEnd / base - 1) * 100 : (data[n - 1] / data[0] - 1) * 100;
  const noteLabel = projecting ? `projected · ${PROJ_LABEL[range]}` : ST_LABEL[range];

  const onDown = (i) => { dragStart.current = i; setLive([i, i]); };
  const onMove = (i) => { if (dragStart.current != null && i != null) setLive([dragStart.current, i]); };
  const onUp = (i) => { const a = dragStart.current; dragStart.current = null; setLive(null); if (a == null || i == null) return; const a2 = Math.min(a, i), b2 = Math.max(a, i); if (b2 - a2 < 3) { setSel(null); return; } setSel([a2, b2]); };
  useEffect(() => { setSel(null); setLive(null); }, [ov, range]);
  const analysis = useMemo(() => {
    if (!sel) return null;
    const [a, b] = sel; const sv = data[a], ev = data[b];
    return { a, b, pct: (ev / sv - 1) * 100, sv, ev };
  }, [sel, ov, range]);
  return (
    <section style={{ padding: "16px 18px 6px" }}>

      {searchOpen &&
      <div style={{ marginTop: 12 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "9px 12px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 10 }}>
          <Icon name="search" size={15} color="var(--ink-3)" stroke={1.5} />
          <input autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search your accounts &amp; rates…" style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13.5 }} />
          <button className="press" onClick={() => { setSearchOpen(false); setQ(""); }} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", padding: 0 }}><Icon name="close" size={15} /></button>
        </div>
        <div style={{ marginTop: 6 }}>
          {CASH_SEARCH.filter((it) => (it.label + " " + it.sub).toLowerCase().includes(q.trim().toLowerCase())).map((it) =>
          <button key={it.label} className="press" onClick={() => pickSearch(it)} style={{ width: "100%", textAlign: "left", cursor: "pointer", display: "grid", gridTemplateColumns: "1fr auto", gap: 10, alignItems: "center", background: "none", border: "none", borderBottom: "1px solid var(--rule)", padding: "10px 2px" }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 12.5, fontWeight: 600, color: "var(--ink)" }}>{it.label}</span>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", whiteSpace: "nowrap" }}>{it.sub}</span>
          </button>
          )}
        </div>
      </div>}

      <div style={{ display: "flex", gap: 6, marginTop: 12, overflowX: "auto", alignItems: "center" }} className="hcar">
        {ACCTS.map((a) => {
          const on = acct === a.id;
          return <button key={a.id || "all"} className="press" onClick={() => setAcct(a.id)} style={{ flex: "none", padding: "6px 12px", borderRadius: 999, background: on ? "var(--ink)" : "color-mix(in srgb, var(--ink) 8%, var(--bg-2))", color: on ? "var(--bg)" : "var(--ink-2)", border: "none", fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600, cursor: "pointer", whiteSpace: "nowrap" }}>{a.label}</button>;
        })}
      </div>

      <div style={{ marginTop: 12 }}>
        <StudioStat value={usd(data[n - 1], 2)} perfStr={perf(periodAbs, periodPct)} perfPos={periodAbs >= 0} label={seriesLabel} note={noteLabel} />
      </div>

      {/* analyzer tool — sits below the performance, compact like Investments'
         compare tool; projects the selected balance forward at a rate */}
      <div style={{ marginTop: 10 }}>
        <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
          <button className="press" onClick={() => setProjOpen((o) => !o)} aria-label="Project" title="Project growth at a rate" style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "4px 2px", background: "none", border: "none", cursor: "pointer", color: ov || projOpen ? "var(--accent)" : "var(--ink-3)" }}>
            <Icon name="trade" size={16} stroke={1.6} />
            <span style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600 }}>{ov ? `Projecting · ${RATES[ov][0]} ${RATES[ov][2]}` : "Project growth"}</span>
          </button>
          {ov && <button className="press" onClick={() => { setOv(null); setProjOpen(false); }} style={{ marginLeft: "auto", background: "none", border: "none", color: "var(--ink-3)", fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, cursor: "pointer" }}>Clear</button>}
        </div>
      </div>

      {projOpen &&
      <div style={{ marginTop: 8, border: "1px solid var(--rule-2)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)" }}>
        <div style={{ padding: "9px 13px 7px", fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", borderBottom: "1px solid var(--rule)" }}>Project {curAcct.label.toLowerCase()} forward at</div>
        {[["sav", "Savings", "4.80% APY · liquid"], ["tre", "Treasuries", "5.10% · 26-week"], ["mm", "Money market", "~5.00% · liquid"]].map(([k, label, sub], i) => {
          const on = ov === k;
          return (
            <button key={k} className="press" onClick={() => { setOv(on ? null : k); setProjOpen(false); }} style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "10px 13px", background: on ? "var(--bg-2)" : "none", border: "none", borderTop: i ? "1px solid var(--rule)" : "none", cursor: "pointer", textAlign: "left" }}>
              <span style={{ width: 9, height: 9, borderRadius: 2, background: on ? "var(--accent)" : "transparent", border: on ? "none" : "1px solid var(--rule-2)", flex: "none" }} />
              <span style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600 }}>{label}</span>
              <span style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)" }}>{sub}</span>
              {on && <span style={{ marginLeft: "auto", fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, color: "var(--ink-3)" }}>Clear</span>}
            </button>);
        })}
      </div>}

      <div style={{ marginTop: 12 }}>
        <AnalyzeChart data={data} events={[]} sel={sel} live={live} view={view} area color={lineColor} projFrom={projFrom} onDown={onDown} onMove={onMove} onUp={onUp} />
      </div>

      {/* timeframe — below the chart */}
      <div style={{ marginTop: 12 }}>
        <div style={{ display: "flex", gap: 2 }}>
          {STUDIO_RANGES.map((k) => { const on = range === k; return <button key={k} className="press" onClick={() => setRange(k)} style={{ flex: 1, padding: "5px 0", borderRadius: 8, background: on ? "var(--bg-2)" : "transparent", color: on ? "var(--ink)" : "var(--ink-3)", border: "none", fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: on ? 700 : 600, cursor: "pointer", textAlign: "center" }}>{k}</button>; })}
        </div>
      </div>

      {analysis ?
      <div style={{ marginTop: 14, border: "1px solid var(--accent)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)", position: "relative" }}>
        <div style={{ position: "absolute", top: 0, left: 0, right: 0, height: 2, background: "var(--accent)" }} />
        <div style={{ padding: "13px 14px" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", color: "var(--ink-3)" }}>{dates[analysis.a]} – {dates[analysis.b]}</span>
            <button className="press" onClick={() => setSel(null)} style={{ marginLeft: "auto", background: "none", border: "none", color: "var(--ink-3)", display: "flex", cursor: "pointer" }}><Icon name="close" size={15} /></button>
          </div>
          <p style={{ fontFamily: "var(--f-display)", fontSize: 14, lineHeight: 1.5, margin: "9px 0 0", color: "var(--ink)" }}>
            Over this window your {seriesLabel.toLowerCase()} {projecting ? "would grow" : (analysis.pct >= 0 ? "grew" : "slipped")} <span style={{ fontFamily: "var(--f-mono)", color: analysis.pct >= 0 ? "var(--accent-pos)" : "var(--signal-neg)" }}>{analysis.pct >= 0 ? "+" : "−"}{Math.abs(analysis.pct).toFixed(1)}%</span> · {signed(analysis.ev - analysis.sv, 0)}.{ov ? ` That's ${RATES[ov][2]} compounding.` : " Steady inflows, no big swings."}
          </p>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginTop: 14 }}>
            <Btn onClick={() => nav.ask("Move my idle cash to Treasuries.", "You've got about $15,000 idle in Checking. In 26-week Treasuries near 5.1% that's roughly $760 a year, and it stays available. Want me to draft it?")} style={{ padding: "10px", fontSize: 12.5 }}>Move to Treasuries</Btn>
            <Btn kind="ghost" onClick={() => draftThread(nav, "Cash automation", "Draft a cash automation.", "I can sweep anything over a balance you set into Treasuries or Savings automatically, inside your caps. What threshold should I use?")} style={{ padding: "10px", fontSize: 12.5 }}>Draft an automation</Btn>
          </div>
        </div>
      </div> :
      ov ?
      <div style={{ marginTop: 12, border: "1px solid var(--rule-2)", borderRadius: 12, background: "var(--bg-card)", padding: "12px 14px" }}>
        <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
          <span style={{ width: 16, height: 0, borderTop: "2px solid var(--accent)", alignSelf: "center" }} />
          <span style={{ fontFamily: "var(--f-display)", fontSize: 12.5, color: "var(--ink-2)" }}>At {RATES[ov][2]} in {RATES[ov][0]}, you'd earn</span>
          <span style={{ marginLeft: "auto", fontFamily: "var(--f-mono)", fontSize: 13, fontWeight: 500, color: "var(--accent-pos)" }}>≈ {usd(monthly, 0)}/mo</span>
        </div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", marginTop: 6, lineHeight: 1.45 }}>
          About <span style={{ fontFamily: "var(--f-mono)", color: "var(--accent-pos)" }}>{signed(periodAbs, 0)}</span> over the {PROJ_LABEL[range]}, growing your cash to <span style={{ fontFamily: "var(--f-mono)", color: "var(--ink)" }}>{usd(data[n - 1], 0)}</span>.
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginTop: 12 }}>
          <Btn onClick={() => nav.ask(`Move my cash into ${RATES[ov][0]}.`, `I can move your idle cash into ${RATES[ov][0]} at ${RATES[ov][2]}, earning about ${usd(monthly, 0)} a month while it stays available. Want me to draft it for approval?`)} style={{ padding: "10px", fontSize: 12.5 }}>Move to Yoshi</Btn>
          <Btn kind="ghost" onClick={() => draftThread(nav, "Cash automation", "Draft a cash automation.", "I can sweep anything over a balance you set into Treasuries or Savings automatically, inside your caps. What threshold should I use?")} style={{ padding: "10px", fontSize: 12.5 }}>Automate it</Btn>
        </div>
      </div> :
      null
      }
    </section>
  );
};

const CashView = ({ nav, proposals = [], onReview }) =>
<>
    <CashCurve nav={nav} />
    <StudioReadCard nav={nav} text="Your Yoshi cash earns 4.80%, and short-term Treasuries are near 5.1%. But about $8,200 is sitting idle in your connected Chase checking, earning nothing." q={["What should I do with my idle cash?", "Your Yoshi cash already earns 4.80%. The piece worth moving is the ~$8,200 sitting idle in your connected Chase checking earning nothing — Yoshi can sweep it into 26-week Treasuries near 5.1% and it stays liquid, roughly $420 a year. Want me to draft it for your approval?"]} />
    {!window.__YOSHI_WEB && <StudioBreakdown title="Where your cash sits" rows={[
  { label: "Yoshi cash", note: "Unified account · 4.80% APY", value: usd(CASH_TOTAL, 0), color: "var(--accent-2)" },
  { label: "Chase", note: "External · checking · earning 0%", value: usd(8210.32, 0), color: "var(--rule-2)" }]
  } />}
    {!window.__YOSHI_WEB && <StudioActPanel view="cash" nav={nav} proposals={proposals} onReview={onReview} />}
  </>;


/* customer's debt by loan type over 12 months — a tappable stacked area.
   Mirrors the Cash/Investments hero: balance header, chart, controls, action. */
const DEBTS = [
  { key: "mortgage", label: "Mortgage",     apr: "5.88%", color: "var(--accent-4)",   now: 238400, start: 245200, seed: 11 },
  { key: "auto",     label: "Auto loan",    apr: "6.40%", color: "var(--alloc-cash)", now: 18200,  start: 24600,  seed: 22 },
  { key: "credit",   label: "Credit",       apr: "22.9%", color: "var(--signal-neg)", now: 4391,   start: 6840,   seed: 33 },
];
const DEBT_HIST = DEBTS.map((d) => {
  const n = 12, rnd = mulberry32(d.seed), arr = [];
  for (let i = 0; i < n; i++) { const t = i / (n - 1); let v = d.start + (d.now - d.start) * t; v += (rnd() - 0.5) * d.start * 0.008; arr.push(Math.max(0, v)); }
  arr[n - 1] = d.now;
  return arr;
});

const DebtCurve = ({ nav }) => {
  const [loanSel, setLoanSel] = useState(null);     // null = all loans (total)
  const [range, setRange] = useState("1Y");
  const [sel, setSel] = useState(null);             // scrub window
  const [live, setLive] = useState(null);
  const dragStart = useRef(null);
  const [searchOpen, setSearchOpen] = useState(false);
  const [q, setQ] = useState("");

  const totalNow = DEBTS.reduce((s, d) => s + d.now, 0);
  const selD = loanSel ? DEBTS.find((d) => d.key === loanSel) : null;
  const endVal = selD ? selD.now : totalNow;
  const seriesSeed = selD ? selD.seed : 7;
  const lineColor = selD ? selD.color : "var(--signal-neg)";
  const built = useMemo(() => ({ data: trendSeries(seriesSeed, range, endVal, DEBT_TREND[range]), dates: studioDates(range) }), [loanSel, range]);
  const data = built.data, dates = built.dates, n = data.length;
  const view = [0, n - 1];
  const paid = data[0] - data[n - 1];
  const paidPct = data[0] ? paid / data[0] * 100 : 0;

  const onDown = (i) => { dragStart.current = i; setLive([i, i]); };
  const onMove = (i) => { if (dragStart.current != null && i != null) setLive([dragStart.current, i]); };
  const onUp = (i) => { const a = dragStart.current; dragStart.current = null; setLive(null); if (a == null || i == null) return; const a2 = Math.min(a, i), b2 = Math.max(a, i); if (b2 - a2 < 3) { setSel(null); return; } setSel([a2, b2]); };
  useEffect(() => { setSel(null); setLive(null); }, [loanSel, range]);
  const analysis = useMemo(() => {
    if (!sel) return null;
    const [a, b] = sel; const sv = data[a], ev = data[b];
    return { a, b, pct: (ev / sv - 1) * 100, sv, ev };
  }, [sel, loanSel, range]);
  const pillStyle = (on) => ({ flex: "none", padding: "6px 12px", borderRadius: 999, background: on ? "var(--ink)" : "color-mix(in srgb, var(--ink) 8%, var(--bg-2))", color: on ? "var(--bg)" : "var(--ink-2)", border: "none", fontFamily: "var(--f-display)", fontSize: 12, fontWeight: 600, cursor: "pointer", whiteSpace: "nowrap" });
  const askPayoff = () => selD
    ? nav.ask(`Help me pay down my ${selD.label.toLowerCase()}.`, `Your ${selD.label.toLowerCase()} is ${usd(selD.now)} at ${selD.apr} APR. I can schedule extra payments from Checking to clear it faster — how much a month can you put toward it?`)
    : nav.ask("Plan my debt payoff.", "Highest-APR first saves the most: your credit cards at ~22.9%, then the auto loan, then the mortgage. I can schedule extra payments from Checking — how much a month can you put toward it?");
  const DEBT_SEARCH = [
    ...DEBTS.map((d) => ({ label: d.label, sub: `${d.apr} APR · ${usd(d.now, 0)}`, sel: d.key })),
    { label: "Highest-APR payoff", sub: "Clear the costliest first", q: ["Plan my debt payoff.", "Highest-APR first saves the most: your credit cards at ~22.9%, then the auto loan, then the mortgage. I can schedule extra payments from Checking — how much a month can you put toward it?"] },
    { label: "Refinance mortgage", sub: "Rates eased 3 weeks", q: ["Should I refinance my mortgage?", "Mortgage rates have dipped for a third straight week. On your 5.88% balance a refi could make sense if you can drop about 0.75% after closing costs. Want me to estimate the break-even?"] },
    { label: "Consolidate credit", sub: "22.9% APR · clear first", q: ["Help me clear my credit card debt.", "Your cards run ~22.9% APR — the most expensive money you hold. I can schedule extra payments from Checking to clear the $4,391 faster. How much a month can you put toward it?"] },
  ];
  const pickSearch = (it) => { if (it.sel) { setLoanSel(it.sel); } else if (nav) { nav.ask(it.q[0], it.q[1]); } setSearchOpen(false); setQ(""); };
  return (
    <section style={{ padding: "16px 18px 6px" }}>

      {searchOpen &&
      <div style={{ marginTop: 12 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "9px 12px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 10 }}>
          <Icon name="search" size={15} color="var(--ink-3)" stroke={1.5} />
          <input autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search your loans &amp; payoff plans…" style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13.5 }} />
          <button className="press" onClick={() => { setSearchOpen(false); setQ(""); }} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", padding: 0 }}><Icon name="close" size={15} /></button>
        </div>
        <div style={{ marginTop: 6 }}>
          {DEBT_SEARCH.filter((it) => (it.label + " " + it.sub).toLowerCase().includes(q.trim().toLowerCase())).map((it) =>
          <button key={it.label} className="press" onClick={() => pickSearch(it)} style={{ width: "100%", textAlign: "left", cursor: "pointer", display: "grid", gridTemplateColumns: "1fr auto", gap: 10, alignItems: "center", background: "none", border: "none", borderBottom: "1px solid var(--rule)", padding: "10px 2px" }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 12.5, fontWeight: 600, color: "var(--ink)" }}>{it.label}</span>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 11.5, color: "var(--ink-3)", whiteSpace: "nowrap" }}>{it.sub}</span>
          </button>
          )}
        </div>
      </div>}

      <div style={{ display: "flex", gap: 6, marginTop: 12, overflowX: "auto", alignItems: "center" }} className="hcar">
        <button className="press" onClick={() => setSearchOpen((o) => !o)} aria-label="Search" style={{ flex: "none", width: 30, height: 30, borderRadius: 999, border: `1px solid ${searchOpen ? "var(--ink)" : "var(--rule-2)"}`, background: searchOpen ? "var(--ink)" : "transparent", color: searchOpen ? "var(--bg)" : "var(--ink-2)", display: "grid", placeItems: "center", cursor: "pointer" }}><Icon name="search" size={15} stroke={1.6} /></button>
        <button className="press" onClick={() => setLoanSel(null)} style={pillStyle(loanSel == null)}>All</button>
        {DEBTS.map((d) => {
          const on = loanSel === d.key;
          return (
            <button key={d.key} className="press" onClick={() => setLoanSel(on ? null : d.key)} style={{ ...pillStyle(on), display: "inline-flex", alignItems: "center", gap: 6 }}>
              <span style={{ width: 8, height: 8, background: d.color, flex: "none" }} />{d.label}
            </button>);
        })}
      </div>

      <div style={{ marginTop: 12 }}>
        <StudioStat value={usd(endVal, 2)} perfStr={`−${usd(paid, 0)} (${Math.abs(paidPct).toFixed(1)}%)`} perfPos label={selD ? selD.label : "Total debt"} color={lineColor} note={ST_LABEL[range]} />
      </div>

      <div style={{ marginTop: 12 }}>
        <AnalyzeChart data={data} events={[]} sel={sel} live={live} view={view} area color={lineColor} onDown={onDown} onMove={onMove} onUp={onUp} />
      </div>

      {/* timeframe — below the chart */}
      <div style={{ marginTop: 12 }}>
        <div style={{ display: "flex", gap: 2 }}>
          {STUDIO_RANGES.map((k) => { const on = range === k; return <button key={k} className="press" onClick={() => setRange(k)} style={{ flex: 1, padding: "5px 0", borderRadius: 8, background: on ? "var(--bg-2)" : "transparent", color: on ? "var(--ink)" : "var(--ink-3)", border: "none", fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: on ? 700 : 600, cursor: "pointer", textAlign: "center" }}>{k}</button>; })}
        </div>
      </div>

      {analysis ?
      <div style={{ marginTop: 14, border: "1px solid var(--accent)", borderRadius: 12, overflow: "hidden", background: "var(--bg-card)", position: "relative" }}>
        <div style={{ position: "absolute", top: 0, left: 0, right: 0, height: 2, background: "var(--accent)" }} />
        <div style={{ padding: "13px 14px" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: 10, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", color: "var(--ink-3)" }}>{dates[analysis.a]} – {dates[analysis.b]}</span>
            <button className="press" onClick={() => setSel(null)} style={{ marginLeft: "auto", background: "none", border: "none", color: "var(--ink-3)", display: "flex", cursor: "pointer" }}><Icon name="close" size={15} /></button>
          </div>
          <p style={{ fontFamily: "var(--f-display)", fontSize: 14, lineHeight: 1.5, margin: "9px 0 0", color: "var(--ink)" }}>
            Over this window your {selD ? selD.label.toLowerCase() : "total debt"} {analysis.pct <= 0 ? "fell" : "rose"} <span style={{ fontFamily: "var(--f-mono)", color: analysis.pct <= 0 ? "var(--accent-pos)" : "var(--signal-neg)" }}>{analysis.pct >= 0 ? "+" : "−"}{Math.abs(analysis.pct).toFixed(1)}%</span> · {signed(analysis.ev - analysis.sv, 0)}.{selD ? ` At ${selD.apr} APR, paying it down sooner saves the most.` : " Clearing the highest-APR balances first saves the most."}
          </p>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, marginTop: 14 }}>
            <Btn onClick={askPayoff} style={{ padding: "10px", fontSize: 12.5 }}>Plan a payoff</Btn>
            <Btn kind="ghost" onClick={() => draftThread(nav, "Debt automation", "Draft a debt automation.", "I can pay each statement from your Yoshi cash before it's due and put a set amount toward the highest-APR balance every month, inside your caps. What amount should I use?")} style={{ padding: "10px", fontSize: 12.5 }}>Draft an automation</Btn>
          </div>
        </div>
      </div> :
      null
      }
    </section>
  );
};

const DebtView = ({ nav, proposals = [], onReview }) =>
<>
    <DebtCurve nav={nav} />
    <StudioReadCard nav={nav} text="Clear the expensive debt first. Your cards run 22–24% APR, far more than your cash earns." q={["How should I pay down my debt?", "Highest-APR first saves the most: your credit cards at ~22.9%, then the auto loan, then the mortgage. I can schedule extra payments from Checking — how much a month can you put toward it?"]} />
    {!window.__YOSHI_WEB && <StudioBreakdown title="Your loans" rows={DEBTS.map((d) => ({ label: d.label, note: `${d.apr} APR`, value: usd(d.now, 0), color: d.color }))} />}
    {!window.__YOSHI_WEB && <StudioActPanel view="debt" nav={nav} proposals={proposals} onReview={onReview} />}
  </>;


const TaxesView = ({ nav }) =>
<>
    <StudioRead text="You're sitting on about $420 of harvestable losses, and you've realized $1,180 in gains this year. A little harvesting could trim what you owe." />
    <StudioTools nav={nav} title="Tax tools" sub="Keep more of what you make." items={[
  { line: "Harvest tax losses", note: "~$420 available now", q: ["Harvest my tax losses.", "Two holdings are down enough to harvest about $420 in losses — that offsets gains and trims your bill. I'd avoid wash sales by swapping into a similar fund for 31 days. Want me to set it up?"] },
  { line: "Estimate this year's taxes", note: "Realized gains + income", q: ["Estimate my taxes this year.", "Based on $1,180 in realized gains plus interest and dividends, your estimated tax on investments is roughly $260 so far. Want a full breakdown by account?"] },
  { line: "Get my tax documents", note: "1099s, cost basis", q: ["Get my tax documents.", "Your 1099-B, 1099-INT and crypto forms post in Documents as they're ready. I can also build a realized-gains report for any date range — want one?"] }]
  } />
  </>;


/* ============================================================
   Studio shell — a workshop; top pills filter by money type
   ============================================================ */
const Studio = ({ nav, initialView, proposals = [], onApprove }) => {
  const [view, setView] = useState(initialView || "investments");
  useEffect(() => { if (initialView) setView(initialView); }, [initialView]);
  const VIEWS = [["investments", "Investments"], ["cash", "Cash"], ["debt", "Debt"], ["automations", "Automations"]];
  return (
    <div style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0 }}>
      <NavBar title="Studio" border left={<YouButton nav={nav} />}
        right={<window.BellButton nav={nav} />} />
      <div className="scroll">
        {window.__YOSHI_WEB && ["investments", "cash", "debt"].includes(view) &&
        <div style={{ padding: "14px 18px 0", fontFamily: "var(--f-display)", fontSize: 17, fontWeight: 600, letterSpacing: "-0.02em" }} data-comment-anchor="studio-title">
          {(VIEWS.find(([k]) => k === view) || [])[1]} Studio
        </div>}
        <div className="hcar" style={{ display: "flex", justifyContent: "space-between", padding: "10px 18px 0", overflowX: "auto", borderBottom: "1px solid var(--rule)" }}>
          {VIEWS.map(([k, l]) => {
            const on = k === view;
            return <button key={k} className="press" onClick={() => setView(k)} style={{ flex: "none", textAlign: "center", position: "relative", padding: "6px 0 10px", background: "none", border: "none", color: on ? "var(--ink)" : "var(--ink-3)", fontFamily: "var(--f-display)", fontSize: 13, fontWeight: on ? 700 : 500, cursor: "pointer", whiteSpace: "nowrap" }}>
              {l}
              {on && <span style={{ position: "absolute", left: 0, right: 0, bottom: -1, height: 2, background: "var(--accent)" }} />}
            </button>;
          })}
        </div>
        {view === "investments" && <InvestmentsView nav={nav} proposals={proposals} onReview={onApprove} />}
        {view === "cash" && <CashView nav={nav} proposals={proposals} onReview={onApprove} />}
        {view === "debt" && <DebtView nav={nav} proposals={proposals} onReview={onApprove} />}
        {view === "taxes" && <TaxesView nav={nav} />}
        {view === "automations" && <AutomationsTab nav={nav} />}
      </div>
    </div>);

};

Object.assign(window, { Studio, MiniSpark, InvestmentsView, CashView, DebtView, StudioActPanel });