/* webchart.jsx — web-only advanced net-worth chart for the Home dashboard.
   Line/area (direction-colored) with crosshair scrubbing + time tooltip,
   automation markers (1D), a Compare picker (rebases to % change vs indices /
   crypto / commodities), and a lightbulb overlay menu: candlesticks, Bollinger
   bands, SMA 20/50, an RSI-14 sub-chart, and a dynamic price axis.
   Drives the Home headline via onScrub; range via setRange. */

/* compare universe — deterministic synthetics, grouped. `trend` = net % move
   across the window so each line has its own believable shape. */
const CMP_UNIVERSE = [
  { id: "spx", label: "S&P 500", group: "Indices", seed: 501, trend: 16, vol: 0.012 },
  { id: "ndx", label: "Nasdaq 100", group: "Indices", seed: 514, trend: 23, vol: 0.016 },
  { id: "dji", label: "Dow Jones", group: "Indices", seed: 522, trend: 9, vol: 0.010 },
  { id: "rut", label: "Russell 2000", group: "Indices", seed: 531, trend: 6, vol: 0.018 },
  { id: "btc", label: "Bitcoin", group: "Crypto", seed: 661, trend: 64, vol: 0.03 },
  { id: "eth", label: "Ethereum", group: "Crypto", seed: 672, trend: 38, vol: 0.034 },
  { id: "gold", label: "Gold", group: "Commodities", seed: 781, trend: 12, vol: 0.009 },
  { id: "oil", label: "Crude oil", group: "Commodities", seed: 792, trend: -8, vol: 0.02 },
];
const CMP_COLORS = ["var(--alloc-cash)", "var(--accent-3)", "var(--alloc-crypto)", "var(--accent-2)"];

/* range → window length in days, for the scrub time label */
const RANGE_DAYS = { "1D": 1, "1M": 30, "6M": 182, "YTD": 160, "1Y": 365, "5Y": 1825, "All": 2550 };
const timeLabel = (range, frac) => {
  if (range === "1D") {
    const mins = Math.round(390 * frac);              // 9:30 → 16:00
    const d = new Date(2026, 5, 1, 9, 30); d.setMinutes(d.getMinutes() + mins);
    const h = d.getHours(), m = d.getMinutes();
    return (h % 12 || 12) + ":" + String(m).padStart(2, "0") + (h >= 12 ? "pm" : "am");
  }
  const days = Math.max(0, Math.round(RANGE_DAYS[range] * (1 - frac)));
  if (days === 0) return "today";
  if (days < 60) return days + "d ago";
  if (days < 730) return Math.round(days / 30) + "mo ago";
  return (days / 365).toFixed(1) + "y ago";
};

const sma = (arr, w) => arr.map((_, i) => { const s = Math.max(0, i - w + 1); const seg = arr.slice(s, i + 1); return seg.reduce((a, b) => a + b, 0) / seg.length; });
const bollinger = (arr, w = 20) => {
  const up = [], low = [], mid = [];
  for (let i = 0; i < arr.length; i++) {
    const s = Math.max(0, i - w + 1), seg = arr.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);
    mid.push(m); up.push(m + 2 * sd); low.push(m - 2 * sd);
  }
  return { up, low, mid };
};
const rsi14 = (arr, w = 14) => {
  const out = [];
  let ag = 0, al = 0;
  for (let i = 0; i < arr.length; i++) {
    if (i === 0) { out.push(50); continue; }
    const ch = arr[i] - arr[i - 1];
    const g = Math.max(0, ch), l = Math.max(0, -ch);
    if (i <= w) { ag += g; al += l; const rs = (al / i) === 0 ? 100 : (ag / i) / (al / i || 1e-9); out.push(100 - 100 / (1 + rs)); }
    else { ag = (ag * (w - 1) + g) / w; al = (al * (w - 1) + l) / w; const rs = al === 0 ? 100 : ag / al; out.push(100 - 100 / (1 + rs)); }
  }
  return out;
};
const ohlcFrom = (data) => {
  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, 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) };
  });
};

