/* transfer.jsx, money movement flows rendered as overlays.
   TransferFlow: recipient → amount (custom keypad) → confirm → success.
   TradeSheet: buy/sell a holding → confirm → success.
   LinkSheet: Plaid-style institution picker.
   All share FlowSuccess (the seam draws, the check draws). */

/* ---- shared success ------------------------------------------------------- */
const FlowSuccess = ({ title, sub, onDone, eyebrow = "Done" }) =>
<div style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "0 28px", textAlign: "center" }}>
    <div style={{ width: 200, margin: "0 0 26px" }}><Seam draw /></div>
    <div style={{ width: 70, height: 70, borderRadius: 999, border: "1.5px solid var(--accent)", display: "grid", placeItems: "center", marginBottom: 20 }}>
      <svg width="36" height="36" viewBox="0 0 24 24" style={{ display: "block" }}>
        <path d="M5 12.5 L10 17.5 L19 6.5" fill="none" stroke="var(--accent)" strokeWidth="2.2" strokeLinecap="square" />
      </svg>
    </div>
    <Eyebrow color="var(--accent)">{eyebrow}</Eyebrow>
    <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(24), fontWeight: 500, letterSpacing: "normal", marginTop: 7 }}>{title}</div>
    <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), color: "var(--ink-2)", marginTop: 8, lineHeight: 1.5, maxWidth: 280 }}>{sub}</div>
    <div style={{ width: "100%", maxWidth: 320, marginTop: 26 }}><Btn full onClick={onDone}>Back to Yoshi</Btn></div>
  </div>;

/* ---- on-screen keypad ----------------------------------------------------- */
const Keypad = ({ onKey }) => {
  const keys = ["1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "0", "⌫"];
  return (
    <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 1, background: "var(--rule)" }}>
      {keys.map((k) =>
      <button key={k} className="press" onClick={() => onKey(k)} style={{ padding: "16px 0", background: "var(--bg)", border: "none", fontFamily: "var(--f-display)", fontSize: typeSize(22), fontWeight: 500, color: "var(--ink)", cursor: "pointer" }}>{k}</button>
      )}
    </div>);

};

/* ============================================================
   Trade sheet · search → explore → order entry → success
   ============================================================ */
const TradeSheet = ({ onClose, id, side: presetSide, nav, embedded, onDepthChange, returnToAccounts, hideMenu, holdings, onPlaced, settledCashAvailable = CASH_TOTAL, instantFundingAvailable = 0, unsettledSaleProceeds = 0 }) => {
  // resolve the launch id against the market catalog; an unknown id (e.g. a
  // linked holding's own id) drops to search instead of a blank sheet
  const launchSel = id && MARKET.find((x) => x.id === id) ? id : null;
  // `holdings` opens straight into the all-holdings list in the middle column
  const [phase, setPhase] = useState(holdings ? "holdings" : launchSel ? "entry" : "search");
  const [query, setQuery] = useState("");
  const [selId, setSelId] = useState(launchSel);
  const [side, setSide] = useState(presetSide || "BUY");
  const [amt, setAmt] = useState("");
  const [unit, setUnit] = useState("usd"); // SELL can switch to "shares"; BUY is always usd
  const [stopOn, setStopOn] = useState(false); // SELL only — stop (limit) order
  const [stop, setStop] = useState("");
  const [focus, setFocus] = useState("amt"); // which field the keypad edits: amt | stop
  const [sector, setSector] = useState(null);
  const [returnPhase, setReturnPhase] = useState("search");
  const sel = MARKET.find((x) => x.id === selId);
  const amtNum = parseFloat(amt || "0");
  const isShares = side === "SELL" && unit === "shares";
  const shares = sel ? isShares ? amtNum : amtNum / sel.last : 0;
  const offTradeLanding = phase !== "search";

  useEffect(() => {
    if (!embedded || !onDepthChange) return;
    onDepthChange(offTradeLanding);
    return () => onDepthChange(false);
  }, [embedded, onDepthChange, offTradeLanding]);

  const key = (k) => {
    const maxDp = focus === "amt" && isShares ? 4 : 2;
    const upd = (prev) => k === "⌫" ? prev.slice(0, -1) : prev.includes(".") && prev.split(".")[1]?.length >= maxDp ? prev : prev + k;
    (focus === "stop" ? setStop : setAmt)(upd);
  };
  const chooseSide = (s) => {setSide(s);if (s === "BUY") {setUnit("usd");setStopOn(false);}setFocus("amt");};
  const goTradeHome = () => {
    if (returnToAccounts) {
      onClose();
      return;
    }
    setSelId(null);setSector(null);setReturnPhase("search");setPhase("search");
  };
  const explore = (mid, backTo = "search") => {setSelId(mid);setReturnPhase(backTo);setPhase("detail");};
  const startOrder = (s) => {setAmt("");setStop("");setStopOn(false);setUnit("usd");setFocus("amt");if (s) setSide(s);setPhase("entry");};

  // Every trade starts in Activity. Market-open orders settle from In motion
  // into Recent; after-hours stock/ETF orders stay queued until the open.
  const queues = !MARKET_OPEN && sel && sel.kind !== "crypto";
  const submitOrder = () => {
    const orderValue = isShares ? amtNum * sel.last : amtNum;
    const unsettledSaleProceedsUsed = side === "BUY" ? Math.min(unsettledSaleProceeds, orderValue) : 0;
    const afterUnsettledSaleProceeds = Math.max(0, orderValue - unsettledSaleProceedsUsed);
    const settledCashUsed = side === "BUY" ? Math.min(settledCashAvailable, afterUnsettledSaleProceeds) : 0;
    const afterSettledCash = Math.max(0, afterUnsettledSaleProceeds - settledCashUsed);
    const reservedInstantFunds = side === "BUY" ? Math.min(instantFundingAvailable, afterSettledCash) : 0;
    const placed = onPlaced || (window.yoshiTrack && window.yoshiTrack.placed);
    if (placed) {
      placed({
        icon: side === "BUY" ? "circle-plus" : "trade",
        title: `${side === "BUY" ? "Buy" : "Sell"} ${sel.ticker}${isShares ? " · " + amtNum + " sh" : ""}`,
        category: "Trade", kind: "once", state: queues ? "queued" : "working", agent: false,
        when: queues ? "Tomorrow · market open" : "Executing now",
        chip: queues ? "Queued · tomorrow 9:30 AM" : "In motion · executing",
        net: side === "BUY" ? -orderValue : orderValue,
        unsettledSaleProceedsUsed,
        settledCashUsed,
        instantFundsUsed: reservedInstantFunds,
        scope: "yoshi", acct: "One-time order · Yoshi Brokerage",
        steps: queues
          ? [["Submitted", "Just now"], ["Queued for market open", "You are here"], ["Executes", "Tomorrow · 9:30 AM"]]
          : [["Submitted", "Just now"], ["Order in motion", "You are here"], ["Executes", "Market open"]],
        stepAt: 1,
        settleMs: queues ? undefined : 6000,
        settle: {
          icon: "trade", title: `${side === "BUY" ? "Bought" : "Sold"} ${sel.ticker} @ ${usd(sel.last)}`,
          detail: `${shares.toFixed(4)} ${sel.kind === "crypto" ? sel.ticker : "sh"} @ ${usd(sel.last)} · Your order`, category: "Investment", accountScope: "yoshi",
          net: side === "BUY" ? -orderValue : orderValue,
          unsettledSaleProceedsUsed,
          settledCashUsed,
          instantFundsUsed: reservedInstantFunds,
          unsettledSaleProceeds: side === "SELL" ? orderValue : 0,
        },
      });
      return;
    }
    onClose();
  };

  const body =
  <>
      {phase === "search" && <TradeSearch query={query} setQuery={setQuery} onPick={(mid) => explore(mid, "search")} onClose={onClose} nav={nav} embedded={embedded} hideMenu={hideMenu} onSeeAll={() => setPhase("holdings")} onSector={(area, name) => {setSector({ area, name });setPhase("sector");}} />}

      {phase === "sector" && sector &&
    <SectorDetail area={sector.area} name={sector.name} onBack={() => setPhase("search")} onClose={goTradeHome} onPick={(mid) => explore(mid, "sector")} nav={nav} />
    }

      {phase === "holdings" && <TradeHoldings onPick={(mid) => explore(mid, "holdings")} onBack={() => setPhase("search")} onClose={goTradeHome} />}

      {phase === "detail" && sel &&
    <TradeDetail sel={sel} onBack={() => setPhase(returnPhase)} onClose={goTradeHome} onTrade={startOrder} nav={nav} />
    }

      {phase === "entry" && sel &&
    <TradeEntry sel={sel} side={side} setSide={chooseSide} unit={unit} setUnit={setUnit}
    amt={amt} setAmt={setAmt} amtNum={amtNum} shares={shares} isShares={isShares}
    stopOn={stopOn} setStopOn={setStopOn} stop={stop} setStop={setStop} focus={focus} setFocus={setFocus}
    onKey={key} onBack={() => setPhase("detail")} onClose={goTradeHome} onReview={submitOrder} nav={nav}
    settledCashAvailable={settledCashAvailable} instantFundingAvailable={instantFundingAvailable} unsettledSaleProceeds={unsettledSaleProceeds} />
    }

      {phase === "success" && sel && (queues
    ? <div style={{ padding: "10px 0 26px", minHeight: 380, display: "flex" }}>
          {/* not a dead-end "success" — the confirmation IS the tracking card */}
          <FlowSuccess eyebrow="Order placed" title={`${side === "BUY" ? "Buy" : "Sell"} ${sel.ticker} queued`} sub={`Markets are closed, so this runs tomorrow at market open as a one-time order — about ${shares.toFixed(4)} sh at today's ${usd(sel.last)}. Cancel free anytime before 9:30 AM, in Activity under In motion.`} onDone={onClose} />
        </div>
    : <div style={{ padding: "10px 0 26px", minHeight: 380, display: "flex" }}>
          <FlowSuccess title={`${side === "BUY" ? "Bought" : "Sold"} ${sel.ticker}`} sub={`${shares.toFixed(4)} ${sel.kind === "crypto" ? sel.ticker : "sh"} at ${usd(sel.last)}. Settles ${sel.kind === "crypto" ? "instantly" : "T+1"}.`} onDone={onClose} />
        </div>
    )}
    </>;


  /* Web: Trade is a two-pane page — browse on the left, the order ticket on the
     right. Picking a symbol on the left opens its ticket on the right.
     (Falls back to the single-column flow on narrow windows.) */
  if (window.__YOSHI_WEB && !embedded && window.innerWidth >= 1100) {
    // Picking a security fills the ticket on the right AND shows the security's
    // detail on the LEFT (the middle column), like the main web app. The order
    // ticket is persistent on the right; the left toggles browse ⇄ detail.
    // back from a security detail returns to wherever it was opened from — the
    // all-holdings list or the browse — not always the browse
    const backFromWebSecurityDetail = returnToAccounts ? onClose : () => { setSelId(null); setPhase(returnPhase); };
    const pickWeb = (mid) => { if (phase === "holdings" || phase === "search" || phase === "sector") setReturnPhase(phase); setSelId(mid); setPhase("entry"); };
    const left =
      sel && (phase === "entry" || phase === "success" || phase === "submitting" || phase === "confirmed")
        ? <TradeDetail sel={sel} onBack={backFromWebSecurityDetail} onClose={onClose} onTrade={startOrder} nav={nav} showClose={false} showTradeFooter={false} />
        : phase === "sector" && sector
        ? <SectorDetail area={sector.area} name={sector.name} onBack={() => setPhase("search")} onClose={onClose} onPick={pickWeb} nav={nav} />
        : phase === "holdings"
          ? <HoldingsDetailSheet embedded onSelect={pickWeb} nav={nav} onClose={onClose} />
          : <TradeSearch web query={query} setQuery={setQuery} onPick={pickWeb} onClose={onClose} nav={nav} embedded={embedded} onSeeAll={() => setPhase("holdings")} onSector={(area, name) => { setSector({ area, name }); setPhase("sector"); }} />;
    const right =
      sel && phase === "success"
        ? <div style={{ flex: 1, display: "flex", padding: "10px 0 26px" }}>
            <FlowSuccess title={`${side === "BUY" ? "Bought" : "Sold"} ${sel.ticker}`} sub={`${shares.toFixed(4)} ${sel.kind === "crypto" ? sel.ticker : "sh"} at ${usd(sel.last)}. Settles ${sel.kind === "crypto" ? "instantly" : "T+1"}.`} onDone={onClose} />
          </div>
        : <TradeEntry web sel={sel || null} side={side} setSide={chooseSide} unit={unit} setUnit={setUnit} onPickSymbol={pickWeb}
            amt={amt} setAmt={setAmt} amtNum={amtNum} shares={shares} isShares={isShares}
            stopOn={stopOn} setStopOn={setStopOn} stop={stop} setStop={setStop} focus={focus} setFocus={setFocus}
            onKey={key} onBack={() => (sel ? setPhase("detail") : setSelId(null))} onClose={onClose} onReview={submitOrder} nav={nav}
            settledCashAvailable={settledCashAvailable} instantFundingAvailable={instantFundingAvailable} unsettledSaleProceeds={unsettledSaleProceeds} />;
    return (
      <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex" }} data-screen-label="Trade">
        <div className={phase === "search" ? "ld-trade-discovery" : undefined} style={{ flex: "1 1 0", minWidth: 300, maxWidth: 750, position: "relative", display: "flex", flexDirection: "column", minHeight: 0, padding: "0 14px", boxSizing: "border-box", borderRight: "1px solid var(--rule)", background: "var(--bg)" }}>
          {left}
        </div>
        <div style={{ flex: "1 1 0", minWidth: 300, maxWidth: 460, position: "relative", display: "flex", flexDirection: "column", minHeight: 0, background: "var(--bg)" }}>
          {right}
        </div>
      </div>
    );
  }

  /* Embedded mode: Trade is a first-class tab. Render inside the viewport
     so the persistent tab bar (a sibling) stays visible & tappable, exactly
     like Home / Accounts. No own StatusBar — the app's already shows above. */
  if (embedded) {
    return (
      <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column" }} data-screen-label="Trade">
        {body}
      </div>);

  }

  /* Overlay mode: launched from a holding's Buy/Sell — a focused modal flow. */
  return (
    <>
      <div className={`push-enter${phase === "search" ? " ld-trade-discovery" : ""}`} style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Trade">
        {window.StatusBar && <window.StatusBar />}
        {body}
      </div>
    </>);

};

