/* simple/app.jsx — the redesigned shell, DESKTOP edition.
   Same state machine as the mobile simple app, re-laid out as three
   persistent pillars: Yoshi chat (left) · Home (center) · Activity (right),
   under a slim top bar (menu · wordmark · bell). A selection takes over the
   area right of chat; Trade / Transfer render their WIDE desktop two-pane
   layouts (market context + ticket, form + more ways), the rest center.
   No phone chrome: window.StatusBar stays undefined so reused screens skip
   their status-bar row, and the pillar wraps hide per-screen headers. */

/* identify as the web build — flips reused components (Trade / Transfer /
   detail screens) to their desktop layouts and the type scale to web sizes,
   exactly like the main web app. Set before first render. */
window.__YOSHI_WEB = true;

const Toast = ({ msg }) => (
  <div style={{ position: "fixed", left: "50%", transform: "translateX(-50%)", bottom: 26, zIndex: 1600, background: "var(--terminal-fill)", color: "var(--terminal-ink)", padding: "12px 16px", display: "flex", alignItems: "center", gap: 9, animation: "count-up 240ms ease both", boxShadow: "0 8px 30px -10px rgba(0,0,0,0.5)", borderRadius: 10 }}>
    <span style={{ width: 6, height: 6, borderRadius: 999, background: "var(--accent)", flex: "none" }} />
    <span style={{ fontFamily: "var(--f-display)", fontSize: typeSize(13), fontWeight: 500, whiteSpace: "nowrap" }}>{msg}</span>
  </div>
);

/* the reused phone sheets are position:absolute inset:0 with their own header +
   close. Instead of a modal, a selection (Move / Trade / Accounts / …) takes
   over the whole area to the right of chat — the center AND the Activity pillar,
   like the main web app's flow panes — with the sheet content centered. Close /
   back returns to Home. willChange:transform makes the inner frame a containing
   block so the sheets' fixed-position sub-sheets (kebab / info / options) anchor
   inside the pane instead of escaping to the browser viewport. */
const CenterPage = ({ wide, onClose, children }) => (
  <div className="tab-swap" style={{ flex: 1, minWidth: 0, position: "relative", display: "flex", justifyContent: "center", overflow: "hidden", background: "var(--bg)" }}>
    <div style={{ position: "relative", width: "100%", maxWidth: wide ? "none" : 720, minHeight: 0, display: "flex", flexDirection: "column", overflow: "hidden", willChange: "transform" }}>
      {children}
    </div>
    {/* flows whose reused web layout carries no close of its own (Trade /
        Transfer) get one here, so there's always a way back to Home */}
    {onClose && (
      <button className="press" onClick={onClose} aria-label="Close" title="Close" style={{ position: "absolute", top: 12, right: 16, zIndex: 50, background: "var(--bg-2)", border: "1px solid var(--rule-2)", borderRadius: 999, width: 32, height: 32, display: "grid", placeItems: "center", color: "var(--ink-2)", cursor: "pointer" }}>
        <Icon name="close" size={18} />
      </button>
    )}
  </div>
);

/* compact accent action for the top bar (Move / Trade), matching the labeled
   Briefs bell so the three read as one cluster */
/* topbar quick action on the Btn atom — filled = solid accent (primary);
   otherwise an accent outline with white (ink) icon + label */
const TopAction = ({ icon, label, onClick, filled }) => (
  <Btn size="md" kind={filled ? "primary" : "ghost"} onClick={onClick} style={filled ? { border: "1px solid transparent" } : { border: "1px solid var(--accent)", background: "var(--bg-2)", color: "var(--ink)" }}>
    <Icon name={icon} size={16} color={filled ? "var(--accent-ink)" : "var(--ink)"} />{label}
  </Btn>
);

/* column geometry — chat (act) and Activity (verify) are resizable; the center
   (see) is prioritized and never drops below CENTER_MIN. Below the 3-column
   threshold the Activity pillar drops away (two columns); below MOBILE_MIN the
   web app blocks and points to the phone. Mirrors the main web app. */
const CHAT_MIN = 300, CHAT_MAX = 560;
const ACT_MIN = 300, ACT_MAX = 520;
const CENTER_MIN = 460;
const MOBILE_MIN = 768;