const OverlayToggle = ({ label, sub, on, onClick }) => (
  <button className="press" onClick={onClick} style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "10px 13px", background: "none", border: "none", borderTop: "1px solid var(--rule)", cursor: "pointer", textAlign: "left" }}>
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 12.5, fontWeight: 600, color: "var(--ink)" }}>{label}</div>
      {sub && <div style={{ fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)", marginTop: 2 }}>{sub}</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>
);

const WebNetWorthChart = ({ range, setRange, nw, ranges, onScrub, nav }) => {
  const data = nw.data;
  const len = data.length;
  const markers = (range === "1D" && nw.markers) ? nw.markers : [];

  const [cmpIds, setCmpIds] = useState([]);
  const [candle, setCandle] = useState(false);
  const [bb, setBB] = useState(false);
  const [s20, setS20] = useState(false);
  const [axis, setAxis] = useState(true);
  const [scrub, setScrub] = useState(null);
  const [menu, setMenu] = useState(false);
  const [cmpOpen, setCmpOpen] = useState(false);
  const [cmpQ, setCmpQ] = useState("");
  const svgRef = useRef(null);
  // pull the grab bar below the chart to make it taller (persisted)
  const [chartH, setChartH] = useState(() => { try { const v = parseInt(localStorage.getItem("yoshi_web_charth") || "0", 10); return v >= 150 && v <= 560 ? v : null; } catch (e) { return null; } });
  const hDrag = useRef(null);

  const cmpMode = cmpIds.length > 0;
  const W = 1000, H = 216, padY = 16;

  // comparison series (rebased to % below)
  const cmpSeries = useMemo(() => cmpIds.map((id, k) => {
    const def = CMP_UNIVERSE.find((c) => c.id === id);
    const raw = series(def.seed + len * 3 + range.length, len, 100, 100 * (1 + def.trend / 100), def.vol);
    return { id, label: def.label, color: CMP_COLORS[k % CMP_COLORS.length], raw };
  }), [cmpIds, len, range]);

  const overlays = useMemo(() => ({
    bb: bb && !cmpMode ? bollinger(data) : null,
    s20: s20 && !cmpMode ? sma(data, 20) : null,
    ohlc: candle && !cmpMode ? ohlcFrom(data) : null,
  }), [data, bb, s20, candle, cmpMode]);

  // y-domain
  const pctOf = (arr, i) => (arr[i] / arr[0] - 1) * 100;
  let lo, hi;
  if (cmpMode) {
    const vals = [0];
    for (let i = 0; i < len; i++) vals.push(pctOf(data, i));
    cmpSeries.forEach((c) => { for (let i = 0; i < len; i++) vals.push(pctOf(c.raw, i)); });
    lo = Math.min(...vals); hi = Math.max(...vals);
  } else {
    const vals = [...data];
    if (overlays.bb) { vals.push(...overlays.bb.up, ...overlays.bb.low); }
    if (overlays.ohlc) overlays.ohlc.forEach((d) => { vals.push(d.h, d.l); });
    lo = Math.min(...vals); hi = Math.max(...vals);
  }
  const rng = (hi - lo) || 1;
  const x = (i) => (i / (len - 1)) * W;
  const yV = (v) => padY + (1 - (v - lo) / rng) * (H - padY * 2);
  const yP = (p) => padY + (1 - (p - lo) / rng) * (H - padY * 2);
  const lineV = (arr) => arr.map((v, i) => `${i ? "L" : "M"}${x(i).toFixed(1)},${yV(v).toFixed(1)}`).join(" ");
  const linePct = (arr) => arr.map((_, i) => `${i ? "L" : "M"}${x(i).toFixed(1)},${yP(pctOf(arr, i)).toFixed(1)}`).join(" ");

  const dir = data[len - 1] >= data[0];
  const color = dir ? "var(--chart-line)" : "var(--signal-neg)";
  const lastX = x(len - 1), lastY = cmpMode ? yP(pctOf(data, len - 1)) : yV(data[len - 1]);

  const idxFrom = (clientX) => {
    const r = svgRef.current.getBoundingClientRect();
    const rel = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
    return Math.round(rel * (len - 1));
  };
  const moveScrub = (clientX) => { const i = idxFrom(clientX); setScrub(i); onScrub && onScrub(data[i]); };
  const endScrub = () => { setScrub(null); onScrub && onScrub(null); };

  const scrubX = scrub != null ? x(scrub) : null;
  const scrubY = scrub != null ? (cmpMode ? yP(pctOf(data, scrub)) : yV(data[scrub])) : null;
  const scrubFrac = scrub != null ? scrub / (len - 1) : 0;

  return (
    <div style={{ marginTop: 2 }}>
      {/* controls: compare + overlays + legend */}
      <div style={{ display: "flex", alignItems: "center", gap: 10, margin: "12px 0 4px", position: "relative" }}>
        <button className="press" onClick={() => { setCmpOpen((o) => !o); setMenu(false); }} title="Compare" aria-haspopup="listbox" aria-expanded={cmpOpen}
          style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 8px 5px 9px", borderRadius: 8, background: cmpMode ? "color-mix(in srgb, var(--accent) 14%, transparent)" : "none", border: "1px solid " + (cmpMode || cmpOpen ? "var(--accent)" : "var(--rule-2)"), cursor: "pointer", color: cmpMode || cmpOpen ? "var(--accent)" : "var(--ink-2)" }}>
          <Icon name="swap" size={15} stroke={1.6} /> <span style={{ fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600 }}>Compare</span>
          <Icon name="down" size={13} stroke={2} style={{ marginLeft: 1, transform: cmpOpen ? "rotate(180deg)" : "none", transition: "transform 160ms ease", opacity: 0.75 }} />
        </button>
        <button className="press" onClick={() => { setMenu((o) => !o); setCmpOpen(false); }} title="Indicators" aria-haspopup="listbox" aria-expanded={menu}
          style={{ display: "inline-flex", alignItems: "center", gap: 5, padding: "5px 8px 5px 9px", borderRadius: 8, background: "none", border: "1px solid " + (menu ? "var(--accent)" : "var(--rule-2)"), cursor: "pointer", color: menu ? "var(--accent)" : "var(--ink-2)" }}>
          <Icon name="bulb" size={15} stroke={1.7} /> <span style={{ fontFamily: "var(--f-display)", fontSize: 11.5, fontWeight: 600 }}>Indicators</span>
          <Icon name="down" size={13} stroke={2} style={{ marginLeft: 1, transform: menu ? "rotate(180deg)" : "none", transition: "transform 160ms ease", opacity: 0.75 }} />
        </button>

        {/* legend strip */}
        <div className="hcar" style={{ display: "flex", alignItems: "center", gap: 12, overflowX: "auto", flex: 1, minWidth: 0 }}>
          {cmpSeries.map((c) => <Legend key={c.id} swatch={c.color} dash label={c.label} onX={() => setCmpIds((ids) => ids.filter((i) => i !== c.id))} />)}
          {!cmpMode && overlays.s20 && <Legend swatch="var(--accent-3)" dash label="SMA 20" />}
        </div>

        {/* compare dropdown */}
        {cmpOpen && (
          <div style={{ position: "absolute", top: "calc(100% + 6px)", left: 0, zIndex: 30, width: 280, maxHeight: 290, overflowY: "auto", background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 12, boxShadow: "0 18px 44px -18px rgba(0,0,0,0.5)" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "9px 12px", borderBottom: "1px solid var(--rule)", position: "sticky", top: 0, background: "var(--bg-card)" }}>
              <Icon name="search" size={14} color="var(--ink-3)" stroke={1.6} />
              <input autoFocus value={cmpQ} onChange={(e) => setCmpQ(e.target.value)} placeholder="Compare against…" style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: 13 }} />
            </div>
            {["Indices", "Crypto", "Commodities"].map((g) => {
              const items = CMP_UNIVERSE.filter((c) => c.group === g && (c.label + " " + c.id).toLowerCase().includes(cmpQ.trim().toLowerCase()));
              if (!items.length) return null;
              return (
                <div key={g}>
                  {items.map((c) => {
                    const on = cmpIds.includes(c.id);
                    return (
                      <button key={c.id} className="press" onClick={() => setCmpIds((ids) => on ? ids.filter((i) => i !== c.id) : (ids.length >= 4 ? ids : [...ids, c.id]))}
                        style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "8px 12px", background: on ? "var(--bg-2)" : "none", border: "none", cursor: "pointer", textAlign: "left" }}>
                        <span style={{ width: 9, height: 9, borderRadius: 2, flex: "none", background: on ? "var(--accent)" : "transparent", border: on ? "none" : "1px solid var(--rule-2)" }} />
                        <span style={{ fontFamily: "var(--f-display)", fontSize: 13, fontWeight: 600 }}>{c.label}</span>
                        {on && <span style={{ marginLeft: "auto", fontFamily: "var(--f-display)", fontSize: 11, color: "var(--ink-3)" }}>Remove</span>}
                      </button>
                    );
                  })}
                </div>
              );
            })}
          </div>
        )}

        {/* studies menu */}
        {menu && (
          <div style={{ position: "absolute", top: "calc(100% + 6px)", left: 110, zIndex: 30, width: 268, background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 12, overflow: "hidden", boxShadow: "0 18px 44px -18px rgba(0,0,0,0.5)" }}>
            <div style={{ padding: "10px 13px 8px" }}><Eyebrow style={{ fontSize: 9.5 }}>Indicators</Eyebrow>{cmpMode && <div style={{ fontFamily: "var(--f-display)", fontSize: 10.5, color: "var(--ink-3)", marginTop: 3 }}>Price studies pause in compare mode.</div>}</div>
            <OverlayToggle label="Candlesticks" sub="Open / high / low / close" on={candle} onClick={() => setCandle((v) => !v)} />
            <OverlayToggle label="Bollinger bands" sub="20-period, ±2σ" on={bb} onClick={() => setBB((v) => !v)} />
            <OverlayToggle label="SMA 20" sub="20-day moving average" on={s20} onClick={() => setS20((v) => !v)} />
            <OverlayToggle label="Crosshair" sub="X / Y lines that track your pointer" on={axis} onClick={() => setAxis((v) => !v)} />
          </div>
        )}
      </div>
      {(cmpOpen || menu) && <div onClick={() => { setCmpOpen(false); setMenu(false); }} style={{ position: "fixed", inset: 0, zIndex: 25 }} />}

      {/* main chart */}
      <div style={{ position: "relative" }}>
        <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) => moveScrub(e.clientX)}
          onPointerMove={(e) => moveScrub(e.clientX)}
          onPointerCancel={endScrub} onPointerLeave={endScrub}>
          {cmpMode && <line x1="0" y1={yP(0)} x2={W} y2={yP(0)} stroke="var(--rule-2)" strokeWidth="1" vectorEffect="non-scaling-stroke" />}

          {cmpMode ? (
            <g style={{ pointerEvents: "none" }}>
              {cmpSeries.map((c) => <path key={c.id} d={linePct(c.raw)} fill="none" stroke={c.color} strokeWidth="1.5" strokeDasharray="4 3" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />)}
              <path d={linePct(data)} fill="none" stroke={color} strokeWidth="1.9" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
            </g>
          ) : (
            <g style={{ pointerEvents: "none" }}>
              {/* Bollinger */}
              {overlays.bb && <>
                <path d={`${lineV(overlays.bb.up)} ${[...overlays.bb.low].map((v, i) => `L${x(len - 1 - i).toFixed(1)},${yV(overlays.bb.low[len - 1 - i]).toFixed(1)}`).join(" ")} Z`} fill="var(--accent)" opacity="0.07" />
                <path d={lineV(overlays.bb.up)} fill="none" stroke="var(--accent)" strokeWidth="1" strokeDasharray="1 3" opacity="0.5" vectorEffect="non-scaling-stroke" />
                <path d={lineV(overlays.bb.low)} fill="none" stroke="var(--accent)" strokeWidth="1" strokeDasharray="1 3" opacity="0.5" vectorEffect="non-scaling-stroke" />
              </>}
              {/* area + line, or candles */}
              {!candle && <path d={`${lineV(data)} L${W},${H} L0,${H} Z`} fill={color} opacity="0.08" />}
              {!candle && <path d={lineV(data)} fill="none" stroke={color} strokeWidth="1.9" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />}
              {overlays.ohlc && (() => {
                const w = Math.max(2, (W / len) * 0.6); const out = [];
                for (let i = 0; i < len; i++) { const d = overlays.ohlc[i], up = d.c >= d.o, col = up ? "var(--chart-line)" : "var(--signal-neg)", cx = x(i), top = yV(Math.max(d.o, d.c)), bot = yV(Math.min(d.o, d.c));
                  out.push(<g key={i}><line x1={cx} y1={yV(d.h)} x2={cx} y2={yV(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;
              })()}
              {overlays.s20 && <path d={lineV(overlays.s20)} fill="none" stroke="var(--accent-3)" strokeWidth="1.2" strokeDasharray="3 3" opacity="0.9" vectorEffect="non-scaling-stroke" />}
              {/* automation markers (1D) */}
              {markers.map((m, i) => <g key={i} style={{ pointerEvents: "none" }}><circle cx={x(m.idx)} cy={yV(data[m.idx])} r="6" fill="var(--accent)" opacity="0.2" /><circle cx={x(m.idx)} cy={yV(data[m.idx])} r="3" fill="var(--accent)" stroke="var(--bg)" strokeWidth="1" /></g>)}
              {/* live dot */}
              <circle cx={lastX} cy={lastY} r="3.4" fill={color} /><circle cx={lastX} cy={lastY} r="8" fill={color} opacity="0.16" />
            </g>
          )}

          {/* crosshair — X/Y lines tracking the pointer */}
          {axis && scrubX != null && <g style={{ pointerEvents: "none" }}>
            <line x1={scrubX} y1="0" x2={scrubX} y2={H} stroke="var(--ink-3)" strokeWidth="1" strokeDasharray="2 2" vectorEffect="non-scaling-stroke" />
            <line x1="0" y1={scrubY} x2={W} y2={scrubY} stroke="var(--ink-3)" strokeWidth="1" strokeDasharray="2 2" vectorEffect="non-scaling-stroke" />
            <circle cx={scrubX} cy={scrubY} r="3.6" fill="var(--bg)" stroke={color} strokeWidth="1.6" />
          </g>}
          {!axis && scrubX != null && <circle cx={scrubX} cy={scrubY} r="3.6" fill="var(--bg)" stroke={color} strokeWidth="1.6" style={{ pointerEvents: "none" }} />}
        </svg>

        {/* dynamic value chip at the crosshair height */}
        {axis && scrub != null && (
          <span style={{ position: "absolute", right: 2, top: `${(scrubY / H) * 100}%`, transform: "translateY(-50%)", fontFamily: "var(--f-mono)", fontSize: 10, fontWeight: 600, color: "var(--bg)", background: color, padding: "1px 5px", borderRadius: 4, pointerEvents: "none", fontVariantNumeric: "tabular-nums" }}>{cmpMode ? ((pctOf(data, scrub) >= 0 ? "+" : "") + pctOf(data, scrub).toFixed(1) + "%") : usd(data[scrub], 0)}</span>
        )}

        {/* scrub tooltip */}
        {scrub != null && (
          <div style={{ position: "absolute", top: -2, left: `${scrubFrac * 100}%`, transform: `translateX(${scrubFrac > 0.7 ? "-105%" : scrubFrac < 0.3 ? "5%" : "-50%"})`, pointerEvents: "none", background: "var(--ink)", color: "var(--bg)", padding: "5px 9px", borderRadius: 8, whiteSpace: "nowrap", boxShadow: "0 8px 22px -10px rgba(0,0,0,0.5)" }}>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: 12, fontWeight: 600, fontVariantNumeric: "tabular-nums" }}>{cmpMode ? ((pctOf(data, scrub) >= 0 ? "+" : "") + pctOf(data, scrub).toFixed(1) + "%") : usd(data[scrub], 0)}</span>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: 9.5, opacity: 0.65, marginLeft: 7 }}>{timeLabel(range, scrubFrac)}</span>
          </div>
        )}
      </div>

      {/* RSI sub-chart */}
      {overlays.rsi && (() => {
        const RH = 56, rp = 6;
        const ry = (v) => rp + (1 - v / 100) * (RH - rp * 2);
        const path = overlays.rsi.map((v, i) => `${i ? "L" : "M"}${x(i).toFixed(1)},${ry(v).toFixed(1)}`).join(" ");
        return (
          <div style={{ marginTop: 6, borderTop: "1px solid var(--rule)", paddingTop: 4 }}>
            <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
              <Eyebrow style={{ fontSize: 9 }}>RSI 14</Eyebrow>
              <span style={{ fontFamily: "var(--f-mono)", fontSize: 10, color: "var(--ink-3)" }}>{overlays.rsi[len - 1].toFixed(0)}</span>
            </div>
            <svg width="100%" viewBox={`0 0 ${W} ${RH}`} preserveAspectRatio="none" style={{ display: "block", overflow: "visible" }}>
              <line x1="0" y1={ry(70)} x2={W} y2={ry(70)} stroke="var(--signal-neg)" strokeWidth="1" strokeDasharray="2 3" opacity="0.5" vectorEffect="non-scaling-stroke" />
              <line x1="0" y1={ry(30)} x2={W} y2={ry(30)} stroke="var(--accent-pos)" strokeWidth="1" strokeDasharray="2 3" opacity="0.5" vectorEffect="non-scaling-stroke" />
              <path d={path} fill="none" stroke="var(--accent-3)" strokeWidth="1.4" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
              {scrubX != null && <line x1={scrubX} y1="0" x2={scrubX} y2={RH} stroke="var(--ink-3)" strokeWidth="1" strokeDasharray="2 2" vectorEffect="non-scaling-stroke" />}
            </svg>
          </div>
        );
      })()}

      {/* range selector */}
      <div style={{ display: "flex", gap: 2, marginTop: 8 }}>
        {ranges.map((r) => {
          const on = r === range;
          return <button key={r} className="press" onClick={() => setRange(r)} style={{ flex: 1, padding: "5px 0", background: on ? "var(--bg-2)" : "transparent", border: "none", borderRadius: 7, fontFamily: "var(--f-mono)", fontSize: 11.5, fontWeight: on ? 700 : 500, letterSpacing: "0.02em", color: on ? "var(--ink)" : "var(--ink-3)", cursor: "pointer", fontVariantNumeric: "tabular-nums" }}>{r}</button>;
        })}
      </div>

      {/* grab bar — pull down to grow the chart, up to shrink it */}
      <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 : 216) }; e.currentTarget.setAttribute("data-drag", "1"); try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {} }}
        onPointerMove={(e) => { const d = hDrag.current; if (!d) return; setChartH(Math.max(150, Math.min(560, Math.round(d.h + (e.clientY - d.y))))); }}
        onPointerUp={(e) => { hDrag.current = null; e.currentTarget.removeAttribute("data-drag"); try { localStorage.setItem("yoshi_web_charth", String(chartH || "")); } catch (_) {} }}
        onPointerCancel={(e) => { hDrag.current = null; e.currentTarget.removeAttribute("data-drag"); }}
        style={{ display: "flex", justifyContent: "center", padding: "6px 0 2px", cursor: "ns-resize", touchAction: "none" }}>
        <span style={{ width: 44, height: 4, borderRadius: 999, background: "var(--rule-2)" }} />
      </div>
    </div>
  );
};

const Legend = ({ swatch, label, dash, onX }) => (
  <span style={{ display: "inline-flex", alignItems: "center", gap: 6, flex: "none" }}>
    <span style={{ width: 16, height: 0, borderTop: `2px ${dash ? "dashed" : "solid"} ${swatch}`, flex: "none" }} />
    <span style={{ fontFamily: "var(--f-display)", fontSize: 11, fontWeight: 600, color: "var(--ink-2)", whiteSpace: "nowrap" }}>{label}</span>
    {onX && <button className="press" onClick={onX} aria-label="Remove" style={{ background: "none", border: "none", padding: 0, display: "flex", color: "var(--ink-3)", cursor: "pointer" }}><Icon name="close" size={11} /></button>}
  </span>
);

window.WebNetWorthChart = WebNetWorthChart;