/* ---- search + explore ----------------------------------------------------- */
/* sector heat map · tiles tinted by move over the selected period, mono numbers */
const SECTOR_PERIODS = ["1D", "1W", "1M", "6M", "YTD", "1Y", "5Y", "ALL"];
const SECTORS = [
["Technology", "tech", { "1D": 1.84, "1W": 3.20, "1M": 6.10, "6M": 14.30, "YTD": 22.40, "1Y": 31.80, "5Y": 146.20, "ALL": 238.00 }],
["Financials", "fin", { "1D": 0.42, "1W": 1.10, "1M": 2.30, "6M": 5.80, "YTD": 9.80, "1Y": 13.40, "5Y": 54.00, "ALL": 91.00 }],
["Comms", "comm", { "1D": 1.12, "1W": 0.80, "1M": 4.40, "6M": 9.70, "YTD": 15.20, "1Y": 24.10, "5Y": 83.00, "ALL": 137.00 }],
["Energy", "ener", { "1D": -1.27, "1W": -2.40, "1M": 1.20, "6M": -1.80, "YTD": -3.60, "1Y": 4.70, "5Y": 44.00, "ALL": 68.00 }],
["Healthcare", "heal", { "1D": -0.38, "1W": 0.60, "1M": -1.80, "6M": 2.70, "YTD": 4.10, "1Y": 8.60, "5Y": 62.00, "ALL": 109.00 }],
["Consumer", "cons", { "1D": 0.61, "1W": -0.90, "1M": 2.10, "6M": 4.90, "YTD": 7.30, "1Y": 12.80, "5Y": 58.00, "ALL": 104.00 }],
["Industrials", "indu", { "1D": 0.24, "1W": 1.40, "1M": 3.00, "6M": 7.90, "YTD": 11.00, "1Y": 17.60, "5Y": 71.00, "ALL": 122.00 }],
["Materials", "mats", { "1D": -0.74, "1W": -1.20, "1M": -2.60, "6M": 0.80, "YTD": 1.90, "1Y": 6.20, "5Y": 38.00, "ALL": 63.00 }],
["Utilities", "util", { "1D": -0.19, "1W": 0.30, "1M": -0.90, "6M": 3.20, "YTD": 5.40, "1Y": 9.10, "5Y": 35.00, "ALL": 57.00 }]];
const SECTOR_AREAS = '"tech tech fin fin" "tech tech comm heal" "ener cons cons indu" "ener mats util util"';

/* Yoshi's shortlist per sector · used when a heat-map tile is tapped */
const SECTOR_PICKS = {
  tech: ["NVDA", "MSFT", "AAPL"],
  fin: ["JPM", "V", "BRK.B"],
  comm: ["GOOGL", "META", "NFLX"],
  heal: ["LLY", "UNH", "ABBV"],
  ener: ["XOM", "CVX", "NEE"],
  cons: ["AMZN", "COST", "WMT"],
  indu: ["CAT", "GE", "UBER"],
  mats: ["LIN", "FCX", "NEM"],
  util: ["NEE", "DUK", "SO"]
};

const SectorHeatmap = ({ onSector }) => {
  const [period, setPeriod] = useState("1D");
  const [open, setOpen] = useState(false);
  const maxAbs = Math.max(...SECTORS.map(([,, v]) => Math.abs(v[period]))) || 1;
  const tint = (d) => {
    const mag = Math.min(Math.abs(d) / maxAbs, 1);
    const color = d >= 0 ? "var(--ld-gain)" : "var(--ld-loss)";
    return `color-mix(in srgb, ${color} ${70 + mag * 30}%, var(--ld-surface))`;
  };
  return (
    <div>
      <div style={{ display: "flex", alignItems: "center", margin: "20px 0 9px" }}>
        <Eyebrow style={{ margin: 0 }}>Sector heat map</Eyebrow>
        <div style={{ position: "relative", marginLeft: 8 }}>
          <button className="press" onClick={() => setOpen((o) => !o)} onBlur={() => setTimeout(() => setOpen(false), 120)} style={{ display: "inline-flex", alignItems: "center", gap: 3, padding: 0, background: "none", border: "none", cursor: "pointer", fontFamily: "var(--f-mono)", fontSize: typeSize(10), fontWeight: 500, letterSpacing: "normal", color: "var(--ink-3)" }}>
            {period}
            <Icon name="down" size={12} color="var(--ink-3)" stroke={1.5} />
          </button>
          {open &&
          <div style={{ position: "absolute", top: "calc(100% + 3px)", left: 0, zIndex: 6, background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 6, boxShadow: "0 10px 24px -14px rgba(0,0,0,0.35)", overflow: "hidden", minWidth: 58 }}>
              {SECTOR_PERIODS.map((p) =>
            <button key={p} className="press" onMouseDown={(e) => {e.preventDefault();setPeriod(p);setOpen(false);}} style={{ display: "block", width: "100%", textAlign: "left", padding: "6px 10px", background: p === period ? "var(--bg-2)" : "none", border: "none", cursor: "pointer", fontFamily: "var(--f-mono)", fontSize: typeSize(10.5), letterSpacing: "normal", color: p === period ? "var(--ink)" : "var(--ink-2)" }}>{p}</button>
            )}
            </div>
          }
        </div>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gridTemplateRows: "repeat(4, 52px)", gridTemplateAreas: SECTOR_AREAS, gap: 5 }}>
        {SECTORS.map(([name, area, vals]) => {
          const d = vals[period];
          const tileName = name === "Healthcare" ? "Health care" : name;
          return (
            <button key={name} className="press" onClick={() => onSector && onSector(name, area, d)} style={{ gridArea: area, padding: "7px 8px", background: tint(d), border: "none", borderRadius: 7, display: "flex", flexDirection: "column", justifyContent: "space-between", gap: 4, overflow: "hidden", cursor: "pointer", textAlign: "left" }}>
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11.5), fontWeight: 500, lineHeight: 1.05, letterSpacing: "normal", color: "var(--ld-white)", whiteSpace: "normal", overflowWrap: "anywhere" }}>{tileName}</span>
              <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(11.5), lineHeight: 1, fontWeight: 500, color: "rgba(255,255,255,0.88)" }}>{pct(d)}</span>
            </button>);
        })}
      </div>
    </div>);
};

/* per-sector constituents · [ticker, name, last, dayChange%, notional$B]
   open / prev-close are derived deterministically from last + day change. */
const SECTOR_MOVERS = {
  tech: [["NVDA", "NVIDIA Corp", 142.17, 2.31, 38.2], ["MSFT", "Microsoft Corp", 468.30, 1.12, 9.8], ["AVGO", "Broadcom Inc", 1712.40, 1.85, 6.1], ["AAPL", "Apple Inc", 214.05, -0.58, 11.4], ["ORCL", "Oracle Corp", 168.22, 0.94, 3.2]],
  fin: [["JPM", "JPMorgan Chase", 248.60, 0.42, 5.6], ["V", "Visa Inc", 312.85, 0.31, 3.1], ["BAC", "Bank of America", 44.20, 0.66, 4.0], ["BRK.B", "Berkshire Hathaway", 462.10, 0.18, 2.4], ["GS", "Goldman Sachs", 561.30, -0.22, 1.9]],
  comm: [["GOOGL", "Alphabet Inc", 178.22, 0.91, 6.8], ["META", "Meta Platforms", 612.05, 1.45, 8.3], ["NFLX", "Netflix Inc", 902.40, 1.88, 3.6], ["TMUS", "T-Mobile US", 224.10, 0.55, 1.7], ["DIS", "Walt Disney Co", 112.30, -0.40, 2.1]],
  ener: [["XOM", "Exxon Mobil", 108.40, -1.27, 4.2], ["CVX", "Chevron Corp", 152.18, -1.10, 3.0], ["COP", "ConocoPhillips", 98.60, -1.84, 2.2], ["SLB", "Schlumberger", 42.10, -2.05, 1.6], ["MPC", "Marathon Petroleum", 168.40, -0.92, 1.3]],
  heal: [["LLY", "Eli Lilly", 824.60, -0.38, 4.4], ["UNH", "UnitedHealth Grp", 512.30, -0.62, 3.1], ["JNJ", "Johnson & Johnson", 152.40, -0.30, 2.6], ["ABBV", "AbbVie Inc", 188.20, 0.24, 2.0], ["MRK", "Merck & Co", 98.10, -0.55, 1.8]],
  cons: [["AMZN", "Amazon.com Inc", 201.40, 1.22, 9.1], ["WMT", "Walmart Inc", 88.60, 0.36, 4.0], ["COST", "Costco Wholesale", 982.30, 0.74, 2.4], ["HD", "Home Depot", 412.50, 0.18, 2.1], ["MCD", "McDonald's Corp", 298.20, -0.22, 1.5]],
  indu: [["CAT", "Caterpillar Inc", 388.40, 0.24, 1.9], ["UBER", "Uber Technologies", 72.30, 1.40, 3.4], ["GE", "GE Aerospace", 198.60, 0.88, 2.2], ["BA", "Boeing Co", 182.10, -0.95, 2.6], ["HON", "Honeywell Intl", 224.50, 0.30, 1.4]],
  mats: [["LIN", "Linde plc", 462.30, -0.74, 1.6], ["FCX", "Freeport-McMoRan", 42.80, -1.20, 2.0], ["NEM", "Newmont Corp", 48.10, -1.60, 1.7], ["NUE", "Nucor Corp", 142.60, -0.88, 1.1], ["SHW", "Sherwin-Williams", 358.20, -0.40, 0.9]],
  util: [["NEE", "NextEra Energy", 78.30, 0.40, 1.9], ["SO", "Southern Co", 88.40, -0.25, 0.9], ["DUK", "Duke Energy", 116.20, -0.19, 0.8], ["D", "Dominion Energy", 56.10, -0.30, 0.7], ["AEP", "American Electric", 98.20, 0.12, 0.6]]
};
const notionalUsd = (b) => b >= 1 ? "$" + b.toFixed(2) + "B" : "$" + (b * 1000).toFixed(2) + "M";