/* a draggable seam. side="right" sits on a column's right edge (chat),
   side="left" on the left edge (Activity). clamp() keeps every column within
   its bounds AND preserves CENTER_MIN for the middle. */
const ColResizer = ({ side, width, setWidth, clamp }) => {
  const drag = useRef(null);
  const onDown = (e) => { drag.current = { x: e.clientX, w: width }; e.currentTarget.setAttribute("data-drag", "1"); try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {} };
  const onMove = (e) => { if (!drag.current) return; const dx = e.clientX - drag.current.x; setWidth(clamp(drag.current.w + (side === "left" ? -dx : dx))); };
  const onUp = (e) => { drag.current = null; e.currentTarget.removeAttribute("data-drag"); };
  return <div className={"col-resize" + (side === "left" ? " col-resize-left" : "")} onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp} title="Drag to resize" />;
};

/* below MOBILE_MIN the desktop pillars don't fit — point to the phone app. */
const MobileBlock = () => (
  <div style={{ position: "fixed", inset: 0, zIndex: 5000, background: "var(--bg)", color: "var(--ink)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
    <div aria-hidden="true" style={{ position: "absolute", top: 0, left: 0, right: 0, height: 3, background: "linear-gradient(90deg, transparent, var(--accent) 20%, var(--accent) 80%, transparent)", opacity: 0.9 }} />
    <div style={{ width: "100%", maxWidth: 340, transform: "translateY(-4vh)", textAlign: "center" }}>
      <div style={{ display: "flex", justifyContent: "center" }}><Logo size={34} /></div>
      <div style={{ marginTop: 24, fontFamily: "var(--f-mono)", fontSize: 11, letterSpacing: "0.16em", textTransform: "uppercase", color: "var(--accent)" }}>Mobile app required</div>
      <h1 style={{ margin: "12px 0 0", fontFamily: "var(--f-display)", fontSize: 30, lineHeight: 1.08, fontWeight: 600, letterSpacing: "-0.03em" }}>Use Yoshi on your phone</h1>
      <p style={{ margin: "13px auto 0", maxWidth: 320, fontFamily: "var(--f-display)", fontSize: 15, lineHeight: 1.55, color: "var(--ink-2)" }}>Yoshi&rsquo;s web app is built for desktop. To manage your money on this device, open the Yoshi mobile app.</p>
    </div>
  </div>
);

/* the empty reading-pane state shared by the Briefs / Automations takeovers */
const InboxEmpty = ({ title, sub }) => (
  <div style={{ flex: 1, display: "grid", placeItems: "center", padding: 40 }}>
    <div style={{ textAlign: "center", maxWidth: 300 }}>
      <div style={{ width: 48, height: 48, margin: "0 auto", borderRadius: 999, border: "1px solid var(--rule-2)", display: "grid", placeItems: "center", color: "var(--ink-3)" }}><Logo size={24} /></div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 16, fontWeight: 600, marginTop: 15, letterSpacing: "-0.01em" }}>{title}</div>
      <div style={{ fontFamily: "var(--f-display)", fontSize: 13, color: "var(--ink-3)", marginTop: 7, lineHeight: 1.55 }}>{sub}</div>
    </div>
  </div>
);

/* the raised reading card the selected item renders into */
const InboxCard = ({ children }) => (
  <div style={{ position: "relative", width: "100%", maxWidth: 780, display: "flex", flexDirection: "column", minHeight: 0, margin: "18px 22px", background: "var(--bg)", border: "1px solid var(--rule-2)", borderRadius: 14, overflow: "hidden", boxShadow: "0 22px 48px -20px rgba(0,0,0,0.4), 0 4px 14px -8px rgba(0,0,0,0.2)" }}>
    {children}
  </div>
);

/* Briefs on web — the same full-width two-pane takeover as the main web app.
   It REPLACES the chat pillar (the brief's own thread lives in the reading
   card), with the inbox list on the left (BriefsBoard) and the selected brief
   on the right (BriefDetail, embedded). Reuses the exact briefs.jsx components
   so it tracks main as briefs evolve. */