/* full-screen sector page · lists the sector's movers with price, open, prev
   close, day change and notional. Reached by tapping a heat-map tile. */
const SectorDetail = ({ area, name, onBack, onClose, onPick, nav }) => {
  const [period, setPeriod] = useState("1D");
  const [rangeOpen, setRangeOpen] = useState(false);
  const [sort, setSort] = useState({ key: "Today", dir: "desc" });
  const sector = SECTORS.find((s) => s[1] === area);
  const periods = sector ? sector[2] : { "1D": 0 };
  const rows = (SECTOR_MOVERS[area] || []).map(([ticker, nm, last, dch, notB]) => {
    const prev = last / (1 + dch / 100);
    const dayAbs = last - prev;
    const open = prev + dayAbs * 0.35; // opened partway between prev close and last
    return { ticker, nm, last, dch, prev, open, notB, dayAbs };
  });
  const advancing = rows.filter((r) => r.dch >= 0).length;
  const declining = rows.length - advancing;
  const COLS = [
  ["Last", (r) => ({ main: usd(r.last, 2) }), (r) => r.last],
  ["Today", (r) => ({ main: (r.dch >= 0 ? "+" : "−") + usd(Math.abs(r.dayAbs), 2), sub: pct(r.dch), color: r.dch >= 0 ? "var(--accent-pos)" : "var(--signal-neg)" }), (r) => r.dch],
  ["Open", (r) => ({ main: usd(r.open, 2), color: "var(--ink-2)" }), (r) => r.open],
  ["Prev close", (r) => ({ main: usd(r.prev, 2), color: "var(--ink-2)" }), (r) => r.prev],
  ["Notional", (r) => ({ main: notionalUsd(r.notB), color: "var(--ink-2)" }), (r) => r.notB]];

  const toggleSort = (key) => setSort((s) => s.key === key ? { key, dir: s.dir === "desc" ? "asc" : "desc" } : { key, dir: key === "Holding" ? "asc" : "desc" });
  const sortAccessor = sort.key === "Holding" ? (r) => r.ticker : (COLS.find(([label]) => label === sort.key) || [])[2];
  const movers = [...rows]
    .sort((a, b) => {
      const av = sortAccessor(a), bv = sortAccessor(b);
      const result = typeof av === "string" ? av.localeCompare(bv) : av - bv;
      return sort.dir === "asc" ? result : -result;
    });

  // Use the same geometry and treatment as the All holdings table.
  const HOLD_W = 132;
  const COL_WIDTHS = [104, 110, 104, 104, 104];
  const W = HOLD_W + COL_WIDTHS.reduce((sum, width) => sum + width, 0);
  const caret = (key) => sort.key === key ? (sort.dir === "desc" ? "▼" : "▲") : "▾";
  const thStyle = { fontFamily: "var(--f-display)", fontSize: typeSize(9.5), fontWeight: 500, letterSpacing: "normal", textTransform: "uppercase", color: "var(--ink-3)", background: "none", border: "none", cursor: "pointer", padding: 0, whiteSpace: "nowrap" };
  const periodLabel = { "1D": "Today", "1W": "Past week", "1M": "Past month", "6M": "Past 6 months", YTD: "Year to date", "1Y": "Past year", "5Y": "Past 5 years", ALL: "All time" };

  return (
    <>
      <div style={{ padding: "8px 20px 0", display: "flex", alignItems: "center", gap: 10, flex: "none" }}>
        <button className="press" onClick={onBack} style={{ background: "none", border: "none", display: "flex", color: "var(--ink)", padding: 0, margin: "0 -2px" }}><Icon name="back" size={20} /></button>
        <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 500, letterSpacing: "normal" }}>{name}</span>
        <button className="press" onClick={onClose} style={{ marginLeft: "auto", background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>
      </div>

      {/* sector summary · same hierarchy as All holdings */}
      <div style={{ flex: "none", padding: "14px 20px 12px" }}>
        <Eyebrow>Sector performance</Eyebrow>
        <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginTop: 6 }}>
          <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(28), fontWeight: 500, letterSpacing: "normal", color: periods[period] >= 0 ? "var(--accent-pos)" : "var(--signal-neg)" }}>{pct(periods[period])}</span>
          <div style={{ position: "relative" }}>
            <button className="press" onClick={() => setRangeOpen((o) => !o)} style={{ display: "inline-flex", alignItems: "center", gap: 4, padding: 0, background: "none", border: "none", cursor: "pointer", fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-3)" }}>
              {periodLabel[period]} <Icon name="down" size={13} color="var(--ink-3)" style={{ transform: rangeOpen ? "rotate(180deg)" : "none" }} />
            </button>
            {rangeOpen && <>
              <div onClick={() => setRangeOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 30 }} />
              <div style={{ position: "absolute", top: 24, left: 0, zIndex: 31, minWidth: 154, background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 10, overflow: "hidden", boxShadow: "0 14px 30px -16px rgba(0,0,0,0.45)" }}>
                {SECTOR_PERIODS.map((p, i) => <button key={p} className="press" onClick={() => { setPeriod(p); setRangeOpen(false); }} style={{ display: "block", width: "100%", padding: "9px 12px", background: p === period ? "var(--bg-2)" : "none", border: "none", borderTop: i ? "1px solid var(--rule)" : "none", textAlign: "left", cursor: "pointer", fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: p === period ? "var(--ink)" : "var(--ink-2)" }}>{periodLabel[p]}</button>)}
              </div>
            </>}
          </div>
        </div>
        <div style={{ marginTop: 12, border: "1px solid var(--rule)", borderRadius: 10, overflow: "hidden", background: "var(--bg-card)" }}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 12px", borderBottom: "1px solid var(--rule)" }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-2)" }}>Advancing today</span>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12.5), color: "var(--accent-pos)" }}>{advancing} of {rows.length}</span>
          </div>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 12px" }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-2)" }}>Declining today</span>
            <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12.5), color: "var(--signal-neg)" }}>{declining} of {rows.length}</span>
          </div>
        </div>
      </div>

      <style>{`
        .yo-sector-table { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; scrollbar-color: var(--rule-2) transparent; }
        .yo-sector-table::-webkit-scrollbar { height: 7px; }
        .yo-sector-table::-webkit-scrollbar-thumb { background: var(--rule-2); border-radius: 999px; }
        .yo-sector-table::-webkit-scrollbar-thumb:hover { background: var(--ink-3); }
        .yo-sector-table::-webkit-scrollbar-track { background: transparent; }
      `}</style>
      <div className="scroll" style={{ borderTop: "1px solid var(--rule)" }}>
        <div className="yo-sector-table">
          <div style={{ minWidth: W }}>
            <div style={{ display: "flex", alignItems: "center", height: 38, borderBottom: "1px solid var(--rule)", background: "var(--bg-2)" }}>
              <button className="press" onClick={() => toggleSort("Holding")} style={{ ...thStyle, position: "sticky", left: 0, zIndex: 2, flex: "1 0 " + HOLD_W + "px", minWidth: HOLD_W, height: "100%", background: "var(--bg-2)", display: "flex", alignItems: "center", gap: 3, padding: "0 12px", borderRight: "1px solid var(--rule)" }}>Holding <span style={{ color: sort.key === "Holding" ? "var(--accent)" : "var(--ink-3)", fontSize: typeSize(8) }}>{caret("Holding")}</span></button>
              {COLS.map(([label], i) => <button key={label} className="press" onClick={() => toggleSort(label)} style={{ ...thStyle, width: COL_WIDTHS[i], flex: "none", height: "100%", display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 3, padding: "0 12px" }}>{label} <span style={{ color: sort.key === label ? "var(--accent)" : "var(--ink-3)", fontSize: typeSize(8) }}>{caret(label)}</span></button>)}
            </div>
            {movers.map((r, ri) => {
              // every mover opens its stock detail (same as picking a search result);
              // tickers outside the tradeable catalog get a synthesized instrument.
              const open = () => {
                let inst = MARKET.find((m) => m.ticker === r.ticker);
                if (!inst) {
                  inst = { id: "sx-" + r.ticker.toLowerCase().replace(/[^a-z0-9]/g, ""), ticker: r.ticker, name: r.nm, last: r.last, dch: r.dch, kind: "stock" };
                  if (!MARKET.find((m) => m.id === inst.id)) MARKET.push(inst);
                }
                onPick(inst.id);
              };
              return (
                <button key={r.ticker} className="press" onClick={open} style={{ display: "flex", alignItems: "center", width: "100%", minHeight: 56, background: "none", border: "none", borderBottom: "1px solid var(--rule)", cursor: "pointer", padding: 0, textAlign: "left" }}>
                <div style={{ position: "sticky", left: 0, zIndex: 1, flex: "1 0 " + HOLD_W + "px", minWidth: HOLD_W, background: "var(--bg)", borderRight: "1px solid var(--rule)", padding: "9px 12px", height: "100%", display: "flex", flexDirection: "column", justifyContent: "center" }}>
                  <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12.5), fontWeight: 500, color: "var(--accent)" }}>{r.ticker}</span>
                  <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)", marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{r.nm}</span>
                </div>
                {COLS.map(([label, f], i) => {
                    const c = f(r);
                    return (
                      <div key={label} style={{ width: COL_WIDTHS[i], flex: "none", padding: "0 12px", display: "flex", flexDirection: "column", alignItems: "flex-end", textAlign: "right" }}>
                      <div style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12.5), fontWeight: 500, color: c.color || "var(--ink)", fontVariantNumeric: "proportional-nums" }}>{c.main}</div>
                      {c.sub && <div style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(10.5), color: c.color || "var(--ink-3)", marginTop: 1, fontVariantNumeric: "proportional-nums" }}>{c.sub}</div>}
                    </div>);
                  })}
              </button>);
            })}
          </div>
        </div>
        <div style={{ height: 20 }} />
      </div>
    </>);
};

/* full-screen list of all holdings — reached via "See all" */
const TradeHoldings = ({ onPick, onBack, onClose }) => {
  const all = [...HOLDINGS].sort((a, b) => b.value - a.value).map((h) => MARKET.find((m) => m.id === h.id)).filter(Boolean);
  return (
    <>
      <div style={{ padding: "8px 20px 0", display: "flex", alignItems: "center", gap: 10, flex: "none" }}>
        <button className="press" onClick={onBack} style={{ background: "none", border: "none", display: "flex", color: "var(--ink)", padding: 0, margin: "0 -2px" }}><Icon name="back" size={20} /></button>
        <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 500, letterSpacing: "normal" }}>Your holdings</span>
        <button className="press" onClick={onClose} style={{ marginLeft: "auto", background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>
      </div>
      <div className="scroll" style={{ padding: "8px 20px 16px" }}>
        {all.map((m, i) => <InstrumentRow key={m.id} m={m} onPick={onPick} last={i === all.length - 1} />)}
      </div>
    </>);

};