const WebBriefs = ({ proposals, nav, onClose, onExecute, onDecline, initialBriefId }) => {
  const itemFromProposal = (p) => ({ id: p.id, kind: "approve", icon: "bolt", title: p.title, body: p.why.split(". ")[0] + ".", value: null, tone: p.net >= 0 ? "in" : "out", sec: "needsyou", when: p.agent, agent: p.agent, legs: p.legs, net: p.net, amountLabel: p.amountLabel, amount: p.amount, proposal: p });
  const firstNeedsYou = proposals && proposals.length ? itemFromProposal(proposals[0]) : null;
  const feedList = window.BRIEF_FEED ? window.BRIEF_FEED() : [];
  const initialProposal = initialBriefId ? (proposals || []).find((p) => p.id === initialBriefId) : null;
  // deep-link can also target the pulse's "daily-brief" (Today's read), which
  // BriefsBoard builds from window.MORNING_BRIEF, or any other feed brief
  const dailyBrief = window.MORNING_BRIEF ? { id: "daily-brief", kind: "insight", icon: "bulb", title: "Today's read", body: window.MORNING_BRIEF.body, ask: window.MORNING_BRIEF.ask, when: "Today", sec: "insights" } : null;
  const initialFeed = initialProposal ? null : (initialBriefId === "daily-brief" ? dailyBrief : (initialBriefId ? feedList.find((x) => x.id === initialBriefId) : null));
  const [sel, setSel] = useState(() => (initialProposal ? itemFromProposal(initialProposal) : initialFeed) || firstNeedsYou);
  const idx = sel ? feedList.findIndex((x) => x.id === sel.id) : -1;
  const hasNext = idx >= 0 && idx < feedList.length - 1;
  const advance = () => { if (hasNext) setSel(feedList[idx + 1]); else setSel(null); };
  const exec = (id) => { onExecute && onExecute(id); setSel(null); };
  const decline = (id) => { onDecline && onDecline(id); setSel(null); };
  return (
    <div style={{ flex: 1, minHeight: 0, background: "var(--bg)", display: "flex", flexDirection: "column", animation: "fade-swap 220ms ease both" }} data-screen-label="Briefs">
      <div style={{ flex: 1, minHeight: 0, display: "flex" }}>
        {/* LEFT — the inbox list */}
        <aside style={{ flex: "none", width: 396, minWidth: 320, borderRight: "1px solid var(--rule)", display: "flex", flexDirection: "column", minHeight: 0 }}>
          <div style={{ flex: "none", display: "flex", alignItems: "center", gap: 8, padding: "16px 18px 10px" }}>
            <span style={{ flex: 1, fontFamily: "var(--f-display)", fontSize: 21, fontWeight: 700, letterSpacing: "-0.025em" }}>Briefs</span>
            <button className="press" onClick={onClose} aria-label="Close" title="Close" style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", cursor: "pointer" }}><Icon name="close" size={20} /></button>
          </div>
          <div className="scroll" style={{ paddingBottom: 24 }}>
            <BriefsBoard nav={nav} onOpen={setSel} onClose={onClose} proposals={proposals} onApprove={exec} selectedId={sel && sel.id} />
          </div>
        </aside>
        {/* RIGHT — the selected brief on a raised reading card */}
        <section style={{ flex: 1, minWidth: 0, display: "flex", justifyContent: "center", minHeight: 0, background: "var(--bg-2)" }}>
          {sel ? (
            <InboxCard>
              <div style={{ flex: "none", display: "flex", justifyContent: "flex-end", padding: "10px 14px 0" }}>
                <button className="press" onClick={() => setSel(null)} aria-label="Close brief" title="Close" style={{ width: 28, height: 28, borderRadius: 999, background: "color-mix(in srgb, var(--ink) 7%, var(--bg-2))", border: "none", display: "grid", placeItems: "center", color: "var(--ink-2)", cursor: "pointer", padding: 0 }}><Icon name="close" size={15} /></button>
              </div>
              <BriefDetail key={sel.id} b={sel} embedded onBack={() => setSel(null)} onClose={onClose} nav={nav} onAdvance={advance} hasNext={hasNext} onExecute={exec} onDecline={decline} />
            </InboxCard>
          ) : <InboxEmpty title="Select a brief" sub="Pick anything from the list to read it here." />}
        </section>
      </div>
    </div>
  );
};

/* Automations on web — the same full-width two-pane takeover: the automations
   list on the left, the selected automation's detail on the right (the reused
   AutomationSheet in embedded mode, which carries its own modify/pause thread).
   Mirrors main's Studio → Automations. */
const WebAutomations = ({ nav, onClose, flash, initialId }) => {
  const list = AUTOMATIONS || [];
  const [sel, setSel] = useState(() => list.find((a) => a.id === initialId) || list[0] || null);
  const build = () => { onClose(); nav.ask("Build a new automation", "Sure. Tell me the rule in plain words — like “invest $200 every Friday” or “keep my spendable cash near $15k and move the rest to Reserve.” I'll set it up inside your limits and show you the first run before it executes."); };
  return (
    <div style={{ flex: 1, minHeight: 0, background: "var(--bg)", display: "flex", flexDirection: "column", animation: "fade-swap 220ms ease both" }} data-screen-label="Automations">
      <div style={{ flex: 1, minHeight: 0, display: "flex" }}>
        {/* LEFT — the automations list */}
        <aside style={{ flex: "none", width: 396, minWidth: 320, borderRight: "1px solid var(--rule)", display: "flex", flexDirection: "column", minHeight: 0 }}>
          <div style={{ flex: "none", padding: "16px 18px 12px" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <span style={{ flex: 1, fontFamily: "var(--f-display)", fontSize: 21, fontWeight: 700, letterSpacing: "-0.025em" }}>Automations</span>
              <button className="press" onClick={onClose} aria-label="Close" title="Close" style={{ background: "none", border: "none", display: "flex", color: "var(--ink-3)", cursor: "pointer" }}><Icon name="close" size={20} /></button>
            </div>
            <Btn full onClick={build} style={{ marginTop: 16 }}>Build with Yoshi <Icon name="plus" size={16} color="var(--accent-ink)" /></Btn>
          </div>
          <div className="scroll" style={{ paddingBottom: 24 }}>
            {list.map((a, i) => (
              <div key={a.id} style={{ position: "relative", background: sel && sel.id === a.id ? "var(--bg-2)" : "transparent" }}>
                {sel && sel.id === a.id && <span style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: 3, background: "var(--accent)", zIndex: 1 }} />}
                <AutoRow a={a} last={i === list.length - 1} onOpen={() => setSel(a)} />
              </div>
            ))}
          </div>
        </aside>
        {/* RIGHT — the selected automation on a raised reading card */}
        <section style={{ flex: 1, minWidth: 0, display: "flex", justifyContent: "center", minHeight: 0, background: "var(--bg-2)" }}>
          {sel ? (
            <InboxCard><AutomationSheet key={sel.id} embedded automation={sel} onClose={() => setSel(null)} nav={nav} flash={flash} /></InboxCard>
          ) : <InboxEmpty title="Select an automation" sub="Pick a rule from the list to see how it's running." />}
        </section>
      </div>
    </div>
  );
};