const TradeSearch = ({ query, setQuery, onPick, onClose, nav, onSeeAll, embedded, onSector, web, hideMenu }) => {
  const q = query.trim().toLowerCase();
  const [focused, setFocused] = useState(false);
  const results = q ? MARKET.filter((m) => m.ticker.toLowerCase().includes(q) || m.name.toLowerCase().includes(q)) : [];
  const topHoldings = [...HOLDINGS].sort((a, b) => b.value - a.value).slice(0, 4).map((h) => MARKET.find((m) => m.id === h.id)).filter(Boolean);
  // the user's actual Yoshi positions only, matching Home's holdings list
  const allHoldings = [...GROUPS.investments.items, ...GROUPS.crypto.items].sort((a, b) => b.value - a.value).map((h) => {const m = MARKET.find((x) => x.id === h.id);return m ? { ...m, posValue: h.value } : null;}).filter(Boolean);
  const moversSorted = [...MARKET].sort((a, b) => b.dch - a.dch);
  const gainers = moversSorted.slice(0, 3);
  const losers = moversSorted.slice(-3).reverse();
  // interleave biggest gainers and biggest losers so strong negatives surface up front
  const moversMixed = (() => {
    const up = moversSorted.filter((m) => m.dch >= 0);
    const down = moversSorted.filter((m) => m.dch < 0).sort((a, b) => a.dch - b.dch);
    const out = [];const max = Math.max(up.length, down.length);
    for (let i = 0; i < max; i++) {if (up[i]) out.push(up[i]);if (down[i]) out.push(down[i]);}
    return out;
  })();
  const askYoshi = () => {onClose();nav && nav.ask("Got any trade ideas for me?", "A few, based on your accounts. You're leaning heavy on NVDA, so moving some of it into VOO would spread out your risk. You've got $15,000.00 sitting idle in Savings that could earn about 5.10% in short-term Treasuries, and SOL has run up a lot if you want to take a little off the table. Want me to put any of these together for you to approve?");};

  return (
    <>
      {web && <h1 className="ld-trade-title">Trade</h1>}
      {!web &&
      <div style={{ padding: "6px 16px 10px", minHeight: 44, display: "flex", alignItems: "center", gap: 10, flex: "none" }}>
        {!hideMenu && <YouButton nav={nav} />}
        <span style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-heading)", fontWeight: 500, letterSpacing: "normal" }}>Trade</span>
        <button className="press" onClick={onClose} style={{ marginLeft: "auto", background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>
      </div>}

      {/* free-form search */}
      <div style={{ padding: "12px 20px 4px", flex: "none", position: "relative", zIndex: 5 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "11px 13px", background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 10 }}>
          <Icon name="trade" size={17} color="var(--ink-3)" stroke={1.5} />
          <input className="ld-input" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search any stock, ETF, or coin"
          onFocus={() => setFocused(true)} onBlur={() => setTimeout(() => setFocused(false), 120)}
          style={{ flex: 1, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)" }} />
          {query && <button className="press" onClick={() => setQuery("")} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", padding: 0 }}><Icon name="close" size={16} /></button>}
        </div>

        {/* quick-select dropdown · your holdings, shown on focus before typing */}
        {focused && !q &&
        <div style={{ position: "absolute", left: 20, right: 20, top: "calc(100% - 2px)", background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 10, boxShadow: "0 12px 28px -14px rgba(0,0,0,0.35)", overflow: "hidden" }}>
            <div style={{ padding: "8px 13px 5px", borderBottom: "1px solid var(--rule)" }}><Eyebrow>Your holdings</Eyebrow></div>
            <div style={{ maxHeight: 234, overflowY: "auto", overscrollBehavior: "contain" }}>
              {allHoldings.map((m, i) =>
            <button key={m.id} className="press" onMouseDown={(e) => {e.preventDefault();onPick(m.id);}} style={{ width: "100%", textAlign: "left", display: "flex", alignItems: "center", gap: 10, padding: "9px 13px", background: "none", border: "none", borderTop: i ? "1px solid var(--rule)" : "none", cursor: "pointer" }}>
                  <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: 500, minWidth: 46 }}>{m.ticker}</span>
                  <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)", flex: 1, minWidth: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{m.name}</span>
                  <span style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 1 }}>
                    <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12), color: "var(--ink)", fontVariantNumeric: "proportional-nums" }}>{usd(m.posValue, 0)}</span>
                    <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(10.5), color: m.dch >= 0 ? "var(--accent-pos)" : "var(--signal-neg)" }}>{pct(m.dch)}</span>
                  </span>
                </button>
            )}
            </div>
          </div>
        }
      </div>

      <div className="scroll" style={{ padding: "8px 20px 16px" }}>
        {q ?
        results.length ? results.map((m) => <InstrumentRow key={m.id} m={m} onPick={onPick} />) :

        <>
                <div style={{ padding: "26px 0 14px", textAlign: "center", fontFamily: "var(--f-display)", fontSize: typeSize(13), color: "var(--ink-3)" }}>No matches for "{query}".</div>
                <YoshiPrompt onClick={askYoshi} sub={`Ask about ${query.toUpperCase()}, or anything else`} />
              </> :

        <>
            {/* explore · market movers as scrollable green/red performance pills */}
            <Eyebrow style={{ margin: "10px 0 8px" }}>Market movers</Eyebrow>
            <div className="hcar" style={{ display: "flex", gap: 8, overflowX: "auto", paddingBottom: 2, margin: "0 -20px", padding: "0 20px 2px" }}>
              {moversMixed.map((m) => {
              const up = m.dch >= 0;
              return (
                <button key={m.id} className="press" onClick={() => onPick(m.id)} style={{
                  flex: "none", display: "inline-flex", alignItems: "center", gap: 7, padding: "8px 13px", borderRadius: 999, cursor: "pointer",
                  background: up ? "color-mix(in srgb, var(--accent-pos) 12%, var(--bg))" : "color-mix(in srgb, var(--signal-neg) 12%, var(--bg))",
                  border: "1px solid " + (up ? "color-mix(in srgb, var(--accent-pos) 38%, transparent)" : "color-mix(in srgb, var(--signal-neg) 38%, transparent)")
                }}>
                    <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), fontWeight: 500, letterSpacing: "normal", color: "var(--ink)" }}>{m.ticker}</span>
                    <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(11.5), fontWeight: 500, fontVariantNumeric: "proportional-nums", color: up ? "var(--accent-pos)" : "var(--signal-neg)" }}>{m.dch >= 0 ? "+" : "−"}{Math.abs(m.dch).toFixed(2)}%</span>
                  </button>);

            })}
            </div>

            {/* explore · the user's largest positions — the chevron swaps the
                middle column to the same full all-holdings list Home opens
                in place; the order ticket stays on the right */}
            <button className="press" onClick={onSeeAll} style={{ width: "100%", background: "none", border: "none", display: "flex", alignItems: "center", justifyContent: "space-between", margin: "18px 0 9px", padding: 0, cursor: "pointer", textAlign: "left" }}>
              <Eyebrow>Top holdings</Eyebrow>
              <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 4, fontFamily: "var(--f-display)", fontSize: typeSize(11.5), fontWeight: 500, color: "var(--accent)" }}>View all <Icon name="back" size={13} color="var(--accent)" style={{ transform: "scaleX(-1)" }} /></span>
            </button>
            <div style={{ border: "1px solid var(--rule)", borderRadius: 12, overflow: "hidden", background: "var(--bg)" }}>
              {allHoldings.slice(0, 3).map((m, i) => (
                <HoldingRow key={m.id} h={{ ...m, value: m.posValue, shares: GROUPS.investments.items.find((holding) => holding.id === m.id)?.shares }} onPick={onPick} first={i === 0} />
              ))}
            </div>

            {/* explore · sector heat map */}
            <SectorHeatmap onSector={(name, area) => onSector(area, name)} />

            {/* explore · AI news brief across the whole market, cited */}
            <Eyebrow style={{ margin: "18px 0 0" }}>Market news brief</Eyebrow>
            <window.NewsBrief data={MARKET_NEWS} hideMeta hideSources />
          </>
        }
      </div>
    </>);

};

/* the impactful "chat with Yoshi" entry point */
const YoshiPrompt = ({ onClick, sub }) => {
  const logo = window.__resources && window.__resources[useTheme() === "bone" ? "logoWhite" : "logoBlack"] || "assets/logo-mark-white.png";
  return (
    <button className="press" onClick={onClick} style={{
      width: "100%", textAlign: "left", display: "flex", alignItems: "center", gap: 12, padding: "13px 14px",
      background: "color-mix(in srgb, var(--accent) 12%, var(--bg-card))", border: "1px solid var(--accent)", borderRadius: 12, cursor: "pointer"
    }}>
      <span style={{ width: 36, height: 36, flex: "none", borderRadius: 999, background: "var(--accent)", display: "grid", placeItems: "center" }}>
        <img src={logo} alt="" style={{ height: 18, width: "auto" }} />
      </span>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500, letterSpacing: "normal" }}>Ask Yoshi</div>
        <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-2)", marginTop: 3 }}>{sub}</div>
      </div>
      <Icon name="back" size={16} color="var(--accent)" style={{ transform: "scaleX(-1)" }} />
    </button>);

};

/* tiny axis-less sparkline · seeded walk, color by direction */
const RowSpark = ({ seed, up, w = 54, h = 22 }) => {
  const n = 18;
  let s = (Number(seed) || 1) % 233280,v = 50,min = Infinity,max = -Infinity;
  const pts = [];
  for (let i = 0; i < n; i++) {
    s = (s * 9301 + 49297) % 233280;
    v += (s / 233280 - 0.5) * 13 + (up ? 1.2 : -1.2);
    pts.push(v);
    if (v < min) min = v;
    if (v > max) max = v;
  }
  const span = max - min || 1;
  const d = pts.map((p, i) => `${i ? "L" : "M"}${(i / (n - 1) * w).toFixed(1)} ${(h - (p - min) / span * h).toFixed(1)}`).join(" ");
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} style={{ display: "block", overflow: "visible" }} aria-hidden="true">
      <path d={d} fill="none" stroke={up ? "var(--accent-pos)" : "var(--signal-neg)"} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
    </svg>);

};

const InstrumentRow = ({ m, onPick, last }) =>
<button className="press" onClick={() => onPick(m.id)} style={{
  width: "100%", textAlign: "left", background: "none", border: "none", borderBottom: last ? "none" : "1px solid var(--rule)",
  display: "flex", alignItems: "center", gap: 10, padding: "11px 0", cursor: "pointer"
}}>
    <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500, letterSpacing: "normal", minWidth: 52 }}>{m.ticker}</span>
    <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)", minWidth: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{m.name}</span>
  </button>;