const SimpleWebApp = () => {
  const [palette, setPaletteState] = useState("graphite"); // web is graphite-first
  const [stack, setStack] = useState([]);           // push stack: holding / account / card
  const [overlay, setOverlay] = useState(null);     // modal sheets
  const [txnOverlay, setTxnOverlay] = useState(null); // txn/receipt detail — a modal that layers OVER the current view (e.g. Accounts) rather than replacing it
  const [confirming, setConfirming] = useState(null); // action in the ConfirmSheet
  const [proposals, setProposals] = useState(PROPOSALS);
  const [activityExtra, setActivityExtra] = useState([]);
  const [toast, setToast] = useState(null);
  const [chatInject, setChatInject] = useState(null);
  const [hasCard, setHasCard] = useState(() => localStorage.getItem("yoshi_card") === "1");

  // resizable columns (persisted). center absorbs the rest, floored at CENTER_MIN.
  const [winW, setWinW] = useState(() => window.innerWidth);
  const [chatW, setChatW] = useState(() => { const v = parseInt(localStorage.getItem("yoshi_web_chatw") || "0", 10); return v >= CHAT_MIN && v <= CHAT_MAX ? v : 420; });
  const [actW, setActW] = useState(() => { const v = parseInt(localStorage.getItem("yoshi_web_actw") || "0", 10); return v >= ACT_MIN && v <= ACT_MAX ? v : 400; });
  useEffect(() => { const onResize = () => setWinW(window.innerWidth); window.addEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize); }, []);
  const setChatWidth = (w) => { setChatW(w); localStorage.setItem("yoshi_web_chatw", String(Math.round(w))); };
  const setActWidth = (w) => { setActW(w); localStorage.setItem("yoshi_web_actw", String(Math.round(w))); };

  useEffect(() => { document.getElementById("app").setAttribute("data-palette", palette); }, [palette]);
  useEffect(() => { window.setBellCount && window.setBellCount(window.totalBriefCount ? window.totalBriefCount(proposals.length) : proposals.length); }, [proposals]);
  const setPalette = (p) => setPaletteState(p);
  const flash = (msg) => { setToast(msg); setTimeout(() => setToast((t) => (t === msg ? null : t)), 2600); };
  const getCard = () => { localStorage.setItem("yoshi_card", "1"); setHasCard(true); };

  const nav = useMemo(() => ({
    // pillars are persistent — "switching tabs" just clears the layers
    tab: () => { setStack([]); setOverlay(null); setConfirming(null); },
    // a transaction/receipt detail layers over whatever's showing (Home,
    // Accounts, …) as a modal; every other sheet replaces the work area
    sheet: (o) => (o && (o.type === "txn" || o.type === "receipt")) ? setTxnOverlay(o) : setOverlay(o),
    closeSheet: () => setOverlay(null),
    push: (v) => setStack((s) => [...s, v]),
    pop: () => setStack((s) => s.slice(0, -1)),
    clearStack: () => setStack([]),
    // selecting a security ANYWHERE opens the Trade view with it preselected —
    // same as opening Trade and picking it (the detail shows on the left, ticket
    // on the right). Holdings resolve because MARKET includes them (held: true).
    security: (id) => { setStack([]); setConfirming(null); setOverlay({ type: "trade", id }); },
    ask: (note, reply) => { setChatInject({ note, reply }); setStack([]); setOverlay(null); setConfirming(null); },
    automation: (id) => { setStack([]); setOverlay({ type: "automation", id }); },
    studio: (view) => { setOverlay(view === "automations" ? { type: "automations" } : null); if (view !== "automations") nav.ask("Show me the markets", "Ask me about anything you hold or want to research — I'll pull the chart, the comparison, or the trade right here."); },
    accountsRoot: () => { setOverlay(null); setStack([]); },
    openAccount: () => flash("Opening an account — out of prototype scope"),
    signOut: () => { setStack([]); setOverlay(null); setConfirming(null); setProposals(PROPOSALS); setActivityExtra([]); flash("Signed out"); },
  }), []);

  const findProp = (id) => proposals.find((p) => p.id === id) || PROPOSALS.find((p) => p.id === id);
  const proposalActivity = (p) => p.activity || { icon: p.kind === "trade" ? "trade" : "swap", title: p.title, detail: p.legs.map((l) => l[1] && typeof l[1] === "object" ? l[1].ticker : l[1]).filter(Boolean).slice(0, 2).join(" · "), category: p.kind === "trade" ? "Investment" : "Transfer", by: p.agent, net: p.net };

  /* both doors of the ConfirmSheet end here — a completed action lands in Activity */
  const onExecuted = (a) => {
    if (a.activity) setActivityExtra((xs) => [{ ...a.activity, id: "x" + Date.now(), when: "Just now" }, ...xs]);
    if (a.id) setProposals((ps) => ps.filter((p) => p.id !== a.id));
    setConfirming(null);
    flash((a.kind === "automation" ? "Scheduled · " : "Executed · ") + a.title);
  };
  const onDeclined = (a, reason) => {
    if (a.id) setProposals((ps) => ps.filter((p) => p.id !== a.id));
    setConfirming(null);
    flash("Declined · " + reason);
  };
  const review = (p) => p && setConfirming({ ...p, activity: proposalActivity(p) });
  // on web, "Review" opens the Briefs UI with that exact brief selected (its
  // detail + approve/decline live in the reading pane), rather than a modal
  const reviewInBriefs = (p) => p && nav.sheet({ type: "briefs", brief: p.id });
  // briefs approve/decline inline in the reading pane (web two-pane)
  const execFromBrief = (id) => { const p = findProp(id); if (p) onExecuted({ ...p, activity: proposalActivity(p) }); };
  const declineFromBrief = (id) => { const p = findProp(id); setProposals((ps) => ps.filter((x) => x.id !== id)); flash("Declined" + (p ? " · " + p.title : "")); };
  const askAboutProposal = (a, text) => {
    setConfirming(null);
    const q = text && text.trim() ? text.trim() : `Question about "${a.title}"`;
    nav.ask(q, `Sure — ${a.why || "here's the thinking."} The short version: it moves you toward the mix you set, inside your limits, and nothing runs until you approve. What would you like changed?`);
  };

  // Esc closes the topmost layer (confirm → sheet → detail)
  useEffect(() => {
    const onKey = (e) => {
      if (e.key !== "Escape") return;
      if (txnOverlay) setTxnOverlay(null);
      else if (confirming) setConfirming(null);
      else if (overlay) setOverlay(null);
      else if (stack.length) setStack((s) => s.slice(0, -1));
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [txnOverlay, confirming, overlay, stack.length]);

  /* the active center page — a selection shifts the center column to this
     inline instead of opening a modal. Precedence: confirm > push detail >
     overlay sheet. null → the Home pane shows. */
  const centerNode = confirming ? (
    <ConfirmSheet action={confirming} onClose={() => setConfirming(null)}
      onApproved={onExecuted} onDeclined={onDeclined} onAsk={askAboutProposal} />
  ) : stack.length > 0 ? (
    stack.map((v, i) => (
      <div key={i} className="push-enter" style={{ position: "absolute", inset: 0, background: "var(--bg)", display: "flex", flexDirection: "column", zIndex: 400 + i }}>
        {v.type === "holding" && <HoldingDetail id={v.id} nav={nav} />}
        {v.type === "account" && <AccountDetail acct={v.acct} nav={nav} />}
        {v.type === "card" && <CardDetail nav={nav} flash={flash} hasCard={hasCard} onGetCard={getCard} />}
      </div>
    ))
  ) : (overlay && !["profile", "briefs", "automations", "automation", "txn", "receipt"].includes(overlay.type)) ? (
    <>
      {overlay.type === "holding" && <HoldingView id={overlay.id} nav={nav} onClose={nav.closeSheet} />}
      {overlay.type === "holdings" && <HoldingsDetailSheet nav={nav} onClose={nav.closeSheet} />}
      {overlay.type === "accounts" && <AccountsSheet nav={nav} onClose={nav.closeSheet} />}
      {overlay.type === "transfer" && <TransferFlow preset={overlay.from} intent={overlay.intent} initialRail={overlay.rail} rootTitle="Move" onClose={nav.closeSheet} nav={nav} flash={flash} />}
      {overlay.type === "trade" && <TradeSheet id={overlay.id} side={overlay.side} holdings={overlay.holdings} hideMenu onClose={nav.closeSheet} nav={nav} />}
      {overlay.type === "link" && <LinkSheet onClose={nav.closeSheet} />}
      {overlay.type === "connect" && <ConnectAgentsSheet onClose={nav.closeSheet} nav={nav} />}
      {overlay.type === "support" && <SupportFlow onClose={nav.closeSheet} nav={nav} />}
      {overlay.type === "documents" && <DocumentsHub onClose={nav.closeSheet} nav={nav} flash={flash} initialAcct={overlay.acct} />}
    </>
  ) : null;

  // too narrow for the desktop pillars → block and point to the phone
  if (winW < MOBILE_MIN) return <ThemeCtx.Provider value={palette}><MobileBlock /></ThemeCtx.Provider>;
  // a flow / detail takes over the whole area right of chat (center + Activity),
  // so Activity isn't its own column then. Otherwise Activity is a third column
  // while it fits; below that it docks BELOW the center (two columns).
  const wideEnough = winW >= chatW + actW + CENTER_MIN;
  const actVisible = !centerNode && wideEnough; // Activity as its own right column
  const docked = !centerNode && !wideEnough;    // Activity flows below the center
  // Briefs / Automations replace the WHOLE work area including chat — the
  // reading pane carries its own thread, so the chat pillar isn't needed (like
  // main's WebInbox / Studio Automations).
  const fullTakeover = !confirming && stack.length === 0 && !!overlay && (overlay.type === "briefs" || overlay.type === "automations" || overlay.type === "automation");
  // Trade / Transfer / Accounts carry their own desktop two-pane layout, so
  // they fill the takeover area right of chat; other sheets center in a column
  const wideFlow = !confirming && stack.length === 0 && !!overlay && (overlay.type === "trade" || overlay.type === "transfer" || overlay.type === "accounts");
  // Trade/Transfer's reused web layout has no close of its own; every other
  // hosted sheet (accounts, briefs, details, confirm) carries one already
  const paneClose = !confirming && stack.length === 0 && !!overlay && (overlay.type === "trade" || overlay.type === "transfer") ? nav.closeSheet : undefined;
  // effective width keeps the center at CENTER_MIN even when a window resize
  // (not a drag) leaves the stored chat width too large
  const effChatW = actVisible ? chatW : Math.max(CHAT_MIN, Math.min(chatW, winW - CENTER_MIN));
  const clampChat = (w) => Math.max(CHAT_MIN, Math.min(CHAT_MAX, Math.min(w, winW - CENTER_MIN - (actVisible ? actW : 0))));
  const clampAct = (w) => Math.max(ACT_MIN, Math.min(ACT_MAX, Math.min(w, winW - chatW - CENTER_MIN)));

  return (
    <ThemeCtx.Provider value={palette}>
      <div style={{ position: "fixed", inset: 0, display: "flex", flexDirection: "column", background: "var(--bg)" }}>
        {/* top bar — menu (left) · wordmark · bell (right) */}
        <div className="topbar">
          <button className="topbar-icon press" aria-label="Menu" title="Menu" onClick={() => nav.sheet({ type: "profile" })}>
            <Icon name="menu" size={22} />
          </button>
          {/* just the wordmark (bigger), like main — masked so it follows the
              theme's ink color; no separate glyph */}
          <button className="press" onClick={() => nav.tab()} aria-label="Yoshi home" title="Home" style={{ background: "none", border: "none", padding: "6px 8px", display: "flex", alignItems: "center", cursor: "pointer" }}>
            <span aria-hidden="true" style={{ display: "block", height: 23, width: 95, background: "var(--ink)", WebkitMaskImage: `url("${(window.__resources && window.__resources.wordmarkWhite) || "assets/logo-wordmark-white.png"}")`, maskImage: `url("${(window.__resources && window.__resources.wordmarkWhite) || "assets/logo-wordmark-white.png"}")`, WebkitMaskRepeat: "no-repeat", maskRepeat: "no-repeat", WebkitMaskPosition: "left center", maskPosition: "left center", WebkitMaskSize: "contain", maskSize: "contain" }} />
          </button>
          <div style={{ flex: 1 }} />
          {/* quick actions live in the top bar on web (Home hides its own pair) */}
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <TopAction icon="swap" label="Move" onClick={() => nav.sheet({ type: "transfer" })} />
            <TopAction icon="trade" label="Trade" filled onClick={() => nav.sheet({ type: "trade" })} />
            <BellButton nav={nav} label="Briefs" size={18} />
          </div>
        </div>

        {fullTakeover ? (
          /* Briefs / Automations take over the full width (chat included) */
          overlay.type === "briefs"
            ? <WebBriefs proposals={proposals} nav={nav} onClose={nav.closeSheet} onExecute={execFromBrief} onDecline={declineFromBrief} initialBriefId={overlay.brief} />
            : <WebAutomations nav={nav} onClose={nav.closeSheet} flash={flash} initialId={overlay.type === "automation" ? overlay.id : undefined} />
        ) : (
        <div className="pillars">
          {/* Yoshi chat — persistent left pillar (act), resizable; no header,
              the chat starts at the top (its phone header stays hidden by CSS) */}
          <aside className="chat-col" style={{ width: effChatW }}>
            <div className="yo-chatwrap">
              <YoshiTab nav={nav} proposals={proposals} onReview={reviewInBriefs} inject={chatInject} onInjected={() => setChatInject(null)} flash={flash} />
            </div>
            <ColResizer side="right" width={effChatW} setWidth={setChatWidth} clamp={clampChat} />
          </aside>

          {centerNode ? (
            /* a selection takes over the whole area right of chat — the center
               AND the Activity column (like main's flow panes) */
            <CenterPage wide={wideFlow} onClose={paneClose}>{centerNode}</CenterPage>
          ) : docked ? (
            /* two columns: Activity docks below the Home center and the whole
               center column scrolls as one region */
            <main className="main-col docked" style={{ position: "relative" }}>
              <div className="yo-mainwrap" style={{ width: "100%", maxWidth: 860, margin: "0 auto" }}>
                <HomeTab nav={nav} proposals={proposals} onReview={reviewInBriefs} hideFastPaths />
              </div>
              <section className="pillar-dock" style={{ width: "100%", maxWidth: 860, margin: "18px auto 0", borderTop: "1px solid var(--rule)" }}>
                <ActivityTab nav={nav} extra={activityExtra} />
              </section>
              {txnOverlay?.type === "txn" && <TxnDetailSheet tx={txnOverlay.tx} onClose={() => setTxnOverlay(null)} nav={nav} />}
              {txnOverlay?.type === "receipt" && <ReceiptSheet tx={txnOverlay.tx} nav={nav} onClose={() => setTxnOverlay(null)} />}
            </main>
          ) : (
            /* three columns: Home center + Activity right pillar (resizable) */
            <>
              <main className="main-col">
                <div className="yo-mainwrap" style={{ width: "100%", maxWidth: 820, margin: "0 auto" }}>
                  <HomeTab nav={nav} proposals={proposals} onReview={reviewInBriefs} hideFastPaths />
                </div>
              </main>
              <aside className="stream-col" style={{ width: actW }}>
                <ColResizer side="left" width={actW} setWidth={setActWidth} clamp={clampAct} />
                <div style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column", paddingTop: 8, position: "relative" }}>
                  <ActivityTab nav={nav} extra={activityExtra} />
                  {txnOverlay?.type === "txn" && <TxnDetailSheet tx={txnOverlay.tx} onClose={() => setTxnOverlay(null)} nav={nav} />}
                  {txnOverlay?.type === "receipt" && <ReceiptSheet tx={txnOverlay.tx} nav={nav} onClose={() => setTxnOverlay(null)} />}
                </div>
              </aside>
            </>
          )}
        </div>
        )}
      </div>

      {/* menu — a left drawer over a grey scrim (like main). ProfileSheet's own
          X (top-left) or a click on the scrim closes it. */}
      {overlay?.type === "profile" && (
        <>
          <div onClick={nav.closeSheet} style={{ position: "fixed", inset: 0, zIndex: 1240, background: "rgba(0,0,0,0.45)", animation: "scrim-in 200ms ease both" }} />
          <aside data-screen-label="Menu" style={{ position: "fixed", top: 0, left: 0, bottom: 0, width: "min(400px, 92vw)", zIndex: 1250, background: "var(--bg)", borderRight: "1px solid var(--rule)", display: "flex", flexDirection: "column", minHeight: 0, boxShadow: "0 24px 60px -20px rgba(0,0,0,0.5)", animation: "drawer-in 240ms cubic-bezier(0.16,1,0.30,1) both" }}>
            <ProfileSheet palette={palette} setPalette={setPalette} nav={nav} onClose={nav.closeSheet} flash={flash} />
          </aside>
        </>
      )}

      {toast && <Toast msg={toast} />}
    </ThemeCtx.Provider>
  );
};

ReactDOM.createRoot(document.getElementById("app")).render(<SimpleWebApp />);