/* ---- instrument detail · the "explore" view ------------------------------- */
const TD_RANGES = ["1D", "1W", "1M", "6M", "YTD", "1Y", "5Y", "ALL"];
const yrReturn = (m) => typeof securityYearReturn === "function" ? securityYearReturn(m) : (() => {const h = HOLDINGS.find((x) => x.id === m.id);return h ? h.perf["1Y"] : m.kind === "crypto" ? 72 : m.kind === "etf" ? 17 : 31;})();
const tdSeries = (m, range) => {
  const seed = (m.ticker.charCodeAt(0) + (m.ticker.charCodeAt(1) || 0)) * 7;
  const yr = yrReturn(m);
  const cfg = {
    "1D": [30, m.dch, 0.0012],
    "1W": [28, m.dch * 1.5, 0.0045],
    "1M": [32, m.dch * 4.5, 0.007],
    "6M": [40, yr * 0.55, 0.011],
    "YTD": [44, yr * 0.7, 0.012],
    "1Y": [54, yr, 0.014],
    "5Y": [60, (Math.pow(1 + yr / 100, 5) - 1) * 100, 0.02],
    "ALL": [64, (Math.pow(1 + yr / 100, 7) - 1) * 100, 0.022]
  }[range];
  const [len, movePct, vol] = cfg;
  const start = m.last / (1 + movePct / 100);
  return { data: series(seed + len, len, start, m.last, vol), movePct };
};
const tdStats = (m) => {
  if (typeof securityFundamentals === "function") return securityFundamentals(m);
  const cap = m.last * (m.kind === "crypto" ? 19e6 : 2.4e9);
  return [["Asset class", m.kind === "crypto" ? "Crypto" : m.kind === "etf" ? "ETF" : "Equity"], ["Market cap", "$" + (cap / 1e9).toFixed(2) + "B"], ["Day range", `${usd(m.last * 0.99)} – ${usd(m.last * 1.01)}`], ["1Y return", pct(yrReturn(m))]];
};
const tdNews = (m) => {
  const h = HOLDINGS.find((x) => x.id === m.id);
  if (h && h.news) return h.news;
  return [[m.kind === "crypto" ? "CoinDesk" : "Bloomberg", `${m.name} in focus as volume picks up`, "1h"], ["Reuters", `Analysts revisit ${m.ticker} after the move`, "4h"]];
};

const TradeDetail = ({ sel, onBack, onClose, onTrade, nav, showClose = true, showTradeFooter = true }) => {
  return (
    <>
      <div style={{ padding: "8px 20px 0", display: "flex", alignItems: "center", gap: 10, flex: "none" }}>
        <button className="press" onClick={onBack} style={{ background: "none", border: "none", display: "flex", color: "var(--ink)", padding: 0, margin: "0 -2px" }}><Icon name="back" size={20} /></button>
        <div style={{ minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 500, letterSpacing: "normal" }}>{sel.ticker}</span>
          </div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-3)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{sel.name}</div>
        </div>
        {showClose && <button className="press" onClick={onClose} style={{ marginLeft: "auto", background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>}
      </div>

      <SecurityDetailHierarchy security={sel} nav={nav} onBack={onBack} onClose={onClose} onTrade={onTrade} />

      {/* sticky trade footer — hidden in the web two-pane, where the order
          ticket already lives in the right column */}
      {showTradeFooter &&
      <div style={{ flex: "none", padding: "12px 20px 18px", borderTop: "1px solid var(--rule)", background: "color-mix(in srgb, var(--bg) 92%, transparent)", backdropFilter: "blur(12px)" }}>
        <Btn full kind="primary" onClick={() => onTrade()}>Trade {sel.ticker}</Btn>
      </div>}
    </>);

};

const TradeStat = ({ label, value, color = "var(--ink)", right, noBorder }) =>
<div style={{ padding: "10px 0", borderBottom: noBorder ? "none" : "1px dashed var(--rule)", textAlign: right ? "right" : "left" }}>
    <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), fontWeight: 500, letterSpacing: "normal", textTransform: "uppercase", color: "var(--ink-3)" }}>{label}</div>
    <div style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(14), fontWeight: 500, color, marginTop: 3, fontVariantNumeric: "proportional-nums" }}>{value}</div>
  </div>;

/* ---- order entry · mirrors the Trade form: account → symbol → action →
   amount (with % quick-fills) → confirm. Buy is USD-only; Sell can be USD or
   shares and can carry a stop (limit) order. Advanced order types live in
   automations. ------------------------------------------------------------- */
/* the Yoshi investment accounts a user can trade into/from, each with its own
   buying power. The picker on the Account row selects between them. */
const TRADE_ACCTS = [
  { name: "Brokerage", sub: "Yoshi · Individual ••2208", power: 10000.00 },
  { name: "Roth IRA", sub: "Yoshi · Retirement ••7731", power: 4291.28 },
  { name: "Crypto", sub: "Yoshi · Spot ••6614", power: 2150.00, crypto: true },
];

const MarketStatus = ({ sel }) => {
  const closed = !MARKET_OPEN && sel.kind !== "crypto";
  const label = closed ? "Market closed · opens tomorrow 9:30 AM ET" : sel.kind === "crypto" ? "Crypto market · executes now" : "Market open · executes now";
  const detail = closed
    ? "This becomes a one-time automation and executes at market open. You can cancel before then."
    : "This market order executes at the live price once you confirm.";
  return <div data-market-status={closed ? "closed" : "open"} style={{ display: "flex", alignItems: "flex-start", gap: 9, marginTop: 16, padding: "11px 12px", background: closed ? "color-mix(in srgb, var(--accent) 8%, var(--bg-card))" : "var(--bg-2)", border: `1px solid ${closed ? "var(--accent)" : "var(--rule-2)"}`, borderRadius: 10 }}>
    <Icon name={closed ? "clock" : "check"} size={15} color={closed ? "var(--accent)" : "var(--accent-pos)"} style={{ flex: "none", marginTop: 1 }} />
    <div style={{ minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), fontWeight: 500, color: "var(--ink)" }}>{label}</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10.5), color: "var(--ink-3)", lineHeight: 1.45, marginTop: 3 }}>{detail}</div>
      {closed && <div style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(9), color: "var(--accent)", letterSpacing: "normal", textTransform: "uppercase", marginTop: 6 }}>One-time automation</div>}
    </div>
  </div>;
};

const TradeMarketQuote = ({ sel }) => {
  const quote = securityMarketQuote(sel);
  const dayAbs = sel.last - sel.last / (1 + sel.dch / 100);
  const positive = sel.dch >= 0;
  const changeColor = positive ? "var(--accent-pos)" : "var(--signal-neg)";
  const detailStyle = { fontFamily: "var(--f-display)", fontSize: 13, lineHeight: 1.45, color: "var(--ink-2)" };
  const priceStyle = { fontFamily: "var(--f-mono)", fontSize: 17, fontWeight: 500, fontVariantNumeric: "proportional-nums", color: "var(--ink)", lineHeight: 1.35 };
  return (
    <section aria-label={`${sel.ticker} market quote`} data-trade-market-quote="compact" style={{ margin: "16px 2px 0" }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 8, flexWrap: "wrap" }}>
        <span style={detailStyle}>Last</span>
        <span style={{ ...priceStyle, fontSize: 22 }}>{usd(sel.last)}</span>
        <span style={{ fontFamily: "var(--f-mono)", fontSize: 15, color: changeColor, fontVariantNumeric: "proportional-nums", lineHeight: 1.4 }}>{positive ? "+" : "−"}{usd(Math.abs(dayAbs))} ({positive ? "+" : "−"}{Math.abs(sel.dch).toFixed(2)}%) today</span>
      </div>
      {quote.lastMarket && <div style={{ ...detailStyle, marginTop: 3 }}>
        {quote.lastSize} shares · {quote.lastMarket}
      </div>}
      <div style={{ display: "flex", flexWrap: "wrap", gap: "10px 20px", marginTop: 12 }}>
        <div style={{ flex: "1 1 130px" }}>
          <div style={detailStyle}>Bid{quote.bidMarket && <> · {quote.bidMarket}</>}</div>
          <div style={{ ...priceStyle, marginTop: 2 }}>${quote.bid.toFixed(quote.decimals)} <span style={{ fontSize: 15, fontWeight: 400 }}>×{quote.bidSize}</span></div>
        </div>
        <div style={{ flex: "1 1 130px" }}>
          <div style={detailStyle}>Ask{quote.askMarket && <> · {quote.askMarket}</>}</div>
          <div style={{ ...priceStyle, marginTop: 2 }}>${quote.ask.toFixed(quote.decimals)} <span style={{ fontSize: 15, fontWeight: 400 }}>×{quote.askSize}</span></div>
        </div>
      </div>
      <div style={{ ...detailStyle, marginTop: 10 }}>Day Volume <span style={{ color: "var(--ink)", fontWeight: 500 }}>{quote.volume}</span></div>
      <div style={{ ...detailStyle, marginTop: 4 }}>As of {quote.asOfDate}, {quote.snapshotTime} ET</div>
    </section>
  );
};

const TradeEntry = ({ sel, side, setSide, unit, setUnit, amt, setAmt, amtNum, shares, isShares, stopOn, setStopOn, stop, setStop, focus, setFocus, onKey, onBack, onClose, onReview, nav, web, onPickSymbol, settledCashAvailable: settledCashCapacity = CASH_TOTAL, instantFundingAvailable: instantFundingCapacity = 0, unsettledSaleProceeds: unsettledProceeds = 0 }) => {
  const [symQ, setSymQ] = useState("");
  const [gate, setGate] = useState(false);
  const symResults = symQ.trim() ? MARKET.filter((m) => m.ticker.toLowerCase().includes(symQ.trim().toLowerCase()) || m.name.toLowerCase().includes(symQ.trim().toLowerCase())).slice(0, 6) : [];
  const [padOpen, setPadOpen] = useState(false);
  const [acctIdx, setAcctIdx] = useState(0);
  const [acctOpen, setAcctOpen] = useState(false);
  // crypto symbols trade only in the Crypto account; stocks/ETFs only in the others
  const accts = TRADE_ACCTS.filter((a) => sel && sel.kind === "crypto" ? a.crypto : !a.crypto);
  const acct = accts[Math.min(acctIdx, accts.length - 1)];
  const held = sel ? HOLDINGS.find((h) => h.id === sel.id) : null;
  const posShares = held ? held.shares : 0;
  const posValue = held ? held.value : 0;
  // Buys can use settled cash, unused instant funding, and proceeds from
  // executed sales that have not settled to transferable cash yet.
  const settled = side === "BUY" || !sel ? settledCashCapacity : isShares ? posShares : posValue;
  const instantFundingAvailable = side === "BUY" && !isShares ? instantFundingCapacity : 0;
  const unsettledSaleProceeds = side === "BUY" && !isShares ? unsettledProceeds : 0;
  const avail = settled + instantFundingAvailable + unsettledSaleProceeds;
  const availLabel = isShares && sel ? `${posShares} ${sel.kind === "crypto" ? sel.ticker : "sh"} available` : side === "BUY" || !sel ? `${usd(avail)} available` : `${usd(avail)} available`;
  const stopNum = parseFloat(stop || "0");
  const marketClosed = !MARKET_OPEN && sel && sel.kind !== "crypto";
  const insufficient = amtNum > avail + 1e-6;
  const canConfirm = !!sel && amtNum > 0 && (!stopOn || stopNum > 0) && !insufficient;
  const openPad = (f) => {setFocus(f);setPadOpen(true);};
  const setPct = (f) => {setFocus("amt");setAmt(isShares ? String(+(settled * f).toFixed(4)) : String(+(avail * f).toFixed(2)));};

  const fieldStyle = (active) => ({ display: "flex", alignItems: "center", gap: 8, minHeight: 50, padding: "0 14px", background: "var(--bg-card)", border: `1px solid ${active ? "var(--accent)" : "var(--rule-2)"}`, borderRadius: 12, cursor: "pointer" });

  return (
    <>
      {!web &&
      <div style={{ padding: "8px 20px 0", display: "flex", alignItems: "center", gap: 10, flex: "none" }}>
        <button className="press" onClick={onBack} style={{ background: "none", border: "none", display: "flex", color: "var(--ink)", padding: 0, margin: "0 -2px" }}><Icon name="back" size={20} /></button>
        <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 500, letterSpacing: "normal" }}>Trade</span>
        <button className="press" onClick={onClose} style={{ marginLeft: "auto", background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>
      </div>}

      <div className="scroll" style={{ padding: web ? "26px 36px 0 20px" : "12px 20px 0" }}>
        {/* account · dropdown to pick which Yoshi account you trade into/from */}
        <Eyebrow>Account</Eyebrow>
        <div className="ld-trade-account-control" style={{ marginTop: web ? 22 : 7 }} onBlur={(e) => { if (!e.currentTarget.contains(e.relatedTarget)) setAcctOpen(false); }} onKeyDown={(e) => { if (e.key === "Escape") { e.stopPropagation(); setAcctOpen(false); e.currentTarget.querySelector("button").focus(); } }}>
          <button className="press ld-account-picker" aria-expanded={acctOpen} onClick={() => setAcctOpen((o) => !o)} style={{ width: "100%", display: "flex", alignItems: "center", gap: 12, padding: "16px", cursor: "pointer", textAlign: "left" }}>
            <span className="ld-account-glyph"><window.Logo size={24} /></span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)", fontWeight: 500 }}>{acct.name}</div>
              <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-caption)", color: "var(--ink-3)", marginTop: 3 }}>{acct.sub}</div>
            </div>
            <span style={{ display: "flex", flex: "none", transform: acctOpen ? "rotate(180deg)" : "none", transition: "transform .15s", color: "var(--ink-3)" }}><Icon name="down" size={16} /></span>
          </button>
          {acctOpen &&
          <div className="ld-account-menu" role="group" aria-label="Choose trading account">
            {accts.map((a, i) =>
            <button key={a.name} className="press ld-account-option" aria-pressed={i === Math.min(acctIdx, accts.length - 1)} onClick={() => {setAcctIdx(i);setAcctOpen(false);}}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)", fontWeight: 500 }}>{a.name}</div>
                <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-caption)", color: "var(--ink-3)", marginTop: 2 }}>{a.sub}</div>
              </div>
              {i === acctIdx && <Icon name="check" size={16} color="var(--accent)" style={{ flex: "none" }} />}
            </button>
            )}
          </div>}
        </div>

        {/* symbol */}
        <Eyebrow style={{ marginTop: 16 }}>Symbol</Eyebrow>
        {sel ? (
        <button className="press" onClick={onBack} style={{ width: "100%", marginTop: 7, display: "flex", alignItems: "center", gap: 12, padding: "13px 14px", background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 12, cursor: "pointer", textAlign: "left" }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)", fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{sel.ticker} · {sel.name}</div>
          </div>
          <Icon name="close" size={16} color="var(--ink-3)" style={{ flex: "none" }} />
        </button>
        ) : (
        <div style={{ position: "relative", marginTop: 7 }}>
          <div style={{ width: "100%", display: "flex", alignItems: "center", gap: 12, padding: "13px 14px", boxSizing: "border-box", background: "var(--bg-card)", border: `1px ${symQ ? "solid" : "dashed"} ${symQ ? "var(--accent)" : "var(--rule-2)"}`, borderRadius: 12 }}>
            <Icon name="search" size={16} color="var(--ink-3)" stroke={1.5} style={{ flex: "none" }} />
            {onPickSymbol
              ? <input value={symQ} onChange={(e) => setSymQ(e.target.value)} placeholder="Search any stock, ETF, or coin"
                  style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", color: "var(--ink)", fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)" }} />
              : <div style={{ flex: 1, minWidth: 0, fontFamily: "var(--f-display)", fontSize: "var(--ld-type-body)", color: "var(--ink-3)" }}>Pick a symbol on the left — search, movers, or your holdings</div>}
          </div>
          {symResults.length > 0 &&
          <div style={{ position: "absolute", left: 0, right: 0, top: "calc(100% + 4px)", zIndex: 40, background: "var(--bg-card)", border: "1px solid var(--rule-2)", borderRadius: 10, boxShadow: "0 12px 28px -14px rgba(0,0,0,0.35)", overflow: "hidden" }}>
            {symResults.map((m, i) =>
            <button key={m.id} className="press" onMouseDown={(e) => { e.preventDefault(); setSymQ(""); onPickSymbol(m.id); }} style={{ width: "100%", textAlign: "left", display: "flex", alignItems: "center", gap: 10, padding: "9px 13px", background: "none", border: "none", borderTop: i ? "1px solid var(--rule)" : "none", cursor: "pointer" }}>
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: 500, minWidth: 46 }}>{m.ticker}</span>
              <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)", flex: 1, minWidth: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{m.name}</span>
              <span style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(12), fontVariantNumeric: "proportional-nums", flex: "none" }}>{usd(m.last, 2)}</span>
            </button>)}
          </div>}
        </div>
        )}

        {sel && <TradeMarketQuote sel={sel} />}

        {/* action */}
        <Eyebrow style={{ marginTop: 18 }}>Action</Eyebrow>
        <div style={{ marginTop: 7 }}>
          <Segmented options={[{ value: "BUY", label: "Buy" }, { value: "SELL", label: "Sell" }]} value={side} onChange={setSide} />
        </div>

        {/* sell-only · dollars or shares (buy is dollars only) */}
        {side === "SELL" &&
        <div style={{ marginTop: 10 }}>
          <Segmented options={[{ value: "usd", label: "Dollars" }, { value: "shares", label: "Shares" }]} value={unit} onChange={(u) => {setUnit(u);setAmt("");}} />
        </div>}

        {/* amount */}
        <Eyebrow style={{ marginTop: 18 }}>Amount <span style={{ textTransform: "none", letterSpacing: 0, color: "var(--ink-3)", fontWeight: 500 }}>({availLabel})</span></Eyebrow>
        <div onClick={() => openPad("amt")} style={{ ...fieldStyle(focus === "amt" && padOpen), marginTop: 7 }}>
          {!isShares && <span style={{ fontFamily: "var(--f-mono)", fontSize: "var(--ld-type-value)", color: "var(--ink-3)" }}>$</span>}
          <span style={{ flex: 1, minWidth: 0, fontFamily: "var(--f-mono)", fontSize: "var(--ld-type-value)", color: amt ? "var(--ink)" : "var(--ink-3)", fontVariantNumeric: "proportional-nums", whiteSpace: "nowrap", overflow: "hidden" }}>{amt ? commaAmt(amt) : "Enter amount"}</span>
          {sel && isShares && amt && <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), color: "var(--ink-3)", flex: "none" }}>{sel.kind === "crypto" ? sel.ticker : "sh"}</span>}
          {amt && <button className="press" onClick={(e) => {e.stopPropagation();setAmt("");}} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", padding: 0, flex: "none" }}><Icon name="close" size={16} /></button>}
        </div>
        <div className="ld-trade-presets" role="group" aria-label="Amount shortcuts">
          {[["25%", 0.25], ["50%", 0.5], ["75%", 0.75], ["Max", 1]].map(([lbl, f]) =>
          <button key={lbl} className="press ld-trade-preset" onClick={() => setPct(f)}>{lbl}</button>
          )}
        </div>
        {sel && insufficient &&
        <div role="alert" style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), color: "var(--signal-neg)", marginTop: 8 }}>Insufficient funds — you can trade up to {usd(avail)}.</div>}

        <div style={{ height: 16 }} />
      </div>

      <div style={{ flex: "none", padding: "12px 20px 18px", borderTop: "1px solid var(--rule)" }}>
        <Btn full disabled={!canConfirm} onClick={onReview}>{marketClosed ? <>Schedule for open</> : <>Confirm</>}</Btn>
        <div style={{ fontFamily: "var(--f-display)", fontSize: "var(--ld-type-caption)", color: "var(--ink-3)", textAlign: "center", marginTop: 10, lineHeight: 1.5 }}>
          {marketClosed ? "Markets closed — this order executes tomorrow at market open." : "Executes as a market order."}
        </div>
      </div>


      {/* keypad sheet — opens on a field tap */}
      {padOpen &&
      <>
        <div onClick={() => setPadOpen(false)} style={{ position: "absolute", inset: 0, zIndex: 40 }} />
        <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 41, background: "var(--bg)", borderTop: "1px solid var(--accent)", animation: "sheet-in 240ms cubic-bezier(0.16,1,0.30,1) both" }}>
          <div style={{ padding: "10px 20px 4px", display: "flex", alignItems: "center" }}>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12), fontWeight: 500, letterSpacing: "normal", textTransform: "uppercase", color: "var(--ink-3)" }}>{focus === "stop" ? "Stop price" : "Amount"}</span>
            <button className="press" onClick={() => setPadOpen(false)} style={{ marginLeft: "auto", background: "none", border: "none", color: "var(--accent)", fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: 500, cursor: "pointer" }}>Done</button>
          </div>
          <Keypad onKey={onKey} />
          <div style={{ height: 8 }} />
        </div>
      </>}
    </>);
};


const PreviewLine = ({ label, value, hint, last }) =>
<div style={{ display: "flex", alignItems: "baseline", gap: 8, padding: "10px 13px", borderBottom: last ? "none" : "1px dashed var(--rule)" }}>
    <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-2)" }}>{label}</span>
    {hint && <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(10), color: "var(--ink-3)" }}>· {hint}</span>}
    <span style={{ marginLeft: "auto", fontFamily: "var(--f-mono)", fontSize: typeSize(12.5), fontWeight: 500, fontVariantNumeric: "proportional-nums" }}>{value}</span>
  </div>;



/* ============================================================
   Link external account sheet
   ============================================================ */
const LinkSheet = ({ onClose }) =>
<>
    <div className="yo-focus-backdrop" onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)", zIndex: 300 }} />
    <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 301, background: "var(--bg)", borderTop: "1px solid var(--accent)", animation: "sheet-in 320ms cubic-bezier(0.16,1,0.30,1) both" }}>
      <div style={{ display: "flex", justifyContent: "center", paddingTop: 8 }}><span style={{ width: 36, height: 4, background: "var(--rule-2)", borderRadius: 999 }} /></div>
      <div style={{ padding: "10px 20px 4px", display: "flex", alignItems: "center" }}>
        <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(17), fontWeight: 500, letterSpacing: "normal" }}>Link an account</span>
        <button className="press" onClick={onClose} style={{ marginLeft: "auto", background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={19} /></button>
      </div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-2)", padding: "4px 20px 0", lineHeight: 1.5 }}>Pick an institution to link with Plaid.</div>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 8, padding: "16px 20px 24px" }}>
        {[["Chase", "C"], ["BofA", "B"], ["Schwab", "S"], ["Fidelity", "F"], ["Wells", "W"], ["Coinbase", "₿"]].map(([n, g]) =>
      <button key={n} className="press" onClick={onClose} style={{ padding: "14px 6px", background: "var(--bg-card)", border: "1px solid var(--rule)", display: "flex", flexDirection: "column", alignItems: "center", gap: 7, cursor: "pointer" }}>
            <span style={{ width: 30, height: 30, background: "var(--ink)", color: "var(--bg-card)", display: "grid", placeItems: "center", fontFamily: "var(--f-display)", fontSize: typeSize(15), fontWeight: 500 }}>{g}</span>
            <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), fontWeight: 500 }}>{n}</span>
          </button>
      )}
      </div>
    </div>
  </>;


Object.assign(window, { TradeSheet, LinkSheet });

/* ============================================================
   Agents · MCP + supported apps
   ============================================================ */
const MCP_CONFIG = `{
  "mcpServers": {
    "yoshi": {
      "url": "https://agents.yoshi.invalid/mcp"
    }
  }
}`;
const CHATGPT_APP_URL = "https://chatgpt.com/apps/yoshi/asdk_app_69b99bd669a481919598a5495d38a0f7";
const SetupNote = ({ children }) =>
  <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11.5), color: "var(--ink-3)", lineHeight: 1.5, margin: "10px 20px 0" }}>{children}</div>;


/* ============================================================
   Agents · MCP + supported apps
   ============================================================ */
const useCopy = () => {
  const [copied, setCopied] = useState(false);
  const copy = (val) => { try { navigator.clipboard && navigator.clipboard.writeText(val); } catch (_) {} setCopied(true); setTimeout(() => setCopied(false), 1600); };
  return [copied, copy];
};

const YOSHI_METHODS = [
  { id: "mcp", name: "Yoshi MCP", icon: "connect", desc: "agents.yoshi.invalid/mcp", action: "copy", copy: MCP_CONFIG, cta: "Copy URL" },
];

const AGENT_APP_ICONS = {
  chatgpt: "assets/agent-chatgpt.svg",
  claude: "assets/agent-claude.svg",
  perplexity: "assets/agent-perplexity.svg",
};
const AGENT_APP_ICON_FILTER = "brightness(0) invert(1)";

const AGENT_APPS = [
  { id: "chatgpt", name: "ChatGPT", icon: "message", iconSrc: AGENT_APP_ICONS.chatgpt, iconFilter: AGENT_APP_ICON_FILTER, kind: "authorize", url: CHATGPT_APP_URL },
  { id: "claude", name: "Claude", icon: "robot", iconSrc: AGENT_APP_ICONS.claude, iconFilter: AGENT_APP_ICON_FILTER, kind: "connected" },
  { id: "perplexity", name: "Perplexity", icon: "search", iconSrc: AGENT_APP_ICONS.perplexity, iconFilter: AGENT_APP_ICON_FILTER, kind: "soon" },
  { id: "cursor", name: "Cursor", icon: "terminal", kind: "soon" },
];

const TILE_ACTION_HEIGHT = window.__YOSHI_WEB ? 38 : 28;

const CopyButtonLabel = ({ copied = false, size = 12 }) => <CopyActionLabel copied={copied} size={size} />;

const TileBtn = ({ children, onClick, href, tone = "default" }) => {
  const web = !!window.__YOSHI_WEB;
  const st = { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6, width: "100%", height: TILE_ACTION_HEIGHT, minHeight: TILE_ACTION_HEIGHT, boxSizing: "border-box", padding: web ? "0 12px" : "0 8px", borderRadius: web ? 10 : 9, cursor: "pointer", fontFamily: "var(--f-display)", fontSize: typeSize(web ? 13 : 11), fontWeight: 500, lineHeight: 1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", border: "1px solid var(--rule-2)", background: "color-mix(in srgb, var(--ink) 4%, var(--bg-card))", color: "var(--ink)", textDecoration: "none" };
  if (tone === "accent") { st.background = "var(--accent)"; st.color = "var(--accent-ink)"; st.border = "none"; }
  if (tone === "pos") { st.color = "var(--accent-pos)"; st.borderColor = "color-mix(in srgb, var(--accent-pos) 45%, var(--rule-2))"; }
  if (tone === "danger") { st.color = "var(--signal-neg)"; st.borderColor = "color-mix(in srgb, var(--signal-neg) 40%, transparent)"; }
  const label = <span style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 5, minWidth: 0, maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis" }}>{children}</span>;
  if (href) return <a className="press" href={href} target="_blank" rel="noopener noreferrer" style={st} onClick={(e) => { e.stopPropagation(); onClick && onClick(); }}>{label}</a>;
  return <button className="press" onClick={(e) => { e.stopPropagation(); onClick && onClick(); }} style={st}>{label}</button>;
};

const TileActionSlot = ({ children }) => (
  <div style={{ flex: "none", width: "100%", height: TILE_ACTION_HEIGHT, minHeight: TILE_ACTION_HEIGHT, display: "flex", alignItems: "stretch" }}>
    {children}
  </div>
);

const AppIcon = ({ src, alt = "", size, filter }) => (
  <img src={src} alt={alt} style={{ width: size, height: size, display: "block", objectFit: "contain", filter }} />
);

const ConnectTile = ({ icon, iconSrc, iconFilter, name, desc, primary, onClick, children }) => {
  const web = !!window.__YOSHI_WEB;
  return (
  <div onClick={onClick} role={onClick ? "button" : undefined} tabIndex={onClick ? 0 : undefined} onKeyDown={onClick ? (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onClick(e); } } : undefined} className={onClick ? "press" : undefined} style={{ position: "relative", display: "flex", flexDirection: "column", gap: web ? 10 : 6, padding: web ? 16 : 9, aspectRatio: "1 / 1", borderRadius: 14, overflow: "hidden", cursor: onClick ? "pointer" : "default", background: primary ? "color-mix(in srgb, var(--accent-pos) 9%, var(--bg-card))" : "var(--bg-card)", border: "1px solid " + (primary ? "color-mix(in srgb, var(--accent-pos) 45%, var(--rule-2))" : "var(--rule-2)") }}>
    {primary && <span style={{ position: "absolute", top: 0, left: 0, right: 0, height: 2, background: "var(--accent-pos)" }} />}
    <span style={{ width: web ? 30 : 24, height: web ? 30 : 24, flex: "none", display: "grid", placeItems: "center", color: "var(--ink)" }}>
      {iconSrc ? <AppIcon src={iconSrc} size={web ? 24 : 19} filter={iconFilter} /> : <Icon name={icon} size={web ? 24 : 19} stroke={1.5} />}
    </span>
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(web ? 16 : 12), fontWeight: 500, letterSpacing: 0, lineHeight: 1.15, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{name}</div>
    </div>
    <TileActionSlot>{children}</TileActionSlot>
  </div>
  );
};

const MethodTile = ({ m, onOpen }) => {
  const [copied, copy] = useCopy();
  const activate = () => {
    if (m.action === "copy") copy(m.copy);
    else onOpen(m.action);
  };
  const body = m.action === "copy"
    ? <TileBtn onClick={activate} tone={copied ? "pos" : "default"}>{copied ? <CopyButtonLabel copied>Copied</CopyButtonLabel> : <CopyButtonLabel>{m.cta}</CopyButtonLabel>}</TileBtn>
    : <TileBtn onClick={activate}>{m.cta}</TileBtn>;
  return <ConnectTile icon={m.icon} name={m.name} desc={m.desc} onClick={activate}>{body}</ConnectTile>;
};

const ConnectedStatus = ({ label = "Connected", mono = false }) => (
  <span style={{ display: "inline-flex", alignItems: "center", gap: 6, minHeight: window.__YOSHI_WEB ? 18 : 14, fontFamily: mono ? "var(--f-mono)" : "var(--f-display)", fontSize: typeSize(window.__YOSHI_WEB ? 11 : 10), fontWeight: 500, letterSpacing: mono ? "0.08em" : 0, color: "var(--accent-pos)" }}>
    <span className={mono ? "yo-mk-pulse" : undefined} style={{ width: mono ? 7 : 6, height: mono ? 7 : 6, borderRadius: 999, background: "var(--accent-pos)" }} />{label}
  </span>
);

const AppTile = ({ app, onAuthorize, onEdit }) => {
  const [copied, copy] = useCopy();
  const [soon, setSoon] = useState(false);
  const authorize = () => onAuthorize && onAuthorize(app.id);
  const openAuthorizedApp = () => {
    authorize();
    if (app.url) window.open(app.url, "_blank", "noopener,noreferrer");
  };
  const copySetup = () => copy(app.copy);
  const markSoon = () => { setSoon(true); setTimeout(() => setSoon(false), 1600); };
  const tileAction = app.connected
    ? () => onEdit && onEdit(app.id)
    : app.kind === "connected"
      ? authorize
      : app.kind === "authorize"
        ? openAuthorizedApp
        : app.kind === "copy"
          ? copySetup
          : markSoon;
  let action;
  if (app.connected)
    action = <ConnectionMiniButton tone="danger" onClick={() => onEdit && onEdit(app.id)}>Revoke</ConnectionMiniButton>;
  else if (app.kind === "connected")
    action = <TileBtn onClick={authorize}>Authorize</TileBtn>;
  else if (app.kind === "authorize")
    action = <TileBtn href={app.url} onClick={authorize}>Authorize</TileBtn>;
  else if (app.kind === "copy")
    action = <TileBtn onClick={copySetup} tone={copied ? "pos" : "default"}>{copied ? <CopyButtonLabel copied>Copied</CopyButtonLabel> : <CopyButtonLabel>Copy</CopyButtonLabel>}</TileBtn>;
  else
    action = <TileBtn onClick={markSoon}>{soon ? "Coming soon" : "Authorize"}</TileBtn>;
  return <ConnectTile icon={app.icon} iconSrc={app.iconSrc} iconFilter={app.iconFilter} name={app.name} primary={app.connected} onClick={tileAction}>{action}</ConnectTile>;
};

const SubHead = ({ title, onBack }) => (
  <div style={{ flex: "none", padding: "6px 16px 10px", borderBottom: "1px solid var(--rule)", display: "flex", alignItems: "center", gap: 8, minHeight: 44 }}>
    <button className="press" onClick={onBack} aria-label="Back" style={{ background: "none", border: "none", display: "flex", color: "var(--ink)", padding: 6, margin: "0 -6px" }}><Icon name="back" size={20} /></button>
    <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(17), fontWeight: 500, letterSpacing: "normal", flex: 1 }}>{title}</span>
  </div>
);

const SheetActionRow = ({ icon, label, tone = "default", onClick }) => {
  const danger = tone === "danger";
  return (
    <button className="press" onClick={onClick} style={{ width: "100%", display: "flex", alignItems: "center", gap: 10, padding: "13px 14px", background: "none", border: "none", borderBottom: label === "Rename" ? "1px solid var(--rule)" : "none", color: danger ? "var(--signal-neg)" : "var(--ink)", fontFamily: "var(--f-display)", fontSize: typeSize(13.5), fontWeight: 500, textAlign: "left", cursor: "pointer" }}>
      <Icon name={icon} size={16} color={danger ? "var(--signal-neg)" : "var(--ink)"} stroke={1.5} />
      <span style={{ flex: 1 }}>{label}</span>
      <Icon name="back" size={13} color={danger ? "var(--signal-neg)" : "var(--ink-3)"} style={{ transform: "scaleX(-1)" }} />
    </button>
  );
};

const EditActionsSheet = ({ subject, icon, iconSrc, iconFilter, statusLabel, statusMono, showRename = false, onClose, onRename, onRevoke }) => (
  <div style={{ position: "fixed", inset: 0, zIndex: 360, display: "flex", alignItems: "flex-end", justifyContent: "center" }} data-screen-label="Edit actions">
    <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.55)", backdropFilter: "blur(2px)", WebkitBackdropFilter: "blur(2px)" }} />
    <div role="dialog" aria-modal="true" className="push-enter" style={{ position: "relative", width: "100%", maxWidth: window.__YOSHI_WEB ? 390 : "none", boxSizing: "border-box", background: "var(--bg)", border: "1px solid var(--rule-2)", borderBottom: "none", borderRadius: "18px 18px 0 0", padding: "10px 20px 24px", boxShadow: "0 -20px 60px -28px rgba(0,0,0,0.75)" }}>
      <div style={{ width: 36, height: 4, borderRadius: 999, background: "var(--rule-2)", margin: "0 auto 16px" }} />
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <span style={{ width: 42, height: 42, flex: "none", border: "1px solid var(--rule-2)", borderRadius: 12, display: "grid", placeItems: "center", color: "var(--ink)" }}>
          {iconSrc ? <AppIcon src={iconSrc} size={21} filter={iconFilter} /> : <Icon name={icon} size={21} stroke={1.5} />}
        </span>
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(17), fontWeight: 500, letterSpacing: 0, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{subject}</div>
          <div style={{ marginTop: 3 }}><ConnectedStatus label={statusLabel} mono={statusMono} /></div>
        </div>
      </div>
      <div style={{ marginTop: 18, border: "1px solid var(--rule-2)", borderRadius: 12, background: "var(--bg-card)", overflow: "hidden" }}>
        {showRename && <SheetActionRow icon="pencil" label="Rename" onClick={onRename} />}
        <SheetActionRow icon="close" label="Revoke" tone="danger" onClick={onRevoke} />
      </div>
      <Btn kind="ghost" full onClick={onClose} style={{ marginTop: 10 }}>Cancel</Btn>
    </div>
  </div>
);


/* the example live connection — created from a Copy setup and named by the
   user during the MCP handshake. */
const AGENT_CONNECTIONS_LIVE = [
  { id: "c1", name: "portfolio-copilot", sub: "Yoshi MCP · named at setup · connected May 28", live: true },
];

const ConnectionMiniButton = ({ children, onClick, tone = "default" }) => <TileBtn onClick={onClick} tone={tone}>{children}</TileBtn>;

/* a live connection renders as a tile alongside the others — the user named
   it during the MCP handshake after tapping Copy setup */
const ConnectionTile = ({ c, onEdit }) => (
  <ConnectTile icon="connect" name={c.name} primary onClick={() => onEdit && onEdit(c.id)}>
    <ConnectionMiniButton onClick={() => onEdit && onEdit(c.id)}>Edit</ConnectionMiniButton>
  </ConnectTile>
);

const ConnectionRenameView = ({ conn, onBack, onSave }) => {
  const [draft, setDraft] = useState(conn.name);
  const nextName = draft.trim();
  const save = () => { if (!nextName) return; onSave(conn.id, nextName); };
  return (
    <>
      <div onClick={onBack} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.4)", zIndex: 360, animation: "scrim-in 200ms ease both" }} />
      <div className="push-enter" style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 361, background: "var(--bg)", borderTop: "1px solid var(--rule-2)", borderRadius: "16px 16px 0 0", animation: "sheet-in 300ms cubic-bezier(0.16,1,0.30,1) both" }} data-screen-label="Rename connection">
        <div style={{ display: "flex", justifyContent: "center", paddingTop: 8 }}><span style={{ width: 36, height: 4, background: "var(--rule-2)", borderRadius: 999 }} /></div>
        <div style={{ padding: "14px 18px max(26px,env(safe-area-inset-bottom))" }}>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 500, letterSpacing: "normal", marginBottom: 16 }}>Edit nickname</div>
          <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), fontWeight: 500, color: "var(--ink-2)", marginBottom: 6 }}>Agent nickname</div>
          <input autoFocus value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") save(); }}
            style={{ width: "100%", padding: "12px 13px", background: "var(--bg-2)", border: "1px solid var(--accent)", borderRadius: 10, outline: "none", fontFamily: "var(--f-display)", fontSize: typeSize(15), fontWeight: 500, color: "var(--ink)" }} />
          <button className="press" onClick={save} style={{ width: "100%", marginTop: 14, padding: "12px", background: "var(--accent)", border: "none", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500, cursor: "pointer", color: "var(--accent-ink)" }}>Save</button>
        </div>
      </div>
    </>
  );
};

const RevokeConnectionConfirmSheet = ({ subject, onBack, onApprove }) => (
  <>
    <div onClick={onBack} style={{ position: "absolute", inset: 0, background: "rgba(0,0,0,0.4)", zIndex: 360, animation: "scrim-in 200ms ease both" }} />
    <div className="push-enter" style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 361, background: "var(--bg)", borderTop: "1px solid var(--rule-2)", borderRadius: "16px 16px 0 0", animation: "sheet-in 300ms cubic-bezier(0.16,1,0.30,1) both" }} data-screen-label="Revoke connection">
      <div style={{ display: "flex", justifyContent: "center", paddingTop: 8 }}><span style={{ width: 36, height: 4, background: "var(--rule-2)", borderRadius: 999 }} /></div>
      <div style={{ padding: "14px 18px max(26px,env(safe-area-inset-bottom))" }}>
        <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(16), fontWeight: 500, letterSpacing: "normal", marginBottom: 8 }}>Revoke connection?</div>
        <p style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13.5), lineHeight: 1.5, color: "var(--ink-2)", margin: 0 }}>Are you sure you want to revoke the connection to {subject}?</p>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 9, marginTop: 16 }}>
          <button className="press" onClick={onBack} style={{ width: "100%", padding: "12px", background: "transparent", border: "1px solid color-mix(in srgb, var(--signal-neg) 40%, transparent)", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500, cursor: "pointer", color: "var(--signal-neg)" }}>Decline</button>
          <button className="press" onClick={onApprove} style={{ width: "100%", padding: "12px", background: "var(--accent)", border: "none", borderRadius: 10, fontFamily: "var(--f-display)", fontSize: typeSize(14), fontWeight: 500, cursor: "pointer", color: "var(--accent-ink)" }}>Approve</button>
        </div>
      </div>
    </div>
  </>
);

const ConnectionRevokeView = ({ conn, onBack, onConfirm }) => {
  const [gate, setGate] = useState(false);
  return (
    <>
      <RevokeConnectionConfirmSheet subject={conn.name} onBack={onBack} onApprove={() => setGate(true)} />
      {gate && <PasskeyGate title={`Revoke ${conn.name}`} detail={`${conn.name} loses access immediately and will be removed from Agents.`} cta="Confirm" onSuccess={() => { setGate(false); onConfirm(conn.id); }} onCancel={() => setGate(false)} />}
    </>
  );
};

const AgentConnectionsSection = () => (
  <>
    <div style={{ padding: "20px 20px 0" }}><Eyebrow>Agent connections</Eyebrow></div>
    <div style={{ margin: "10px 20px 0", border: "1px solid var(--rule-2)", borderRadius: 12, background: "var(--bg-card)", overflow: "hidden" }}>
      {AGENT_CONNECTIONS_LIVE.map((c, i) => (
        <div key={c.id} style={{ display: "flex", alignItems: "center", gap: 12, padding: "13px 14px", borderTop: i ? "1px solid var(--rule)" : "none" }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--f-mono)", fontSize: typeSize(13), fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{c.name}</div>
            <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-3)", marginTop: 3 }}>{c.sub}</div>
          </div>
          <span style={{ flex: "none", display: "inline-flex", alignItems: "center", gap: 6, fontFamily: "var(--f-mono)", fontSize: typeSize(10), fontWeight: 500, letterSpacing: "normal", color: "var(--accent-pos)" }}>
            <span className="yo-mk-pulse" style={{ width: 7, height: 7, borderRadius: 999, background: "var(--accent-pos)", opacity: 1 }} />LIVE
          </span>
        </div>
      ))}
    </div>
    <SetupNote>A connection lands here after the agent completes the handshake from a copied setup. You name it during setup.</SetupNote>
  </>
);

/* The agents controls, extracted so they can live either as a standalone
   sheet (ConnectAgentsSheet) or inline inside the hamburger menu (ProfileSheet).
   The manage/authorize/revoke sub-sheets overlay at zIndex 320+, so they sit
   above both the standalone sheet and the menu. */
const AgentsPanel = ({ intro = true }) => {
  const [view, setView] = useState(null);
  const [apps, setApps] = useState(() => AGENT_APPS.map((app) => ({ ...app, connected: app.kind === "connected" })));
  const [managedAppId, setManagedAppId] = useState(null);
  const [revokingAppId, setRevokingAppId] = useState(null);
  const [connections, setConnections] = useState(AGENT_CONNECTIONS_LIVE);
  const [managedConnection, setManagedConnection] = useState(null);
  const appTarget = managedAppId && apps.find((app) => app.id === managedAppId);
  const revokingApp = revokingAppId && apps.find((app) => app.id === revokingAppId);
  const connectionTarget = managedConnection && connections.find((c) => c.id === managedConnection.id);
  const authorizeApp = (id) => {
    setApps((as) => as.map((app) => app.id === id ? { ...app, connected: true } : app));
  };
  const revokeApp = (id) => {
    setApps((as) => as.map((app) => app.id === id ? { ...app, connected: false } : app));
    setManagedAppId(null);
    setRevokingAppId(null);
  };
  const renameConnection = (id, name) => {
    setConnections((cs) => cs.map((c) => c.id === id ? { ...c, name } : c));
    setManagedConnection(null);
  };
  const revokeConnection = (id) => {
    setConnections((cs) => cs.filter((x) => x.id !== id));
    setManagedConnection(null);
  };
  return (
    <>
      {intro && <div style={{ fontFamily: "var(--f-display)", fontSize: typeSize(12.5), color: "var(--ink-2)", padding: "4px 20px 0", lineHeight: 1.5 }}>Connect Yoshi over MCP, or authorize a supported app.</div>}

      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 9, padding: "16px 20px 4px" }}>
        {YOSHI_METHODS.map((m) => <MethodTile key={m.id} m={m} onOpen={setView} />)}
        {apps.map((a) => <AppTile key={a.id} app={a} onAuthorize={authorizeApp} onEdit={(id) => setManagedAppId(id)} />)}
        {connections.map((c) => <ConnectionTile key={c.id} c={c} onEdit={(id) => setManagedConnection({ type: "actions", id })} />)}
      </div>

      <div style={{ display: "flex", gap: 9, alignItems: "flex-start", margin: "20px 20px 0", padding: "10px 12px", background: "var(--bg-2)" }}>
        <Icon name="shield" size={16} color="var(--ink-3)" stroke={1.5} style={{ marginTop: 1 }} />
        <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(11), color: "var(--ink-3)", lineHeight: 1.45 }}>Propose-only access. An agent never holds or moves your money on its own. Revoke any connection anytime.</span>
      </div>

      {managedAppId && appTarget && <EditActionsSheet subject={appTarget.name} icon={appTarget.icon} iconSrc={appTarget.iconSrc} iconFilter={appTarget.iconFilter} statusLabel="Connected" onClose={() => setManagedAppId(null)} onRevoke={() => { setManagedAppId(null); setRevokingAppId(appTarget.id); }} />}
      {revokingAppId && revokingApp && <RevokeConnectionConfirmSheet subject={revokingApp.name} onBack={() => setRevokingAppId(null)} onApprove={() => revokeApp(revokingApp.id)} />}
      {managedConnection?.type === "actions" && connectionTarget && <EditActionsSheet subject={connectionTarget.name} icon="connect" statusLabel="LIVE" statusMono showRename onClose={() => setManagedConnection(null)} onRename={() => setManagedConnection({ type: "rename", id: connectionTarget.id })} onRevoke={() => setManagedConnection({ type: "revoke", id: connectionTarget.id })} />}
      {managedConnection?.type === "rename" && connectionTarget && <ConnectionRenameView conn={connectionTarget} onBack={() => setManagedConnection(null)} onSave={renameConnection} />}
      {managedConnection?.type === "revoke" && connectionTarget && <ConnectionRevokeView conn={connectionTarget} onBack={() => setManagedConnection(null)} onConfirm={revokeConnection} />}
    </>);
};

const ConnectAgentsSheet = ({ onClose }) => (
  <div className="push-enter" style={{ position: "absolute", inset: 0, zIndex: 320, background: "var(--bg)", display: "flex", flexDirection: "column" }} data-screen-label="Agents">
    {window.StatusBar && <window.StatusBar />}
    <div style={{ flex: "none", padding: window.__YOSHI_WEB ? "26px 20px 6px" : "6px 16px 10px", borderBottom: window.__YOSHI_WEB ? "none" : "1px solid var(--rule)", display: "flex", alignItems: "center", gap: 10, minHeight: 44 }}>
      <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(window.__YOSHI_WEB ? 21 : 17), fontWeight: window.__YOSHI_WEB ? 500 : 500, letterSpacing: window.__YOSHI_WEB ? "-0.025em" : "-0.02em", flex: 1 }}>Agents</span>
      {!window.__YOSHI_WEB && <button className="press" onClick={onClose} style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)" }}><Icon name="close" size={20} /></button>}
    </div>
    <div className="scroll" style={{ overflowY: "auto", padding: "0 0 24px" }}>
      <AgentsPanel />
    </div>
  </div>
);

Object.assign(window, { ConnectAgentsSheet, AgentsPanel });
