/* The Core/Pact chat box — every pixel of it — is @ancientpantheon/claude-chat-shell, linked from
   index.html as /chat-shell/chat-shell.css. Nothing about the chat shell is styled in this file. */
:root {
  /* Canonical Pantheonic tokens (names fixed across sites; these are Claudstermind's values). */
  --bg: #0b1020; --bg-2: #0e1530; --panel: #121933; --panel-2: #1a2444; --line: #24304f;
  --ink: #e7ecff; --ink-soft: #9aa6c7; --ink-mute: #6f7a96;
  --accent: #e879f9; --accent-dim: #a21caf; --danger: #f87171; --radius: 14px;
  --maxw: 1536px;
  --shadow: 0 6px 24px rgba(0,0,0,.35);
  /* Legacy aliases — existing components reference these; kept during migration. */
  --panel2: var(--bg-2); --chip: var(--panel-2); --ink-dim: var(--ink-soft);
}
:root[data-theme="light"], body[data-theme="light"] {
  --bg: #f4f6fc; --bg-2: #eef1fa; --panel: #ffffff; --panel-2: #eef2ff; --line: #d7def0;
  --ink: #16203a; --ink-soft: #5b678a; --ink-mute: #7c88a6;
  --accent: #a21caf; --accent-dim: #7a1585; --danger: #d64550; --radius: 14px;
  --shadow: 0 6px 20px rgba(20,30,60,.10);
}
/* WORKSPACE IDENTITY (Core "Midnight Violet" / Pact "Harbour Slate") — accent, full dark palette and
   bubble tokens all belong to the chat-shell package now; index.html links its one stylesheet and
   that pulls them in. Nothing about the chat box is declared in this file. P#/R# addressing tag
   colours ship with the package too, deliberately identical in both workspaces: they are an
   addressing scheme, not decor, and must be learnable once. */
* { box-sizing: border-box; }
html, body { height: 100%; }
/* Never let a stray wide element push the page past the viewport (which on a phone triggers the
   browser's "shrink to fit" — the zoomed-out desktop look). Legit horizontal scrollers (the matrix,
   the heatmap, tab strips) have their OWN overflow container, so they're unaffected.
   `-webkit-text-size-adjust: 100%` stops mobile browsers auto-inflating text. */
html { overflow-x: hidden; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body { overflow-x: hidden; max-width: 100%; }
/* Flex column so the page always fills the viewport (background + footer sink to the bottom on
   short views) and grows past it on long ones. */
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  display: flex; flex-direction: column; min-height: 100%;
}
.ph { flex: 0 0 auto; }
/* Collapse-header toggle: hide the whole top app header to reclaim vertical working area. The toggle
   buttons live below the header (Pact toolbar / Core workspace controls) so they stay reachable. The
   mobile bottom tab bar (.ph-tabbar) is NOT part of .ph, so navigation survives the collapse. */
/* The collapse-header toggle only applies ON the workspace/pact cockpit views (body.ws-full is set
   only there). Scoping to .ws-full stops the persisted preference from hiding the header on other
   pages — e.g. after logging back in on a non-workspace view, where the nav would otherwise vanish. */
body.ph-collapsed.ws-full .ph { display: none; }
.ph-collapse-btn.--on { color: var(--bg); background: var(--accent); border-color: var(--accent); }
/* #main is FULL width (no auto-margin — that shrinks a flex item to content width); the content
   inside (#statbar / #view) is what caps + centres at --maxw. */
#main { flex: 1 1 auto; width: 100%; min-width: 0; display: flex; flex-direction: column; }
#statbar { flex: 0 0 auto; width: 100%; max-width: var(--maxw); margin-inline: auto; }
#view { flex: 1 1 auto; min-height: 0; width: 100%; max-width: var(--maxw); margin-inline: auto; }
.foot { flex: 0 0 auto; }
/* ---------- The Pantheonic Header (§3): sticky, full-chrome-width separator, 3 levels ---------- */
.ph { position: sticky; top: 0; z-index: 30; background: linear-gradient(180deg, var(--panel), var(--bg-2));
  border-bottom: 1px solid var(--line); }
.ph-inner { width: 100%; max-width: var(--maxw); margin: 0 auto; padding: 0 24px; display: flex; flex-direction: column; }
.ph-l1 { display: flex; align-items: center; gap: 10px; height: 52px; }
.ph-name { display: flex; align-items: center; gap: 9px; text-decoration: none; color: var(--ink); font-size: 17px; font-weight: 700; letter-spacing: .2px; }
.ph-name .brainmark { filter: drop-shadow(0 0 7px rgba(59,130,246,.5)); }
.ph-ver { font: 600 11px/1 ui-monospace, monospace; color: var(--ink-mute); border: 1px solid var(--line);
  border-radius: 6px; padding: 3px 6px; text-decoration: none; }
.ph-ver:hover { color: var(--ink); border-color: var(--accent-dim); }
.ph-spacer { flex: 1; }
.ph-identity { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--ink-soft); }
.ph-id-name b { color: var(--ink); }
.ph-l2 { display: flex; align-items: center; gap: 8px; height: 46px; border-top: 1px solid color-mix(in srgb, var(--line) 55%, transparent); }
.ph-sections { display: flex; gap: 6px; flex: 1; flex-wrap: wrap; }
.ph-l3 { display: flex; align-items: center; gap: 6px; min-height: 40px; }
.ph-l3:empty::before { content: ""; }   /* reserved zone — height holds even when empty */

.model-pill { font-size: 11px; color: var(--ink-mute); background: var(--panel-2); border: 1px solid var(--line);
  padding: 4px 10px; border-radius: 999px; white-space: nowrap; }

/* Strong squared header buttons (§3.5) */
.ph-btn { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600;
  padding: 8px 15px; border-radius: 8px; border: 1px solid transparent; cursor: pointer; text-decoration: none;
  color: var(--ink-soft); background: transparent; white-space: nowrap; }
.ph-btn.--sm { padding: 6px 12px; font-size: 12.5px; }
.ph-btn.--ghost:hover { color: var(--ink); border-color: var(--line); background: var(--panel-2); }
.ph-btn.--active { color: var(--ink); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.ph-btn.--primary { color: #0b1020; background: var(--accent); border-color: var(--accent); }
.ph-btn.--primary:hover { background: var(--accent-dim); border-color: var(--accent-dim); }
.ph-btn.is-disabled { opacity: .45; cursor: not-allowed; }
.role-badge { font-size: 10.5px; font-weight: 700; text-transform: lowercase; letter-spacing: .03em;
  border: 1px solid var(--line); color: var(--ink-soft); border-radius: 999px; padding: 2px 8px; }
.role-badge.is-ancient { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
/* "local-only engine" warning badge next to the version pill (this dashboard isn't on the shared daemon). */
.ph-engine-badge { font-size: 10px; font-weight: 700; color: #e2b04a; border: 1px solid color-mix(in srgb, #e2b04a 55%, transparent);
  border-radius: 999px; padding: 2px 8px; white-space: nowrap; cursor: help; }
/* Session keep-alive pill (header) — time left on the sliding login; renews on click. */
.ph-session { font-size: 10.5px; font-weight: 600; color: var(--ink-mute); border: 1px solid var(--line);
  border-radius: 999px; padding: 2px 8px; cursor: pointer; white-space: nowrap; }
.ph-session[hidden] { display: none; }
.ph-session.--warn { color: #e2b04a; border-color: color-mix(in srgb, #e2b04a 55%, transparent); }
.ph-session.--bad { color: #f87171; border-color: color-mix(in srgb, #f87171 60%, transparent); }
/* Non-destructive "login expired" banner — fixed at the top, never redirects you away. */
.session-banner { position: fixed; top: 0; left: 0; right: 0; z-index: 9999; display: flex; gap: 12px;
  align-items: center; justify-content: center; flex-wrap: wrap; padding: 8px 14px;
  background: color-mix(in srgb, #f87171 22%, var(--bg, #12101c)); color: var(--ink, #e8e6f0);
  border-bottom: 1px solid color-mix(in srgb, #f87171 55%, transparent); font-size: 13px; }

/* Legacy .ghost (still used by in-view buttons) */
.ghost { background: var(--panel-2); color: var(--ink); border: 1px solid var(--line);
  border-radius: 8px; padding: 6px 10px; cursor: pointer; font-size: 14px; }
.ghost:hover { border-color: var(--accent); }

main { padding: 18px 24px 60px; }

/* ---------- Admin: sidebar + content pane (§5) ---------- */
.admin-layout { display: flex; gap: 16px; align-items: flex-start; }
.admin-side { width: 236px; flex: 0 0 236px; display: flex; flex-direction: column; gap: 3px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 8px; position: sticky; top: 118px; }
.admin-item { display: flex; align-items: center; gap: 9px; padding: 9px 11px; border-radius: 9px; text-decoration: none;
  color: var(--ink-soft); border-left: 3px solid transparent; font-size: 13.5px; }
.admin-item:hover { background: var(--panel); color: var(--ink); }
.admin-item.on { color: var(--ink); background: var(--panel); border-left-color: var(--accent); font-weight: 600; }
.admin-item.disabled { opacity: .5; cursor: default; }
.admin-ic { width: 18px; text-align: center; }
.admin-label { flex: 1; }
.admin-soon { font-size: 9.5px; text-transform: uppercase; color: var(--ink-mute); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; }
.admin-pane { flex: 1; min-width: 0; }
.admin-empty { color: var(--ink-mute); padding: 40px 8px; text-align: center; font-size: 14px; }
@media (max-width: 820px) {
  .admin-layout { flex-direction: column; }
  /* Wrap the section pills onto as many rows as needed so EVERY tab is visible (no hidden horizontal
     scroll that clipped "Tokens" → "To"); each pill stays on one line (no more "Model routing" splitting). */
  .admin-side { width: auto; flex: none; flex-direction: row; flex-wrap: wrap; overflow-x: visible; position: static; gap: 6px; }
  .admin-item { flex: 0 0 auto; white-space: nowrap; }
}
[hidden] { display: none !important; }   /* §5.3 the [hidden]-wins rule */

.statbar { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 18px; }
.stat {
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 16px; min-width: 120px; box-shadow: var(--shadow);
}
.stat .n { font-size: 24px; font-weight: 700; }
.stat .l { font-size: 11px; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .5px; }

.grid-orgs { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px; }
/* Narrower screens reflow to as-many-as-fit; the inline per-org-count columns apply above this. */
@media (max-width: 1180px) { .grid-orgs { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)) !important; } }
.orgcard { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; overflow: hidden; box-shadow: var(--shadow); min-width: 0; }
.orgcard .hd { padding: 12px 14px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 8px; }
.orgcard .dot { width: 12px; height: 12px; border-radius: 50%; flex: none; }
.orgcard .hd b { font-size: 14px; }
.orgcard .hd .scope { font-size: 11px; color: var(--ink-dim); margin-left: auto; font-family: ui-monospace, monospace; }
.orgcard .desc { padding: 8px 14px; font-size: 11.5px; color: var(--ink-dim); border-bottom: 1px solid var(--line); }
.orgcard .body { padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; }

/* ---- org "greater cardboard": an organisation container holding repo cards.
   Shared by Brain + Git-state so a repo sits in the same place in every tab. ---- */
.orggroup { border: 1px solid var(--line); border-left: 4px solid var(--org, var(--line)); border-radius: 14px;
  background: var(--panel2); margin-bottom: 16px; overflow: hidden; box-shadow: var(--shadow); }
.orggroup-hd { display: flex; align-items: center; gap: 9px; padding: 11px 15px; background: var(--panel);
  border-bottom: 1px solid var(--line); }
.orggroup-hd .dot { width: 12px; height: 12px; border-radius: 50%; flex: none; }
.orggroup-hd b { font-size: 14.5px; letter-spacing: .2px; }
.orggroup-hd .scope { font-size: 11px; color: var(--ink-dim); font-family: ui-monospace, monospace; }
.orggroup-hd .grouptag { margin-left: auto; }
.orggroup-body { padding: 12px; display: grid; grid-template-columns: repeat(auto-fill, minmax(255px, 1fr)); gap: 10px; }
/* the repo "cardboard" inside a group */
.repocard { background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--stripe, var(--line));
  border-radius: 11px; padding: 9px 11px; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.repocard .rc-hd { display: flex; align-items: center; gap: 7px; }
.repocard .rc-hd .rc-name { font-weight: 700; font-size: 13px; white-space: nowrap; overflow: hidden; min-width: 0; flex: 1; }
.repocard .rc-hd .rc-name-inner { display: inline-block; will-change: transform; }
/* JS adds .train on hover ONLY when the name overflows, so it scrolls like a train to
   reveal the rest; short names never get the class and stay put. */
.repocard .rc-hd .rc-name-inner.train { animation: rcTrain 2.8s ease-in-out infinite alternate; }
@keyframes rcTrain { 0%, 12% { transform: translateX(0); } 88%, 100% { transform: translateX(var(--marq, 0px)); } }
@media (prefers-reduced-motion: reduce) { .repocard .rc-hd .rc-name-inner.train { animation: none; } }
.repocard .rc-hd .rc-branch { margin-left: auto; font-size: 10.5px; color: var(--ink-dim); font-family: ui-monospace, monospace;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%; }
.repocard .rc-sub { font-size: 11px; color: var(--ink-dim); }
.repocard.is-muted { opacity: .5; }
.gitbtn { font-size: 11px; padding: 3px 9px; border-radius: 7px; background: var(--chip); color: var(--ink);
  border: 1px solid var(--line); cursor: pointer; }
.gitbtn:hover:not(:disabled) { border-color: var(--accent); }
.gitbtn:disabled { opacity: .55; cursor: default; }
.repocard .gitmsg { font-family: ui-monospace, monospace; font-size: 10.5px; white-space: pre-wrap; }

/* ---- themed modal (replaces window.prompt for commit etc.) ---- */
.modal-overlay { position: fixed; inset: 0; background: rgba(6, 9, 20, .62); backdrop-filter: blur(2px);
  display: flex; align-items: center; justify-content: center; z-index: 1000; animation: modalIn .12s ease-out; }
@keyframes modalIn { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .modal-overlay { animation: none; } }
.modal { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; width: min(560px, 92vw);
  box-shadow: 0 20px 60px rgba(0, 0, 0, .5); overflow: hidden; }
/* Expanded History — the full-page view: same conversations the cramped sidebar list holds, but
   with room to actually navigate, grouped by organisation then by repository. */
.ws-hexp-modal { width: 96vw; height: 92vh; max-width: 1100px; display: flex; flex-direction: column; }
.ws-hexp-hd { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--line); }
.ws-hexp-title { margin: 0; font-size: 15px; white-space: nowrap; }
.ws-hexp-hd .ws-search { flex: 1; }
.ws-hexp-body { flex: 1; overflow-y: auto; padding: 12px 16px; display: flex; flex-direction: column; gap: 10px; }
.ws-hexp-repos { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 8px; padding: 8px !important; }
.ws-hexp-repo { background: var(--chip); border: 1px solid var(--line); border-radius: 8px; padding: 7px; min-width: 0; }
.ws-hexp-repo-hd { display: flex; justify-content: space-between; align-items: center; font-size: 12px; font-weight: 700;
  color: var(--ink); padding: 2px 3px 6px; }
.ws-hexp-repo-body { display: flex; flex-direction: column; gap: 6px; }
.ws-hexp-flat { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 8px; }
@media (max-width: 640px) {
  .ws-hexp-modal { width: 100vw; height: 100vh; max-width: none; border-radius: 0; }
  .ws-hexp-repos, .ws-hexp-flat { grid-template-columns: 1fr; }
}
.modal-hd { display: flex; align-items: center; gap: 9px; padding: 13px 18px; border-bottom: 1px solid var(--line);
  font-weight: 700; font-size: 14.5px; }
.modal-hd .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); flex: none; }
.modal-bd { padding: 15px 18px; }
.modal-bd textarea { width: 100%; box-sizing: border-box; min-height: 84px; background: var(--chip);
  border: 1px solid var(--line); color: var(--ink); border-radius: 9px; padding: 10px 12px;
  font-family: ui-monospace, monospace; font-size: 13px; line-height: 1.5; resize: vertical; }
.modal-bd textarea:focus { outline: none; border-color: var(--accent); }
.modal-sub { font-size: 12px; color: var(--ink-dim); margin-bottom: 9px; }
/* Optional opt-in tick in a modal (showModal's `checkbox`) — e.g. Reload's "also restart the session
   engine". Grid so the sub-label lines up under the bold label, not under the box. */
.modal-check { display: grid; grid-template-columns: auto 1fr; gap: 3px 8px; align-items: center;
  margin-top: 11px; padding: 10px 11px; background: var(--chip); border: 1px solid var(--line);
  border-radius: 8px; cursor: pointer; }
.modal-check:hover { border-color: var(--accent); }
.modal-check input { margin: 0; cursor: pointer; width: 15px; height: 15px; accent-color: var(--accent); }
.modal-check b { font-size: 12.5px; font-weight: 600; }
.modal-check-sub { grid-column: 2; font-size: 11.5px; color: var(--ink-dim); line-height: 1.45; }
.modal-ft { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 18px; border-top: 1px solid var(--line); }
.modal-ft .btn-primary { background: var(--accent); color: #0b1020; border: 1px solid var(--accent); font-weight: 700; }
.modal-ft .btn-primary:hover { filter: brightness(1.08); }
.modal-hint { font-size: 11px; color: var(--ink-dim); margin-right: auto; align-self: center; }

.repo { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 8px; background: var(--panel2); border: 1px solid transparent; cursor: default; min-width: 0; }
.repo:hover { border-color: var(--line); }
.repo .glyph { width: 18px; text-align: center; flex: none; }
.repo .rn { font-weight: 600; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.repo .rolechip { font-size: 10px; padding: 1px 7px; border-radius: 999px; margin-left: auto; color: #0b1020; font-weight: 700; }
.repo .move { color: #f59e0b; font-size: 11px; margin-left: 6px; }
.repo .ver { font-family: ui-monospace, monospace; font-size: 10.5px; color: var(--ink-dim); }

/* matrix */
/* The matrix table can be wider than a phone; its wrapper scrolls it horizontally on its own
   (so the whole page doesn't become pannable). Harmless on desktop where the table fits. */
.matrix-wrap { overflow-x: auto; max-width: 100%; }
.matrix { width: 100%; border-collapse: collapse; }
.matrix th, .matrix td { border: 1px solid var(--line); vertical-align: top; padding: 8px; }
.matrix th { background: var(--panel2); font-size: 12px; position: sticky; top: 96px; z-index: 5; }
.matrix td { background: var(--panel); min-width: 150px; }
.matrix .rolehead { writing-mode: horizontal-tb; font-weight: 700; font-size: 12px; background: var(--panel2); white-space: nowrap; }
.mchip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; margin: 3px; border-radius: 8px; background: var(--chip); border: 1px solid var(--line); font-size: 12px; }
.mchip.moving { border-color: #f59e0b; box-shadow: 0 0 0 1px #f59e0b33; }

/* graph */
.graphwrap { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 8px; box-shadow: var(--shadow); overflow-x: auto; }
.graph-controls { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; flex-wrap: wrap; }
svg text { font-family: ui-sans-serif, system-ui, sans-serif; }
.node rect { stroke-width: 1.5; cursor: pointer; }
.edge { fill: none; stroke: var(--line); stroke-width: 1.4; opacity: .55; }
.edge.cross { stroke: var(--accent); stroke-dasharray: 5 4; opacity: .8; }
.edge.dim { opacity: .08; }
.edge.hot { opacity: 1; stroke-width: 2.4; }
.node.dim { opacity: .22; }
.laybar { fill: var(--panel2); }
.laylabel { fill: var(--ink-dim); font-size: 11px; }

/* movements */
.movelist { display: flex; flex-direction: column; gap: 10px; }
.movecard { background: var(--panel); border: 1px solid var(--line); border-left: 3px solid #f59e0b; border-radius: 10px; padding: 12px 14px; box-shadow: var(--shadow); }
.movecard h4 { margin: 0 0 6px; font-size: 14px; display: flex; align-items: center; gap: 8px; }
.movecard ul { margin: 6px 0 0; padding-left: 18px; color: var(--ink-dim); font-size: 12.5px; }
.movecard .tag { font-size: 10px; padding: 1px 7px; border-radius: 999px; color: #0b1020; font-weight: 700; }

/* packages */
.pkgtable { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.pkgtable th, .pkgtable td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); }
.pkgtable th { color: var(--ink-dim); font-weight: 600; background: var(--bg); }
.pkgtable code { font-family: ui-monospace, monospace; }
.pkgtable .pub { color: #34d399; } .pkgtable .priv { color: var(--ink-dim); }
.was { color: var(--ink-dim); font-size: 11px; }
.arrow { color: var(--accent); }

.legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 11px; color: var(--ink-dim); }
.legend .li { display: inline-flex; align-items: center; gap: 5px; }
.legend .sw { width: 10px; height: 10px; border-radius: 3px; }
.foot { border-top: 1px solid var(--line); padding: 12px 22px; background: var(--panel2); }

.tooltip {
  position: fixed; z-index: 50; max-width: 320px; background: var(--panel); color: var(--ink);
  border: 1px solid var(--accent); border-radius: 10px; padding: 10px 12px; font-size: 12px;
  box-shadow: var(--shadow); pointer-events: none;
}
.tooltip b { color: var(--accent); }
.tooltip .row { color: var(--ink-dim); margin-top: 3px; }
.hint { color: var(--ink-dim); font-size: 12px; margin: 4px 0 14px; }

/* Online-relay connection banner: shown when the local bridge is offline, so a viewer
   knows the data is the last received and actions are disabled. Amber, not alarming. */
.conn-banner {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 22px; font-size: 13px; font-weight: 500;
  color: #7c4a03; background: #fde68a; border-bottom: 1px solid #f59e0b;
}
:root[data-theme="dark"] .conn-banner, body[data-theme="dark"] .conn-banner {
  color: #fde68a; background: #3a2c0a; border-bottom-color: #a16207;
}
/* An expanding ring, done compositably: a STATIC ring on ::after whose transform+opacity animate.
   The box-shadow version repainted the dot and the area the shadow grew into on every frame — see
   lib/compositedAnimations.test.mjs for what that costs when several of them are on screen. */
.conn-banner .conn-dot {
  width: 9px; height: 9px; border-radius: 999px; background: #f59e0b; flex: 0 0 auto; position: relative;
}
.conn-banner .conn-dot::after {
  content: ""; position: absolute; inset: 0; border-radius: 999px; pointer-events: none;
  box-shadow: 0 0 0 7px #f59e0b88; will-change: transform, opacity;
  animation: connPulse 2s ease-out infinite;
}
@keyframes connPulse { 0% { transform: scale(.2); opacity: .55; } 70% { transform: scale(1); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .conn-banner .conn-dot::after { animation: none; opacity: 0; } }
/* Full "work machine offline" / view-error screen — shown in #view instead of a blank void when the tunnel
   is down or a view throws (see render()'s renderOfflineScreen / renderViewError). */
.conn-lost { min-height: 60vh; display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 12px; padding: 40px 24px; max-width: 560px; margin: 0 auto; }
.conn-lost-icon { font-size: 52px; line-height: 1; filter: grayscale(.2); opacity: .95; }
.conn-lost-title { margin: 0; font-size: 22px; font-weight: 800; color: var(--ink, #e8eefc); }
.conn-lost-msg { margin: 0; font-size: 15px; line-height: 1.55; color: var(--ink-mute, #9fb0d0); }
.conn-lost-sub { margin: 0; font-size: 13px; line-height: 1.5; color: var(--ink-dim, #6b7a99); }
.conn-lost-retry { margin-top: 8px; padding: 9px 20px; border-radius: 9px; border: 1px solid var(--accent, #6ea8fe);
  background: color-mix(in srgb, var(--accent, #6ea8fe) 16%, transparent); color: var(--accent, #6ea8fe);
  font-weight: 700; font-size: 14px; cursor: pointer; }
.conn-lost-retry:hover { background: color-mix(in srgb, var(--accent, #6ea8fe) 28%, transparent); }

/* Login / access gate — the branded full-screen state shown before an admin session
   exists on the live site. The local dashboard never shows this (no auth). */
.gate { display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 14px; min-height: 62vh; padding: 48px 20px; }
.gate-mark { border-radius: 22px; }
.gate-title { margin: 6px 0 0; font-size: 26px; }
.gate-sub { color: var(--ink-dim); margin: 0; max-width: 400px; line-height: 1.55; }
.gate-note { color: var(--ink-dim); font-size: 12px; opacity: .8; margin: 2px 0 0; }
.loginbtn { display: inline-flex; align-items: center; gap: 8px; margin-top: 10px;
  padding: 11px 24px; border-radius: 10px; background: var(--accent); color: #fff;
  font-weight: 600; text-decoration: none; border: 1px solid var(--accent); }
.loginbtn:hover { filter: brightness(1.08); }
.loginbtn.secondary { background: var(--chip); color: var(--ink); border-color: var(--line); }

/* Ops activity blinker — a pulsing green dot on the repo card that has a live agent. */
.actdot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: #64748b; flex: 0 0 auto; }
.actdot.on { background: #34d399; position: relative; }
.actdot.on::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  box-shadow: 0 0 0 6px rgba(52,211,153,.55); will-change: transform, opacity;
  animation: actPulse 1.25s ease-out infinite;
}
@keyframes actPulse { 0% { transform: scale(.2); opacity: 1; } 70% { transform: scale(1); opacity: 0; } 100% { transform: scale(1); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .actdot.on::after { animation: none; opacity: 0; } }

/* Activity — weekly view: nav, org heatmap (readable day headers), time-of-day chart. */
.actnav { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.actnav .actweek { display: flex; flex-direction: column; line-height: 1.25; }
.actnav .actweek b { font-size: 15px; }
.actnav .actweektot { margin-left: auto; display: flex; align-items: baseline; gap: 5px; }
.actnav .actweektot .big { font-size: 25px; font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }
.actnav .actweektot .lbl { font-size: 11px; color: var(--ink-dim); margin-right: 10px; }

.heatmap2 { border-collapse: separate; border-spacing: 3px; }
.heatmap2 th.hmday2 { color: var(--ink-dim); font-weight: 600; font-size: 11px; padding: 2px 4px; white-space: nowrap; text-align: center; min-width: 46px; }
.heatmap2 th.hmrepo, .heatmap2 td.hmrepo { text-align: left; white-space: nowrap; font-weight: 600; font-size: 12.5px; padding-right: 12px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
.heatmap2 td.hmorg-hd { font-size: 12px; font-weight: 700; padding: 7px 9px; background: var(--panel); border-radius: 7px; }
.heatmap2 .hmorg { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 7px; vertical-align: middle; }
.heatmap2 td.hmcell2 { min-width: 44px; height: 28px; text-align: center; vertical-align: middle; border-radius: 6px; background: var(--chip);
  font-weight: 700; font-size: 11.5px; font-variant-numeric: tabular-nums; }
.heatmap2 td.hmcell2:hover { outline: 1px solid var(--accent); }

.hchart-wrap { max-width: 520px; }
.hchart { display: flex; align-items: flex-end; gap: 2px; height: 40px; }
.hchart .hbar { flex: 1; background: var(--chip); border-radius: 2px 2px 0 0; min-height: 2px; }
.hchart .hbar.on { background: #34d399; }
.hbar-labels { display: flex; justify-content: space-between; font-size: 10px; color: var(--ink-dim); margin-top: 3px; }

/* Workspace — the remote Claude cockpit (online + ancient only). Full-width, multi-pane. */
/* Workspace = a single-screen fixed page (Pantheonic Form A): no page scroll, the cockpit fills
   the viewport and its sidebar/panes scroll internally. */
body.ws-full { height: 100vh; overflow: hidden; }
body.ws-full #main { overflow: hidden; min-height: 0; padding: 10px 18px; }
body.ws-full #statbar { display: none; }   /* reclaim the stat-cards height for the cockpit */
body.ws-full #view { max-width: none; margin: 0; }   /* full width — the sanctioned Workspace exception */
body.ws-full .foot { display: none; }
body.ws-full .ws-root { height: 100%; }
body.ws-full .ws-body { flex: 1; min-height: 0; }
body.ws-full .ws-side { max-height: none; height: 100%; }
body.ws-full .ws-grid { height: 100%; min-height: 0; }
body.ws-full .ws-pane { height: 100%; min-height: 0; }
body.ws-full .ws-transcript { max-height: none; flex: 1; }
body.ws-full .stick-wrap-ws { max-height: none; flex: 1; }

.ws-root { display: flex; flex-direction: column; gap: 10px; }
/* Mobile-only Core bottom control bar + Live/Held strip — hidden on desktop (shown under .ws-mobile). */
.ws-mcbar, .ws-mmodebar, .ws-drawer-actions { display: none; }
/* Mobile bottom sheet (conversations switcher / pane settings) — a slide-up panel over a scrim. Only
   ever opened on a phone; harmless (display:none) on desktop. */
.ws-sheet { position: fixed; inset: 0; z-index: 90; display: none; }   /* above the fixed bottom nav (.ph-tabbar ~79) so it's a true modal */
.ws-sheet.open { display: block; }
.ws-sheet-back { position: absolute; inset: 0; background: rgba(0,0,0,.5); }
.ws-sheet-panel { position: absolute; left: 0; right: 0; bottom: 0; max-height: 76vh; display: flex; flex-direction: column;
  background: var(--panel2); border-top: 1px solid var(--line); border-radius: 16px 16px 0 0;
  padding: 10px 12px calc(12px + env(safe-area-inset-bottom)); animation: wsSheetUp .18s ease-out; }
@keyframes wsSheetUp { from { transform: translateY(12%); opacity: .6; } to { transform: translateY(0); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .ws-sheet-panel { animation: none; } }
.ws-sheet-hd { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.ws-sheet-ttl { font-weight: 700; font-size: 14px; color: var(--ink); }
.ws-sheet-x { background: transparent; border: none; color: var(--ink-dim); font-size: 18px; cursor: pointer; padding: 4px 8px; }
.ws-sheet-body { overflow-y: auto; -webkit-overflow-scrolling: touch; }
.ws-sheet-list { display: flex; flex-direction: column; gap: 6px; }
.ws-sheet-row { display: flex; align-items: center; gap: 9px; width: 100%; text-align: left; min-height: 46px;
  background: var(--chip); border: 1px solid var(--line); color: var(--ink); border-radius: 10px; padding: 8px 12px; font-size: 14px; cursor: pointer; }
.ws-sheet-row.--active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.ws-sheet-row-lbl { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-sheet-row-x { color: var(--ink-mute); font-size: 18px; padding: 0 4px; }
.ws-sheet-add { justify-content: center; color: var(--accent); font-weight: 600; }
/* The borrowed pane-controls node, shown inside the settings sheet (it's display:none while in the pane). */
.ws-toolbar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.ws-spacer { flex: 1; }
.ws-trust { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink); }
.ws-usage-total { font-size: 12px; color: var(--ink-dim); }
/* Plan usage limits (5h/7d) — a compact badge, full breakdown in its title tooltip (see
   renderUsageLimits in app.js). Hidden entirely on a build where the SDK's experimental
   usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET() never answers (e.g. API-key auth). */
.ws-usage-limits { font-size: 12px; color: var(--ink-dim); border: 1px solid var(--line); border-radius: 6px; padding: 2px 7px; cursor: default; }
/* Usage & Keys tab (Workspace → Usage): multi-key OAuth store + plan rate-limit viewer + failover state. */
.usage-view { padding: 16px 20px; max-width: 720px; }
.usage-view .deploy-h { margin: 0 0 4px; }
.usage-actions { margin: 12px 0; }
.usage-keys { display: flex; flex-direction: column; gap: 10px; }
.usage-key-card { border: 1px solid var(--line); border-radius: 10px; padding: 12px 14px; background: var(--panel2); }
.usage-key-card.--active { border-color: color-mix(in srgb, #34d399 55%, var(--line)); }
.usage-key-card.--exhausted { opacity: .82; border-color: color-mix(in srgb, #f87171 50%, var(--line)); }
.usage-key-hd { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.usage-key-name { font-weight: 700; font-size: 14px; color: var(--ink); }
.usage-key-fp { font: 11px ui-monospace, monospace; color: var(--ink-mute); background: var(--chip); padding: 1px 6px; border-radius: 5px; }
.usage-key-acct { font-size: 11px; color: var(--ink-dim); }
.usage-key-badge { font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 999px; white-space: nowrap; }
.usage-key-badge.--active { color: #34d399; border: 1px solid color-mix(in srgb, #34d399 55%, transparent); }
.usage-key-badge.--exhausted { color: #f87171; border: 1px solid color-mix(in srgb, #f87171 55%, transparent); }
.usage-bar-row { display: flex; align-items: center; gap: 10px; margin: 4px 0; font-size: 12px; }
.usage-bar-lbl { flex: 0 0 92px; color: var(--ink-dim); }
.usage-bar { flex: 1 1 auto; height: 8px; background: var(--chip); border-radius: 999px; overflow: hidden; }
.usage-bar-fill { height: 100%; background: #5aa9ec; border-radius: 999px; }
.usage-bar-fill.--warn { background: #f59e0b; } .usage-bar-fill.--hot { background: #f87171; }
.usage-bar-pct { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--ink-soft); min-width: 34px; text-align: right; }
.usage-bar-pct.--warn { color: #f59e0b; } .usage-bar-pct.--hot { color: #f87171; }
.usage-bar-reset { flex: 0 0 auto; font-size: 10.5px; }
.usage-unavail { font-size: 12px; line-height: 1.5; color: var(--ink-dim); background: color-mix(in srgb, #f59e0b 10%, transparent); border: 1px solid color-mix(in srgb, #f59e0b 35%, var(--line)); border-radius: 8px; padding: 8px 10px; margin: 4px 0; }
.usage-note { margin-top: 14px; }
.wsel { background: var(--chip); color: var(--ink); border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; font-size: 13px; min-width: 200px; }
.wsel-sm { min-width: 0; flex: 1; padding: 4px 8px; font-size: 12.5px; }

/* layout picker + sidebar-mode toggle */
.ws-layout, .ws-modes { display: inline-flex; align-items: center; gap: 4px; }
.ws-layout-lbl { font-size: 12px; color: var(--ink-dim); margin-right: 4px; }
.ws-lbtn, .ws-mode { background: var(--chip); border: 1px solid var(--line); color: var(--ink-dim); border-radius: 8px;
  padding: 4px 11px; font-size: 12.5px; cursor: pointer; }
.ws-lbtn.on, .ws-mode.on { color: var(--ink); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }

/* the 8×2 grid-size picker — hover previews, click applies */
.ws-cellgrid { display: grid; grid-template-columns: repeat(8, 13px); grid-template-rows: repeat(2, 13px); gap: 2px; }
.ws-cell { padding: 0; border-radius: 2px; cursor: pointer; background: var(--chip); border: 1px solid var(--line); }
.ws-cell.on { background: color-mix(in srgb, var(--accent) 45%, transparent); border-color: var(--accent); }
.ws-cell.hov { background: var(--accent); border-color: var(--accent); }
.ws-layout-n { font-size: 12px; color: var(--ink-dim); margin-left: 6px; min-width: 34px; }

/* permission-mode selects — the two modes that change what runs unattended are tinted */
.ws-defmode { min-width: 148px; flex: none; }
.wsel-mode { min-width: 0; flex: 0 0 auto; width: 74px; padding: 3px 4px; font-size: 11.5px; }
.wsel-mode.danger { border-color: #f87171; color: #fca5a5; background: color-mix(in srgb, #f87171 14%, transparent); }
.wsel-mode.plan { border-color: #60a5fa; color: #93c5fd; background: color-mix(in srgb, #60a5fa 14%, transparent); }
.ws-modes { width: 100%; }
.ws-mode { flex: 1; }
/* Model + effort + fast-mode — matches Claude Code Desktop's own selector. wsel-model shows the
   full display name (wider than the fixed-width mode select, since names like "Opus 5" or a
   longer alias need room); wsel-effort is hidden entirely (see fillEffortSelect in app.js) for a
   model with no effort levels, same "just don't show it" treatment as ws-fastmode below. */
.wsel-model { min-width: 0; flex: 0 1 auto; max-width: 120px; padding: 3px 4px; font-size: 11.5px; }
.wsel-effort { min-width: 0; flex: 0 0 auto; width: 92px; padding: 3px 4px; font-size: 11.5px; }
.wsel-effort[hidden] { display: none; }
.ws-fastmode { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; color: var(--ink-dim); cursor: pointer; white-space: nowrap; }
.ws-fastmode[hidden] { display: none; }
.ws-fastmode-cb { cursor: pointer; }

/* body: sidebar + pane grid */
.ws-body { display: flex; gap: 12px; align-items: stretch; }
.ws-side { width: 293px; flex: 0 0 293px; display: flex; flex-direction: column; gap: 8px;
  background: var(--panel2); border: 1px solid var(--line); border-radius: 12px; padding: 10px; max-height: 78vh; overflow-y: auto; }
.ws-side-list { display: flex; flex-direction: column; gap: 8px; }
/* Repositories | Tree toggle stays pinned atop the scrolling side menu (bleeds over the
   sidebar's 10px padding so items scroll cleanly beneath it). */
.ws-modes { position: sticky; top: 0; z-index: 3; background: var(--panel2); margin: -10px -10px 4px; padding: 10px 10px 6px; }
/* org "cardboard" — the Brain-page grouping, compact for the sidebar */
.ws-orggroup { border: 1px solid color-mix(in srgb, var(--org, #64748b) 40%, var(--line)); border-radius: 10px; overflow: hidden;
  background: color-mix(in srgb, var(--org, #64748b) 6%, transparent); }
.ws-orggroup-hd { display: flex; align-items: center; gap: 7px; padding: 5px 9px; font-size: 12.5px; color: var(--ink);
  background: color-mix(in srgb, var(--org, #64748b) 13%, transparent); border-bottom: 1px solid color-mix(in srgb, var(--org, #64748b) 30%, var(--line)); }
.ws-org-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--org, #64748b); flex: 0 0 auto; }
.ws-org-count { margin-left: auto; font-size: 10.5px; color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.ws-orggroup-body { display: flex; flex-direction: column; gap: 2px; padding: 5px; }
.ws-side-item { display: flex; align-items: center; gap: 7px; width: 100%; text-align: left; background: transparent;
  border: 1px solid transparent; color: var(--ink); border-radius: 7px; padding: 5px 8px; font-size: 13px; cursor: pointer; }
.ws-side-item:hover { border-color: color-mix(in srgb, var(--org, #64748b) 55%, transparent); background: var(--chip); }
.ws-side-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-repobadge { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #34d399;
  border: 1px solid color-mix(in srgb, #34d399 45%, transparent); border-radius: 5px; padding: 0 4px; flex: 0 0 auto; }
.ws-side-sep { border-top: 1px solid var(--line); margin: 4px 0; }

/* tree — Windows-style collapsible */
.ws-tree-node { display: flex; flex-direction: column; }
.ws-tree-row { display: flex; align-items: center; gap: 4px; width: 100%; text-align: left; background: transparent; border: 0;
  color: var(--ink-dim); padding: 3px 6px; font-size: 12.5px; cursor: pointer; border-radius: 6px; user-select: none; }
.ws-tree-row:hover { background: var(--chip); }
.ws-tree-row.is-repo { color: var(--ink); font-weight: 600; }
.ws-chev { width: 14px; flex: 0 0 14px; text-align: center; font-size: 10px; color: var(--ink-dim); cursor: pointer; }
.ws-chev.ghost { cursor: default; }
.ws-tree-ic { flex: 0 0 auto; font-size: 12px; line-height: 1; }
.ws-tree-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* history */
.ws-hist { display: flex; flex-direction: column; gap: 6px; }
.ws-hist-hd { display: flex; align-items: center; gap: 7px; font-weight: 700; font-size: 12.5px; color: var(--ink); }
.ws-hist-scope { font-weight: 500; font-size: 11px; color: var(--ink-dim); }
.ws-hist-item { background: var(--chip); border: 1px solid var(--line); border-radius: 8px; padding: 7px 8px; display: flex; flex-direction: column; gap: 3px; }
.ws-hist-item.ws-hist-click { cursor: pointer; }
.ws-hist-item.ws-hist-click:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--chip)); }
.ws-hist-line1 { display: flex; justify-content: space-between; gap: 6px; font-size: 12px; }
.ws-hist-meta { color: var(--ink-dim); font-size: 10.5px; }
.ws-hist-first { font-size: 11.5px; color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-hist-actions { display: flex; align-items: center; gap: 4px; }
.ws-hist-when { font-size: 10px; color: var(--ink-dim); }
/* A history row whose worktree has been removed — real, permanent history, just not something
   Resume can casually continue (there's no checkout left to run in). The orange border/label
   says so up front, rather than surprising you with a refusal after clicking Resume. */
.ws-hist-item.missing-worktree { border-color: #f59e0b; }
.ws-hist-missing { color: #f59e0b; font-size: 10.5px; }
/* A conversation that's LIVE right now (a session running on its workspace, across any client) —
   a green border mirroring the orange "removed worktree" mark, plus an inline badge saying whether
   it's just live or actively working, and in how many chat boxes it's open. */
.ws-hist-item.ws-hist-live { border-color: #34d399; }
.ws-hist-label { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.ws-hist-livebadge { flex: 0 0 auto; font-size: 9.5px; color: #34d399; border: 1px solid color-mix(in srgb, #34d399 45%, transparent);
  border-radius: 999px; padding: 0 6px; white-space: nowrap; }
.ws-hist-livebadge.working { color: #86efac; border-color: #34d399; background: color-mix(in srgb, #34d399 16%, transparent); }
/* The toolbar "N conversations · M working · K clients" readout. */
.ws-livestats { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-dim);
  border: 1px solid var(--line); border-radius: 6px; padding: 2px 9px; }
.ws-livestats-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-mute); flex: 0 0 auto; }
.ws-livestats-dot.on { background: #34d399; animation: wsCursorBlink 1.4s step-start infinite; }

/* pane grid — up to 8 across × 2 down. Columns never shrink below WS_PANE_MIN, so on a
   narrower screen the grid scrolls sideways instead of crushing every pane to a sliver;
   on an ultrawide (5120px) eight columns land at ~600px each and 1fr wins. */
.ws-grid { flex: 1; display: grid; gap: 12px; min-width: 0; overflow-x: auto; overflow-y: hidden;
  grid-template-columns: repeat(var(--ws-cols, 1), minmax(340px, 1fr));
  grid-template-rows: repeat(var(--ws-rows, 1), minmax(0, 1fr)); }

/* `contain: layout paint` ISOLATES each pane's rendering: a change inside one pane (typing, a
   streaming reply, the compose box auto-growing) no longer forces the browser to re-lay-out and
   RE-PAINT the sibling panes. With several chat boxes open that was the multiplier behind
   "typing lags hard with 3 panes" on a weaker/software-rendering browser — each keystroke repainted
   the whole grid; now it repaints only its own pane. (Not `size`/`strict` containment — the grid
   still sizes the panes; only their internal layout+paint is fenced off. Native <select> popups and
   tooltips render outside the box and are unaffected.) */
/* The frame reads --edge, the SAME token chat-shell-theme.css gives the shell's header/footer seams,
   so "the border around the chat box" and "the border between its regions" are one value: a focused
   pane's pink frame gets pink seams for free, and neither can drift from the other. Focus therefore
   sets the TOKEN rather than border-color directly — setting border-color alone would have left the
   seams behind on the old colour, which is exactly the mismatch being fixed. */
.ws-pane { display: flex; flex-direction: column; min-width: 0; background: var(--panel2); border: var(--seam-w, 1px) solid var(--edge, var(--line));
  border-radius: 12px; overflow: hidden; min-height: 300px; contain: layout paint; }
/* --edge for a focused pane is set in chat-shell-theme.css, at a specificity that can actually win
   against the theme blocks there (see its own comment) — not here, where it silently lost. */
.ws-pane.on { box-shadow: 0 0 0 1px var(--accent); }
.ws-pane.ro { opacity: .96; }
.ws-pane-hd { display: flex; align-items: center; gap: 7px; padding: 7px 9px; border-bottom: 1px solid var(--line); background: var(--panel); }
/* The header's second row — size/wrap stats as chips, built live in wsPaintStatsRow(). Wraps onto a
   second line rather than clipping/scrolling on a narrow pane; zero bottom padding when empty costs
   nothing extra since the row still renders (chips are always present, even at zero — see comment in
   wsPaintStatsRow for why "none yet" must not look like "not tracked"). */
.ws-hstats { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; padding: 5px 9px 7px; border-bottom: 1px solid var(--line); background: var(--panel); }
/* Core's multi-chat toggle + tab strip. OFF by default (one repo usually means one thread); the
   `hidden` attribute (not just empty children) is set by wsPaintConvTabs so the row costs nothing
   until turned on — the lab's "closed, the drawer does not exist" reasoning applied here. */
.ws-multichat-label { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--ink-mute); cursor: pointer; white-space: nowrap; }
.ws-multichat-cb { accent-color: var(--accent); cursor: pointer; }
.ws-conv-tabs { display: flex; align-items: center; gap: 4px; padding: 4px 9px; border-bottom: 1px solid var(--line); background: var(--panel); flex-wrap: wrap; }
.ws-conv-tab { border: 1px solid var(--line); background: var(--chip); color: var(--ink-soft); border-radius: 8px;
  padding: 3px 9px; font-size: 11px; cursor: pointer; }
.ws-conv-tab.--act { border-color: var(--accent); color: var(--ink); background: color-mix(in srgb, var(--accent) 16%, var(--chip)); }
.ws-conv-tab-star { color: #fbbf24; }
.ws-conv-tab-add { border: 1px dashed var(--line); background: transparent; color: var(--ink-mute); border-radius: 8px;
  width: 22px; height: 22px; font-size: 12px; line-height: 1; cursor: pointer; }
.ws-conv-tab-add:hover { color: var(--accent); border-color: var(--accent); }
/* Pane-scoped dialog — the confirmation surface for manual wrap and any future in-pane dialog.
   Scoped to the PANE (.ws-pane / .pact-right), not the viewport — critical for the 4-pane cockpit,
   where a viewport-fixed dialog would open nowhere near the pane that raised it. */
.ws-pane-dialog-overlay { position: absolute; inset: 0; z-index: 80; display: flex; align-items: center;
  justify-content: center; padding: 16px; background: rgba(0,0,0,.5); }
.ws-pane-dialog { width: 100%; max-width: min(420px, 100%); max-height: 100%; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow);
  padding: 14px 16px; }
.ws-pane-dialog-hd { font-size: 14px; font-weight: 700; color: var(--ink); margin-bottom: 8px; }
.ws-pane-dialog-body { font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }
.ws-pane-dialog-btns { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.ws-pane-dialog-btn { padding: 7px 14px; border-radius: 8px; border: 1px solid var(--line); background: var(--chip);
  color: var(--ink); font-size: 12.5px; cursor: pointer; }
.ws-pane-dialog-btn:hover { filter: brightness(1.15); }
.ws-pane-dialog-btn.--primary { background: var(--accent); border-color: var(--accent); color: #1a0b2e; font-weight: 700; }
.ws-pane-dialog-btn:disabled { opacity: .5; cursor: not-allowed; filter: none; }
/* The identity readout — plain text, always visible, never a control. What this pane is actually
   showing (repo@worktree) must never be in doubt, so this reads directly from the same p.repo/
   p.worktree the controls below (now moved near the compose row) do — see paintPane(). */
.ws-identity { font-size: 12px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The live activity feed — "what's happening right now", separate from the chat transcript. Chat
   Shell Lab parity: this is a CHIP in the header row (like the lab's agents/recon chips), not a
   permanent full-width row the lab never had — everything the orange Send button alone couldn't
   tell you: sending, thinking, streaming, a tool running, waiting for permission, done, a
   connection hiccup. See logActivity()/renderActivityLog(). */
/* The cap is a LENGTH, never a percentage. `max-width: 34%` resolved against the shrink-to-fit slot
   this chip lives in, and that slot sizes itself to its own max-content — so the slot was first sized
   to fit the FULL activity text, then the chip capped itself at 34% of that width, leaving the
   difference as dead space INSIDE the slot. Measured live: a 273px slot holding 125px of controls,
   which parked the × button 148px short of the header's right edge while the stats row below it ended
   flush — the visible "these are not aligned to the right" report. A length has no such circularity,
   and flex-shrink still lets it give way on a narrow pane. */
.ws-activity-wrap { position: relative; flex: 0 1 auto; min-width: 0; max-width: 260px; }
.ws-activity { border: 1px solid var(--line); border-radius: 999px; padding: 2px 9px; font-size: 11px;
  color: var(--ink-dim); background: var(--chip); cursor: pointer; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; display: block; }
.ws-activity:hover { color: var(--ink); border-color: var(--accent); }
.ws-activity.ws-act-ok { color: #34d399; }
.ws-activity.ws-act-err { color: #f87171; }
/* The expanded log docks BELOW its chip as an anchored dropdown (like the bookmark popover) rather
   than an in-flow sibling row that always costs height, even collapsed. */
.ws-activity-log { position: absolute; top: calc(100% + 4px); right: 0; z-index: 40; width: max(240px, 100%);
  max-height: 220px; overflow-y: auto; background: var(--panel2); border: 1px solid var(--line);
  border-radius: 8px; box-shadow: var(--shadow); padding: 4px 9px; display: flex; flex-direction: column; gap: 2px; }
.ws-activity-log[hidden] { display: none; }
.ws-activity-item { font-size: 10.5px; color: var(--ink-dim); display: flex; gap: 6px; }
.ws-activity-item.ws-act-ok { color: #34d399; }
.ws-activity-item.ws-act-err { color: #f87171; }
.ws-activity-time { color: var(--ink-mute); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
/* The interactive controls (repo/worktree/mode/history/usage) — moved down here, next to the
   compose row, rather than pinned in a header far from where you're actually typing. Mirrors
   Claude's own chat UI, which keeps its controls near the input, not in a fixed top bar. */
/* Core's model bar now sits at the BOTTOM of the pane (was above the composer) — matching Pact's
   .pc-modelbar so the two workspaces read identically. Same surface treatment as Pact's. */
/* Pane turn-lock status icon — plain CSS spinner (no glyph, no dependency): a muted ring
   at rest, spinning while the pane's session is mid-turn (busy), still again the moment
   that turn concludes (success or error). Toggled by paintPane() from p.status. */
.ws-status { display: inline-block; width: 11px; height: 11px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--ink-dim); }
.ws-status.spinning { border-top-color: #34d399; animation: wsSpin .7s linear infinite; }
@keyframes wsSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ws-status.spinning { animation: none; border-color: #34d399; } }
/* Deep Work — the visible turn already ended but Claude's SDK query is still producing content
   (a backgrounded Bash/Task settling with no new prompt sent, see claudeSession.mjs). Still spins
   (it's still `busy`) but in red, not green — deliberately alarming-adjacent so it reads as
   distinct from an ordinary foreground turn, not just "still thinking as expected". */
.ws-status.spinning.deepwork { border-top-color: #f87171; }
@media (prefers-reduced-motion: reduce) { .ws-status.deepwork { animation: none; border-color: #f87171; } }
.ws-usage { font-size: 10.5px; color: var(--ink-dim); white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* "✓ Saved" pane-header badge — a calm green chip confirming the conversation is fully persisted
   and safe to close / continue elsewhere (see p._saved in app.js). Hidden while a turn is live. */
.ws-saved { font-size: 10.5px; color: #34d399; border: 1px solid color-mix(in srgb, #34d399 45%, transparent);
  border-radius: 999px; padding: 1px 8px; white-space: nowrap; flex: 0 0 auto; }
.ws-saved[hidden] { display: none; }
.ws-x, .ws-ico { background: transparent; border: 1px solid transparent; color: var(--ink-dim); border-radius: 6px; cursor: pointer; font-size: 13px; padding: 2px 6px; }
.ws-x:hover, .ws-ico:hover { border-color: var(--line); color: var(--ink); }

.ws-transcript { flex: 1; background: transparent; padding: 11px 12px; min-height: 220px; max-height: 60vh;
  overflow-y: auto; display: flex; flex-direction: column; gap: 8px; font-size: 13px; line-height: 1.5; }
/* Stick-to-bottom wrapper (attachStickController): a thin relative box placed exactly where a scroll
   container sat, so the "↓ New output" pill can float over the viewport instead of scrolling away
   with the content. The scroll element fills the wrapper; the wrapper carries its old flex sizing. */
.stick-wrap { position: relative; display: flex; flex-direction: column; min-width: 0; }
/* CORE IS THE ONLY FLEXING REGION (chat-shell.js's model: "FOOTER grows with the type box, taking
   height FROM Core"). A 220px floor here contradicted that: once the footer grew past what was left,
   the pane's `overflow: hidden` clipped the BOTTOM of the footer — the model row with Compact│Wrap
   and the context readout simply vanished below the pane's edge. Measured on a real 1×1 pane: header
   84 + floor 220 + footer 185 = 489 inside a 472px pane. The transcript yields instead; the Lab does
   the same thing (it hides Core outright at full expand). */
.stick-wrap-ws { flex: 1; min-height: 0; max-height: 60vh; }
.stick-wrap-ws > .ws-transcript { flex: 1; min-height: 0; max-height: none; }
.stick-wrap-pc { flex: 1; min-height: 0; }
.stick-wrap-pc > .pc-scroll { flex: 1; min-height: 0; }
/* The floating pill: hidden while the reader is pinned to the tail; shown + blinking when new output
   arrived while they were scrolled up. Bottom-right of the wrapper, so it sits above any compose row. */
.stick-pill { position: absolute; right: 12px; bottom: 12px; z-index: 5; display: none; align-items: center; gap: 5px;
  padding: 5px 11px; border-radius: 999px; cursor: pointer; font-size: 12px; font-weight: 600; line-height: 1;
  background: var(--accent); color: #fff; border: 1px solid var(--accent); box-shadow: 0 3px 10px rgba(0, 0, 0, .35); }
/* The pill blinks in EVERY pane whose reader is scrolled up ("Held"), so with a cockpit of eight it
   is eight simultaneous animations — worth the same treatment as the work ring: the pill's own
   OPACITY pulses (composited), and the accent halo is a static ring on ::after whose opacity pulses
   with it. The old keyframes animated box-shadow, which repaints every frame. */
.stick-pill.--show { display: inline-flex; animation: stickPulse 1.15s ease-in-out infinite; }
.stick-pill.--show::after {
  content: ""; position: absolute; inset: 0; border-radius: 999px; pointer-events: none;
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 30%, transparent);
  opacity: 0; will-change: opacity; animation: stickHalo 1.15s ease-in-out infinite;
}
.stick-pill:hover { filter: brightness(1.08); }
@keyframes stickPulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@keyframes stickHalo  { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .stick-pill.--show { animation: none; }
  .stick-pill.--show::after { animation: none; opacity: 0; } }
/* Persistent scroll-MODE bulb (attachStickController): always-visible readout of whether the transcript
   is following the tail (green "Live" — incoming messages scroll in) or holding your scrolled-up spot
   (amber "Held" — incoming messages stay put). Bottom-LEFT, opposite the "↓ New output" pill; click to
   jump to the latest and go Live. */
.stick-mode { display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px 3px 8px; border-radius: 999px; cursor: pointer; font-size: 11px; font-weight: 600; line-height: 1;
  background: color-mix(in srgb, var(--panel) 82%, transparent); border: 1px solid var(--line); color: var(--ink-dim);
  opacity: .82; transition: opacity .15s; }
.stick-mode:hover { opacity: 1; }
/* Placement variants (attachStickController.dockMode picks one):
   --float: default, floats bottom-left over the transcript (fallback / mobile Core for now).
   --dock : docked just above the Send button on desktop — off the transcript text (Core + Pact). */
.stick-mode--float { position: absolute; left: 10px; bottom: 12px; z-index: 5; }
.stick-mode--dock { position: absolute; right: 0; bottom: calc(100% + 6px); z-index: 6; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,.3); }
/* --bar: inline inside the Pact mobile bottom mode-strip (a normal flex child, not floating). */
.stick-mode--bar { position: static; }
/* --head: inline inside the chat/pane HEADER (mobile) — the Live/Held bulb lives up by the workspace
   title/medallion, not the bottom controls row. Compact so it fits the header. */
.stick-mode--head { position: static; font-size: 10px; padding: 2px 9px 2px 7px; }
/* --seam: centred on the SEAM between the transcript and the footer (desktop) — matching the Chat
   Shell Lab's placement exactly, the middle of Core's lowest border, not a corner near Send. */
.stick-mode--seam { position: static; }
.ws-seam-bulb { display: flex; justify-content: center; padding: 3px 0; background: var(--panel);
  border-bottom: 1px solid var(--line); min-height: 22px; }
/* Header dock hosts — mobile only; empty (and hidden) on desktop, where the bulb docks above Send. */
.pc-head-bulb, .ws-head-bulb { display: none; align-items: center; flex: 0 0 auto; margin: 0 2px; }
/* The Pact mobile "drawer" strip that hosts the mode bulb, directly under the control bar. */
/* The Conversations/Bookmarks controls are RISER TABS glued to the top edge of the footer nav (Ouronet
   "Controls [N]" style): "💬 Conversations [n]" bottom-LEFT, "★ Bookmarks" bottom-RIGHT, flanking the
   centered Live/Held bulb. align-items:flex-end + zero bottom padding drops the tabs onto the footer bar. */
.pactm-modebar { display: flex; justify-content: space-between; align-items: flex-end; gap: 8px; padding: 3px 4px 0; flex: 0 0 auto; }
.pactm-modebar-bulb { flex: 1 1 auto; display: flex; justify-content: center; align-items: center; min-width: 0; margin-bottom: 3px; }
.pactm-modebar .stick-mode { font-size: 10.5px; padding: 2px 10px 2px 8px; opacity: 1; }
/* Mobile model picker docked in the mode strip (both workspaces) — grows to fill the middle without crushing
   the Conversations/Bookmarks bars flanking it. */
.pactm-modebar .cm-mmodel, .ws-mmodebar .cm-mmodel { flex: 1 1 auto; min-width: 0; max-width: 46%; align-self: center; height: 22px; }
.pactm-mbtn { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; height: 19px; padding: 0 8px;
  border-radius: 7px 7px 0 0; border: 1px solid color-mix(in srgb, var(--accent) 48%, var(--line)); border-bottom: none;
  background: color-mix(in srgb, var(--accent) 13%, var(--panel)); color: var(--accent);
  font-size: 9.5px; font-weight: 700; cursor: pointer; white-space: nowrap; -webkit-tap-highlight-color: transparent; }
.pactm-mbtn:active { background: color-mix(in srgb, var(--accent) 24%, var(--panel)); }
.pactm-mbtn-n { font-size: 9.5px; font-weight: 800; color: var(--accent); }
.pactm-mbtn-bm { color: #fbbf24; border-color: color-mix(in srgb, #fbbf24 45%, var(--line)); background: color-mix(in srgb, #fbbf24 11%, var(--panel)); }
/* Relative hosts so a --dock bulb anchors directly above the Send button (desktop Core + Pact). */
.ws-send-wrap, .pc-send-wrap { position: relative; display: inline-flex; }
.stick-mode-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: var(--ink-mute); }
.stick-mode.--live { color: #34d399; border-color: color-mix(in srgb, #34d399 45%, var(--line)); }
.stick-mode.--live .stick-mode-dot { background: #34d399; box-shadow: 0 0 6px #34d399; animation: stickBulb 1.6s ease-in-out infinite; }
.stick-mode.--held { color: #f59e0b; border-color: color-mix(in srgb, #f59e0b 45%, var(--line)); }
.stick-mode.--held .stick-mode-dot { background: #f59e0b; box-shadow: none; }
@keyframes stickBulb { 0%, 100% { opacity: 1; } 50% { opacity: .4; } }
@media (prefers-reduced-motion: reduce) { .stick-mode.--live .stick-mode-dot { animation: none; } }
/* Per-turn wrapper for the incremental renderer (renderTranscriptInto): generates no box of its
   own (`display:contents`), so its child lines participate in the transcript's flat flex column
   (row gap, per-line align-self) exactly as if they were direct children — the wrapper is only a
   single cacheable, atomically-insertable/removable DOM node per turn. Keeping the DOM SMALL (a
   capped number of turns; see WS_TURN_RENDER_CAP) is what keeps a long conversation light for a
   weak client — deliberately NOT `content-visibility:auto`, whose on-scroll rendering made
   scrolling feel like it was loading. */
.ws-turn { display: contents; }
/* "Show earlier messages" — the affordance to render turns beyond the cap, at the top of the
   transcript, styled as a quiet full-width chip rather than a loud button. */
.ws-show-earlier { align-self: center; background: var(--chip); color: var(--ink-dim); border: 1px solid var(--line);
  border-radius: 999px; padding: 4px 14px; font-size: 12px; cursor: pointer; margin-bottom: 2px; }
.ws-show-earlier:hover { color: var(--ink); border-color: var(--accent); }
.ws-line { white-space: pre-wrap; word-break: break-word; }
/* Exact match to the chat-shell lab's own bubble colours (--bub-u/-line/-fg, --bub-a/-line, set per
   workspace above) — not merely a derived approximation. This replaced a color-mix() formula that was
   only theme-CONSISTENT (picked up the right accent) but not colour-IDENTICAL to the lab; "same
   colours" means the literal hex, so the lab's own flat values are used directly, with the old
   color-mix as a graceful fallback for light mode (no lab light design exists to copy) and any other
   context these tokens aren't defined in. */
.ws-line.ws-user { color: var(--bub-u-fg, #fff); background: var(--bub-u, color-mix(in srgb, var(--accent) 26%, var(--panel-2))); border: 1px solid var(--bub-u-line, color-mix(in srgb, var(--accent) 40%, var(--line))); border-radius: 8px; padding: 7px 10px; align-self: flex-end; max-width: 90%; position: relative; }
/* Absolute prompt/response number tags (P#n / R#n) — a non-interactive corner label you can refer to.
   Counts the whole conversation, including turns not currently on screen (see server promptOffset). */
.ws-num { position: absolute; top: -10px; z-index: 1; font: 700 11.5px ui-monospace, monospace; line-height: 1.5;
  padding: 1px 7px; border-radius: 8px; letter-spacing: .4px; pointer-events: none; white-space: nowrap; box-shadow: 0 1px 3px rgba(0,0,0,.4); }
.ws-num-p { right: 6px; background: #1b4fc4; color: #dbe6ff; border: 1px solid rgba(255,255,255,.30); }
/* FIXED violet, not var(--accent) — this is the P#/R# addressing scheme (comment above the workspace
   identity block, line ~24, already says so); it must read identically in Core, Pact, and light mode or
   a user could never learn it. Was riding --accent, which drifts per workspace/theme — a real bug: R#
   stopped being the same color the moment Pact's teal or light mode was in play. */
.ws-num-r { left: 6px; background: #c77dff; color: #1a0a1e; }
/* Interrupted prompt = DARK BLUE / Discarded = RED, with ▶ resume (green, left) · ✕ discard (red, right). */
.ws-line.ws-interrupted { background: #172554; box-shadow: inset 0 0 0 1px #3b5bbf; }
.ws-line.ws-discarded { background: #3a0d0d; color: #fca5a5; box-shadow: inset 0 0 0 1px #7f1d1d; }
.ws-intr-btn { position: absolute; bottom: -13px; width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--bg, #0b0f14);
  font-size: 12px; font-weight: 800; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 5px rgba(0,0,0,.5); z-index: 2; }
.ws-intr-resume { left: 8px; background: #22c55e; color: #052e13; }
.ws-intr-discard { right: 8px; background: #ef4444; color: #2a0606; }
.ws-intr-btn:hover { filter: brightness(1.1); }
/* THE ANSWER BUBBLE'S CORNER ROW (see wsMsgHead in app.js). The medallion and the ⧉ ↩ ⤴ ★ buttons
   used to be five separately-pinned absolute boxes, so none of them added anything to the bubble's
   width — and on a short answer the buttons were drawn straight over the medallion (measured:
   R#7,767 in a 153px bubble, gap -21px). Here they are one IN-FLOW flex row, so the bubble's own
   intrinsic width includes them and a short answer is widened to fit. No min-width to guess at.

   The margins do two things at once: they cancel the row's height entirely (-17 top and -3 bottom
   against the 20px the row measures) so no bubble grew a millimetre taller, and the -4px sides put
   the medallion at the same x it had at `left: 6px` against this bubble's 10px padding. Both
   numbers were measured, not chosen: the medallion lands at x=7, y=-9 and the buttons 7px from the
   right edge — pixel-for-pixel where the old pins put them. */
.ws-msghead { display: flex; align-items: center; gap: 8px; margin: -17px -4px -3px; flex-wrap: wrap; }
/* `wrap` is a SAFETY NET, not a layout: measured across 117 real answers at 1400/700/460/320px the
   row never wrapped (every row 20px tall, no bubble overflowed its column). It matters only where
   the column is narrower than the row itself — an 8-column grid on a small screen — and there it
   turns an overflow into an extra line. Wrapping also caps the bubble's MIN-content at its widest
   single item, while fit-content still uses the row's full width, so the row stays on one line
   everywhere it fits. */
/* A pinned child contributes no width — which is the entire bug. Nothing in this row may be pinned,
   whatever the per-element rules below say for the contexts where the pins are still right.
   Written with THREE classes on purpose: the rules it has to beat (`.ws-assistant .ws-copy-btn`
   and friends) carry two, and a tie would be settled by source order — i.e. by where in this file
   somebody happens to paste the next rule. Measured the tie first: the buttons stayed absolute and
   the -21px overlap was still there. */
.ws-line.ws-assistant .ws-msghead > *, .pc-msg.pc-asst .ws-msghead > * {
  position: static; top: auto; left: auto; right: auto; }
/* Takes the slack so a WIDE bubble still reads medallion-hard-left, buttons-hard-right. Its
   flex-basis is the minimum breathing space the two are allowed to close to on a narrow one. */
.ws-msghead .ws-msghead-sp { flex: 1 1 8px; min-width: 8px; }

/* ★ Bookmark a response + the jump-list popup. */
.ws-bm-star { position: absolute; top: -9px; right: 6px; z-index: 2; width: 22px; height: 20px; border: none; background: transparent;
  color: var(--ink-mute); font-size: 14px; line-height: 1; cursor: pointer; padding: 0; }
.ws-bm-star.on { color: #fbbf24; }
.ws-bm-star:hover { color: #fbbf24; filter: brightness(1.15); }
/* ⤴ Share a response — sits just left of the ★, same floating style. */
.ws-share-btn { position: absolute; top: -9px; right: 30px; z-index: 2; width: 22px; height: 20px; border: none; background: transparent;
  color: var(--ink-mute); font-size: 15px; line-height: 1; cursor: pointer; padding: 0; }
.ws-share-btn:hover { color: #34d399; filter: brightness(1.15); }
.ws-share-btn.copied { color: #34d399; }
/* ↩ Reply/quote — on a PROMPT it sits inline next to "you" (no star/share there to share the corner
   with); on an ANSWER it joins the same floating corner row as ⤴/★, one slot further left. */
.ws-reply-btn { background: transparent; border: none; color: var(--ink-mute); font-size: 13px; line-height: 1;
  cursor: pointer; padding: 0 4px; vertical-align: middle; }
.ws-reply-btn:hover { color: var(--accent); filter: brightness(1.15); }
.ws-assistant .ws-reply-btn, .pc-asst .ws-reply-btn { position: absolute; top: -9px; right: 54px; z-index: 2;
  width: 22px; height: 20px; padding: 0; }
/* NOT `content-visibility: auto` on message rows — tried, measured (~11% less main-thread work,
   ~15% less layout with eight panes) and REVERTED, because it broke two visible things and the
   trade was not close:
     • it implies `contain: paint`, which CLIPS anything drawn outside the row's border box — and the
       P#/R# badges sit at `top: -9px`, so the turn numbers were cut off;
     • off-screen rows fall back to `contain-intrinsic-size` estimates, so `scrollHeight` shifts as
       rows render — the transcript slid on its own, and the stick controller reads exactly that
       value to decide "am I at the bottom".
   Left here as a signpost: a future attempt needs the badges inside the row's box first. */

/* ⧉ Copy this turn — on a PROMPT it sits inline beside "you" (the corner there is empty); on an
   ANSWER it joins the floating corner row, one slot further left than ↩. Same geometry as the three
   it stands beside, so the row stays a row. Click copies the raw text; Alt/Shift copies the turn's
   reference (see wsCopyMsgBtn) — the tooltip says so, since an unadvertised modifier helps nobody. */
.ws-copy-btn { background: transparent; border: none; color: var(--ink-mute); font-size: 13px; line-height: 1;
  cursor: pointer; padding: 0 4px; vertical-align: middle; }
.ws-copy-btn:hover { color: var(--accent); filter: brightness(1.15); }
.ws-copy-btn.copied { color: #34d399; }
.ws-assistant .ws-copy-btn, .pc-asst .ws-copy-btn { position: absolute; top: -9px; right: 78px; z-index: 2;
  width: 22px; height: 20px; padding: 0; }
/* The removable chips shown above the compose box for pending reply references (round 22 design:
   "reply to a turn the way a chat reply works"). Empty/absent when there is nothing pending — costs
   no layout space until used. */
.ws-reply-row-host:empty { display: none; }
.ws-reply-row { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0; }
.ws-reply-chip { display: inline-flex; align-items: center; gap: 6px; max-width: 260px; padding: 3px 6px 3px 9px;
  border-radius: 8px; background: var(--chip); border: 1px solid var(--line); border-left: 3px solid var(--accent);
  font-size: 11px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-reply-chip-x { flex: 0 0 auto; border: none; background: transparent; color: var(--ink-mute); cursor: pointer;
  font-size: 13px; line-height: 1; padding: 0 2px; }
.ws-reply-chip-x:hover { color: var(--danger); }
.ws-bm-wrap { position: relative; display: inline-flex; }
.ws-bm-btn { background: var(--chip); border: 1px solid var(--line); color: #fbbf24; border-radius: 8px; padding: 6px 11px; cursor: pointer; font-size: 15px; line-height: 1; }
.ws-bm-btn:hover { filter: brightness(1.2); }
.ws-bm-pop { position: absolute; bottom: calc(100% + 8px); right: 0; min-width: 250px; max-width: 340px; max-height: 300px; overflow-y: auto;
  background: #0e1420; border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 8px 26px rgba(0,0,0,.55); padding: 6px; z-index: 50; display: none; }
.ws-bm-pop.--show { display: block; }
.ws-bm-pop.--down { bottom: auto; top: calc(100% + 8px); }   /* opens below the anchor (Pact head ★) */
.pc-bm-ico.pact-ed-ico { color: #fbbf24; }
.ws-bm-hd { font-size: 11px; font-weight: 700; color: var(--ink-mute); padding: 4px 8px 7px; }
.ws-bm-row { display: flex; gap: 9px; align-items: baseline; padding: 7px 8px; border-radius: 7px; cursor: pointer; }
.ws-bm-row:hover { background: var(--chip); }
.ws-bm-rn { flex: 0 0 auto; font: 700 10px ui-monospace, monospace; color: var(--accent); }
.ws-bm-snip { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--ink-dim); }
.ws-bm-del { flex: 0 0 auto; width: 24px; height: 24px; border: none; background: transparent; color: var(--ink-mute);
  font-size: 17px; line-height: 1; cursor: pointer; border-radius: 6px; -webkit-tap-highlight-color: transparent; }
.ws-bm-del:hover { color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.ws-bm-empty { padding: 10px 8px; font-size: 12px; color: var(--ink-mute); }
.ws-bm-flash { animation: wsBmFlash 1.6s ease-out; }
@keyframes wsBmFlash { 0% { box-shadow: 0 0 0 3px #fbbf24; } 100% { box-shadow: 0 0 0 3px transparent; } }
.ws-mcbtn-bm { color: #fbbf24; }
.ws-bm-sheet { display: flex; flex-direction: column; gap: 2px; padding: 4px 0 12px; }
.ws-bm-sheet .ws-bm-row { padding: 12px; min-height: 46px; -webkit-tap-highlight-color: transparent; }
.ws-bm-sheet .ws-bm-snip { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
/* A message typed and sent while Claude was still working on the previous turn — "frozen in
   cache" until that turn finishes (see send()/drainQueue() in app.js), then dispatched for real
   (merged with any other messages queued alongside it into one prompt). Orange text as well as
   an orange border/tint, distinct from a normal (already-accepted, white-on-blue) user bubble,
   so it's obvious this hasn't actually gone out yet — the same orange it's released into white
   the instant the turn actually finishes and drainQueue() fires. */
.ws-line.ws-queued { background: color-mix(in srgb, #f59e0b 16%, var(--chip)); border: 1px solid #f59e0b; color: #f59e0b; }
.ws-queued-tag { display: block; margin-top: 3px; font-size: 10px; font-weight: 400; color: #f59e0b; }
/* A message queued while the pane was specifically in "deepwork" (not just an ordinary busy
   turn) — pink instead of orange, matching the Send button/status dot's own Deep Work red/pink
   treatment, so it's clear at a glance THIS queued message is waiting on open-ended background
   activity, not "hang tight a moment". */
.ws-line.ws-queued.ws-queued-deep { background: color-mix(in srgb, #f472b6 16%, var(--chip)); border-color: #f472b6; color: #f472b6; }
.ws-line.ws-queued.ws-queued-deep .ws-queued-tag { color: #f472b6; }
/* A prompt that WAS actually accepted and sent (not queued — this is still the normal blue
   bubble's shape/position), but landed within DEEP_WORK_RISK_GRACE_MS of a "deepwork" phase
   ending — real chance that phase's own leftover output arrives instead of (or mixed with) a
   reply to this message. A red ring around the ordinary blue bubble, not a wholesale recolor —
   this message genuinely was sent, unlike the orange/pink queued treatment above for one that
   wasn't yet. */
.ws-line.ws-user.ws-deepwork-risk { box-shadow: 0 0 0 2px #f87171; }
.ws-deepwork-risk-tag { display: block; margin-top: 4px; font-size: 10px; font-weight: 400; color: #ffd6d6; }
/* Claude's own signature color throughout the rest of the UI (tooltips, headers) — reused here
   so a reply reads as distinctly "Claude" at a glance, the same way the user's own text reads as
   distinctly "you" against its solid blue bubble above. */
.ws-line.ws-assistant { color: var(--accent); align-self: flex-start; max-width: 90%; position: relative;
  background: var(--bub-a, var(--chip)); border: 1px solid var(--bub-a-line, var(--line)); border-radius: 8px; padding: 7px 10px; }   /* boxed like a prompt: one response = one rectangle; --bub-a matches the lab exactly */
/* A fenced code block within a reply — its own bordered "copy paste window", separate from the
   surrounding prose. Matches Claude's own code-block rendering: a small header (language +
   copy) above the monospaced body. Only a FENCED block gets the copy button — the prose around
   it gets lightweight markdown (bold/headers/lists/inline code) via renderInline/renderProseBlock
   in app.js, not a second copy-paste window. */
.ws-codeblock { background: #05070f; border: 1px solid var(--line); border-radius: 8px; margin: 6px 0; overflow: hidden; }
.ws-codeblock-hd { display: flex; align-items: center; justify-content: space-between; padding: 5px 9px;
  font-size: 10.5px; color: var(--ink-mute); text-transform: uppercase; letter-spacing: .03em; border-bottom: 1px solid var(--line); }
.ws-codeblock-body { margin: 0; padding: 10px 12px; font: 12px/1.5 ui-monospace, monospace; color: #cdd6f4;
  white-space: pre-wrap; word-break: break-word; overflow-x: auto; }
/* Inline `code` in prose — a lighter-weight cousin of the fenced .ws-codeblock above: no header,
   no copy button, just enough visual distinction (chip background, monospace) to read as code
   rather than prose, matching Claude's own inline-code rendering. */
.ws-inline-code { background: var(--chip); border-radius: 4px; padding: 1px 5px; font: 12px/1.4 ui-monospace, monospace; }
.ws-line.ws-assistant .ws-inline-code { color: var(--ink); }
/* A genuine outlier reply (a full technical document pasted into the chat, 10-30k+ characters)
   collapses to a fixed height with a fade at the bottom, rather than taking over the screen. The
   full text is never removed — only ::after's fade masks it — so a reload / a resync / print still
   has the complete reply; only the LIVE, collapsed view is shorter until you click through. Shared
   by Core's .ws-reply-body wrapper and Pact's .pc-asst-body directly (see wsReplyCollapseToggle /
   wsRenderReplyBody in app.js) — one rule serves both, so they can't visually drift apart. */
.ws-reply-collapsed { max-height: 340px; overflow: hidden; position: relative; }
.ws-reply-collapsed::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 64px;
  background: linear-gradient(to bottom, transparent, var(--bub-a, var(--chip))); pointer-events: none; }
.ws-reply-expand { display: block; margin: 4px 0 2px; padding: 5px 10px; font-size: 11.5px; color: var(--ink-mute);
  background: var(--chip); border: 1px solid var(--line); border-radius: 6px; cursor: pointer; }
.ws-reply-expand:hover { color: var(--ink); border-color: var(--ink-mute); }
/* A `#`..`######` line in a reply — bold and one size step up, not a full heading hierarchy
   (this is a chat transcript, not a document — six visually distinct heading sizes would be
   excessive here; one consistent step reads clearly enough at any nesting depth). */
.ws-md-heading { display: block; font-size: 1.08em; margin: 2px 0; }
/* Images attached to a sent prompt — up to 5, Claude Code's own limit — a row of modest
   thumbnails above the message text (its own block, not inline with it), wrapping onto more than
   one line when there isn't room; each clickable through to its full image in a new tab. */
.ws-user-images { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.ws-user-image-link { display: block; }
.ws-user-image { display: block; max-width: 140px; max-height: 140px; border-radius: 6px; border: 1px solid rgba(255,255,255,.25); }
/* Always visible (not hover-only — has to work on touch), sitting in the code block's own header
   rather than floating over the text. */
.ws-copy { background: transparent; border: 1px solid var(--line); border-radius: 6px; color: var(--ink-dim);
  font-size: 11px; line-height: 1; padding: 2px 6px; cursor: pointer; }
.ws-copy:hover { color: var(--ink); border-color: var(--accent); }
.ws-copy.copied { color: #34d399; border-color: #34d399; }
.ws-line.ws-tool { color: #7c9cff; font-size: 12px; font-family: ui-monospace, monospace; }
.ws-line.ws-toolres { color: var(--ink-dim); font-size: 11.5px; }
.ws-line.ws-result { color: #34d399; font-size: 11.5px; }
.ws-line.ws-note { color: #34d399; font-size: 12px; }
.ws-line.ws-err { color: #f87171; font-size: 12.5px; }
/* The live-typing preview (see app.js's assistant_delta handling): text streamed so far this
   turn, not yet finalized. A soft blinking cursor after the text is the only visual difference
   from a normal assistant line — the "Claude is typing" signal, matching the desktop app's feel,
   without looking like a second, separate kind of message. */
.ws-line.ws-live::after { content: "▍"; display: inline-block; margin-left: 1px; color: var(--accent); animation: wsCursorBlink 1s step-start infinite; }
@keyframes wsCursorBlink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .ws-line.ws-live::after { animation: none; opacity: .6; } }
.ws-toolgroup { color: #7c9cff; font-size: 12px; }
.ws-toolgroup-summary { cursor: pointer; list-style: none; }
.ws-toolgroup-summary::-webkit-details-marker { display: none; }
.ws-toolgroup-summary::before { content: "▸"; display: inline-block; margin-right: 4px; transition: transform .1s ease; }
.ws-toolgroup[open] > .ws-toolgroup-summary::before { transform: rotate(90deg); }
.ws-toolgroup > .ws-line { margin: 4px 0 0 14px; }
.ws-compose { display: flex; gap: 7px; align-items: flex-end; padding: 8px 9px; border-top: 1px solid var(--line); }
.ws-compose.ws-drag { background: var(--chip); box-shadow: inset 0 0 0 1px var(--accent); }
/* Action cluster (attach / stop / send). A horizontal group on desktop; the phone block below turns
   it into a vertical column of round icon buttons so a full-width text box can sit beside it. */
.ws-compose-btns { display: flex; align-items: flex-end; gap: 7px; flex: 0 0 auto; }
/* "N lines" — the honest signal of how big the prompt has gotten once the 40% cap hides the rest. */
.ws-lines-chip { font-size: 10.5px; color: var(--ink-mute); white-space: nowrap; padding: 0 2px; align-self: center; }
/* Ultracode — same "autolbl" checkbox-with-caption look used by fast-mode/multi-chat elsewhere. */
.ws-ultracode-label { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; color: var(--ink-dim); cursor: pointer; white-space: nowrap; }
/* Grows with what you type — up to WS_PROMPT_MAX_ROWS lines (app.js's wsAutoResizePrompt), then
   scrolls — matching Claude Code's own desktop compose box instead of staying a fixed 2-line box.
   `resize: none`: manual drag-resize would just fight the auto-grow on the next keystroke. */
.ws-prompt { flex: 1; background: var(--chip); color: var(--ink); border: 1px solid var(--line); border-radius: 10px; padding: 8px 30px 8px 11px;
  font-family: inherit; font-size: 13px; resize: none; min-height: 40px; overflow-y: hidden; }
.ws-prompt:disabled { opacity: .5; }
/* The jump/recall search toggle — pinned to the compose field's right edge, mirroring the lab
   (chat-shell-lab.html's #f-search). ws-prompt reserves right padding above so typed text can never
   run under it. */
.ws-tawrap { flex: 1; position: relative; min-width: 0; }
.ws-tawrap .ws-prompt { width: 100%; }
/* LINE-NUMBER GUTTER — matches chat-shell-lab.html's .gutter exactly. Absolutely positioned,
   deliberately: as a flex sibling with align-items:stretch it would take its height FROM the
   textarea, which the textarea's own height depends on (via the padding-left it reserves for the
   gutter) — a circular dependency. Taking the gutter out of the sizing path breaks that: the
   textarea's height is driven only by its own content, and the gutter just stretches to match via
   top/bottom. Width is set from JS (wsPaintGutter) to the measured digit count; this is only the
   initial/fallback value. */
.ws-gutter { position: absolute; left: 1px; top: 1px; bottom: 1px; padding: 7px 8px 7px 4px; text-align: right;
  font: 11px/18px ui-monospace, Consolas, monospace; color: var(--ink-mute); overflow: hidden; pointer-events: none;
  border-right: 1px solid var(--line); width: 22px; }
.ws-gutter b { display: block; font-weight: 400; color: var(--ink-mute); opacity: .55; }
.ws-gutter b.--on { color: var(--ink); opacity: 1; font-weight: 600; }
/* ⇕ Expand — same icon-button treatment as attach/compact/wrap; toggled state has no separate look
   because the label itself already states the action (see app.js's expandBtn: "⇕ 40%" / "⇕ Full"
   is the destination, not the current state — matching the lab's f-expand). */
.ws-expand { font-size: 11px; }
.ws-search-toggle { position: absolute; right: 6px; bottom: 8px; width: 20px; height: 20px; padding: 0;
  border: 1px solid var(--line); border-radius: 6px; background: var(--panel2); color: var(--ink-mute);
  font-size: 12px; line-height: 1; cursor: pointer; }
.ws-search-toggle:hover { color: var(--ink); border-color: var(--accent); }
/* The drawer itself — closed, it isn't rendered at all (see searchDrawer.hidden in app.js), so it
   costs nothing when unused. Reuses the exocortex's REAL .exo-jump/.exo-jump-in/.exo-btn controls,
   just re-homed here instead of a separate bar above the transcript. */
.ws-search-drawer { display: flex; align-items: center; gap: 6px; padding: 6px 9px; border-top: 1px solid var(--line); }
.ws-search-drawer[hidden] { display: none; }
.ws-send { padding: 8px 16px; }
/* "■ Stop" — interrupt the in-flight response. A distinct red so it reads as "halt", shown only
   while a turn is working (see paintPane). Sits next to Send in the compose row. */
.ws-stop { align-self: flex-end; padding: 8px 14px; border-radius: 10px; font-weight: 700; font-size: 13px; cursor: pointer;
  background: #f87171; border: 1px solid #f87171; color: #2a0a0a; white-space: nowrap; }
.ws-stop:hover { filter: brightness(1.05); }
.ws-stop[hidden] { display: none; }
/* Same acknowledgement treatment as Pact's — a pressed Stop reads as pressed. */
.ws-stop:disabled { opacity: .55; cursor: not-allowed; filter: none !important; }
/* Busy = Claude is mid-turn in this pane. The button itself becomes the loud indicator
   (orange), reverting to the normal accent color the instant the turn ends — a bigger,
   harder-to-miss signal than the small header spinner dot alone. */
.ws-send.busy { background: #f59e0b; border-color: #f59e0b; color: #1a1400; }
.ws-send.busy:hover { filter: brightness(1.05); }
/* Deep Work overrides the ordinary orange busy color with red: the visible turn already ended
   (this isn't "hang tight a moment") but Claude is still producing content in the background —
   see claudeSession.mjs's re-arm-from-idle comment and .ws-status.deepwork above. */
.ws-send.busy.deepwork { background: #f87171; border-color: #f87171; color: #2a0a0a; }
.ws-send.busy.deepwork:hover { filter: brightness(1.05); }
/* "Work is happening" pulse — a blinking ring around the Send button whenever ANY work is active:
   an ordinary/deep-work turn, OR agent-spawned background work (a workflow/task) that keeps running
   even while the chat is idle and the button still reads "Send". The ring blinks so it's noticeable
   at rest; a darker outline variant when the chat is free (background-only) vs the loud busy colors. */
/* Animates OPACITY on a pseudo-element, not box-shadow — see the long note on csWorkPulse in the
   chat-shell package. Measured there: the box-shadow version was 54% of all main-thread work with
   eight panes open, because box-shadow cannot be composited and repaints every frame. */
@keyframes wsWorkPulse { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
.ws-send.work-pulse, .pc-send.work-pulse, .ws-bgbadge { position: relative; }
.ws-send.work-pulse::after, .pc-send.work-pulse::after, .ws-bgbadge::after {
  content: ""; position: absolute; inset: -2px; border-radius: 999px; pointer-events: none;
  box-shadow: 0 0 0 2px rgba(148,163,184,.9); opacity: 0; will-change: opacity;
  animation: wsWorkPulse 1.15s ease-in-out infinite; }
.ws-send.work-pulse { /* ring drawn by ::after above */ }
@media (prefers-reduced-motion: reduce) { .ws-send.work-pulse::after, .pc-send.work-pulse::after { animation: none; opacity: .8; } }
/* "⚙ N background" pane-header badge — chat is free but hidden work runs; a calm slate chip that
   pulses gently, so background activity is discoverable, not only implied by the button ring. */
.ws-bgwork { font-size: 10.5px; color: #cbd5e1; border: 1px solid color-mix(in srgb, #94a3b8 55%, transparent);
  border-radius: 999px; padding: 1px 8px; white-space: nowrap; flex: 0 0 auto; position: relative; }
/* Its ring pulses, not the chip itself: wsWorkPulse animates OPACITY now, so animating the element
   would blink the whole badge out instead of breathing a ring around it. */
.ws-bgwork::after { content: ""; position: absolute; inset: -2px; border-radius: 999px; pointer-events: none;
  box-shadow: 0 0 0 2px rgba(148,163,184,.55); opacity: 0; will-change: opacity;
  animation: wsWorkPulse 1.6s ease-in-out infinite; }
.ws-bgwork[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .ws-bgwork::after { animation: none; opacity: .6; } }
/* Image attach: hidden native file input (triggered by the 📎 button, `multiple` so one dialog
   picks several at once), up to WS_IMG_MAX_COUNT thumbnail chips — each with its OWN remove (×)
   control, not one fixed control for a single image — and an inline error line for the "still too
   large after compression" / "too many images" cases — sits above the compose row so it doesn't
   reflow the textarea/send button. */
.ws-img-input { display: none; }
.ws-attach { font-size: 15px; align-self: flex-end; }
.ws-attach:disabled { opacity: .4; cursor: default; }
.ws-compose-extras:empty { display: none; }
/* Empty permission host must NOT sit in the flex flow — otherwise ws-root's gap adds a phantom band below
   the mode strip, so the Conversations/Bookmarks riser tabs never reach the footer nav (the Core "not flush"). */
.ws-perm-host:empty { display: none; }
.ws-img-preview { display: flex; flex-wrap: wrap; gap: 10px; padding: 0 9px; margin-top: 6px; }
.ws-img-chip { display: flex; align-items: center; gap: 4px; }
.ws-img-thumb { max-width: 64px; max-height: 64px; border-radius: 6px; border: 1px solid var(--line); object-fit: cover; }
.ws-img-x { background: transparent; border: 1px solid var(--line); color: var(--ink-dim); border-radius: 50%; cursor: pointer;
  width: 18px; height: 18px; line-height: 1; font-size: 12px; padding: 0; }
.ws-img-x:hover { color: var(--ink); border-color: var(--ink-dim); }
.ws-img-err { color: #f87171; font-size: 12px; padding: 0 9px; margin-top: 4px; }
.ws-img-err:empty { display: none; }

/* ---------- Admin → Deploy & Version ---------- */
.deploy-wrap { display: flex; flex-direction: column; gap: 12px; max-width: 1120px; }
.deploy-h { margin: 0; }

/* ---- Deploy admin: two tabs + terminator split ---- */
.deploy-tabbar { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin: 2px 0 4px; }
.deploy-tab { background: none; border: 0; border-bottom: 2px solid transparent; color: var(--ink-mute);
  font: 600 13px/1 inherit; padding: 9px 14px; cursor: pointer; border-radius: 8px 8px 0 0; }
.deploy-tab:hover { color: var(--ink); background: var(--panel); }
.deploy-tab.on { color: var(--ink); border-bottom-color: var(--accent); }
.deploy-tabpanel[hidden] { display: none; }
.deploy-pending-hd { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 16px; }
/* Reload (LEFT) + Deploy (RIGHT) as two symmetric columns. Each column is a SUBGRID that adopts the
   split's four rows (header · action card · restarts banner · progress checker), so the paired
   sections stay top-aligned across the columns — Reload's 2-step and Deploy's 4-step checkers still
   start at the same y — regardless of their content heights. */
.deploy-split { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: repeat(4, auto); gap: 14px; margin-top: 12px; }
.deploy-col { grid-row: 1 / -1; display: grid; grid-template-rows: subgrid; row-gap: 10px; min-width: 0;
  background: color-mix(in srgb, var(--panel) 55%, transparent);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 12px; }
.deploy-col-hd { font-size: 13px; font-weight: 700; color: var(--ink); letter-spacing: .02em; }
.deploy-cardbox { display: contents; }
.deploy-term-hd { font-size: 11px; color: var(--ink-mute); text-transform: uppercase; letter-spacing: .04em; margin-top: 2px; }
@media (max-width: 900px), (pointer: coarse) and (max-width: 1180px) {
  .deploy-split { grid-template-columns: 1fr; grid-template-rows: auto; }
  .deploy-col { grid-row: auto; display: flex; flex-direction: column; gap: 10px; }
}

/* One shared, collapsible terminal below the split (Explorer-style: collapsed by default). Single
   pane for one active/last stream; terminator-style two-pane split when reload AND deploy run at
   once (reload LEFT, deploy RIGHT). */
.deploy-shared-term { margin-top: 14px; border: 1px solid var(--line); border-radius: var(--radius);
  background: color-mix(in srgb, var(--panel) 55%, transparent); overflow: hidden; }
.deploy-term-toggle { display: flex; align-items: baseline; gap: 8px; width: 100%; text-align: left;
  background: none; border: 0; color: var(--ink); font: 600 13px/1.2 inherit; padding: 11px 14px; cursor: pointer; }
.deploy-term-toggle:hover { background: var(--panel); }
.deploy-term-caret { flex: none; width: 12px; color: var(--ink-mute); }
.deploy-term-live { margin-left: auto; font-size: 11px; color: var(--accent); font-family: ui-monospace, monospace; }
.deploy-term-body { padding: 0 12px 12px; }
.deploy-term-body[hidden] { display: none; }
.deploy-term-body.--split { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; align-items: start; }
.deploy-term-pane { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
@media (max-width: 900px), (pointer: coarse) and (max-width: 1180px) { .deploy-term-body.--split { grid-template-columns: 1fr; } }
.deploy-vers { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.deploy-card { flex: 1; min-width: 220px; background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 13px 15px; }
.deploy-card.ok { border-color: color-mix(in srgb, #34d399 45%, var(--line)); }
.deploy-card.stale { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.deploy-card.pending { border-color: color-mix(in srgb, var(--accent) 30%, var(--line)); }
.deploy-card-t { font-size: 11px; color: var(--ink-mute); text-transform: uppercase; letter-spacing: .04em; }
.deploy-ver { font: 700 24px/1.2 ui-monospace, monospace; color: var(--ink); margin-top: 3px; }
.deploy-sha { font-size: 11px; color: var(--ink-mute); font-family: ui-monospace, monospace; }
.deploy-arrow { font-size: 22px; color: var(--ink-mute); }
.deploy-actions-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.deploy-actions { display: flex; gap: 8px; }
/* "Pending" wraps/contains both targets — visually the one number either action converges on. */
.deploy-pending { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 16px; }
.deploy-ver-lg { font: 700 30px/1.2 ui-monospace, monospace; color: var(--ink); margin-top: 3px; }
.deploy-targets { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 14px; }
.deploy-targets .deploy-card { display: flex; flex-direction: column; gap: 6px; }
.deploy-stale-note { font-size: 11px; color: var(--accent); margin-top: 2px; }
.deploy-release { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; }
.deploy-release-row { display: flex; gap: 8px; align-items: flex-start; margin-top: 8px; }
.deploy-release-row .ws-prompt { flex: 1; }
.deploy-term { background: #05070f; color: #cdd6f4; border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 14px; font: 12px/1.5 ui-monospace, monospace; max-height: 340px; overflow: auto;
  white-space: pre-wrap; word-break: break-word; margin: 0; }

/* ---- Deploy: "what this deploy restarts" banner + live process list (deploy-survivable agents) ---- */
.deploy-restart-banner { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px;
  background: var(--panel); border-left-width: 3px; }
.deploy-restart-banner.--safe { border-left-color: #34d399; }
.deploy-restart-banner.--warn { border-left-color: #f59e0b;
  background: color-mix(in srgb, #f59e0b 8%, var(--panel)); }
.deploy-restart-hd { font-size: 12px; font-weight: 700; letter-spacing: .02em; }
.deploy-restart-banner.--safe .deploy-restart-hd { color: #34d399; }
.deploy-restart-banner.--warn .deploy-restart-hd { color: #f59e0b; }
.deploy-restart-txt { font-size: 13px; color: var(--ink); margin-top: 4px; }
.deploy-restart-meta { font-size: 11px; color: var(--ink-mute); margin-top: 6px; font-family: ui-monospace, monospace; }
.deploy-restart-busy { color: #f59e0b; font-weight: 700; }

.deploy-proc { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px;
  display: flex; flex-direction: column; gap: 2px; }
.deploy-proc-row { display: flex; align-items: center; gap: 10px; padding: 6px 2px; }
.deploy-proc-row + .deploy-proc-row { border-top: 1px solid color-mix(in srgb, var(--line) 60%, transparent); }
.deploy-proc-dot { flex: none; width: 16px; text-align: center; font-size: 13px; line-height: 1; }
.deploy-proc-dot.--running { color: #34d399; }
.deploy-proc-dot.--stopped { color: #f59e0b; }
.deploy-proc-dot.--not-installed { color: var(--ink-mute); }
.deploy-proc-dot.--unknown { color: var(--ink-mute); }
.deploy-proc-main { display: flex; flex-direction: column; min-width: 0; }
.deploy-proc-name { font-size: 13px; color: var(--ink); font-weight: 600; }
.deploy-proc-role { font-size: 11px; color: var(--ink-mute); }
.deploy-proc-detail { margin-left: auto; font-size: 11px; color: var(--ink-mute); font-family: ui-monospace, monospace;
  text-align: right; white-space: nowrap; }
.deploy-proc-more { align-self: flex-start; margin-top: 8px; background: var(--chip); border: 1px solid var(--line);
  color: var(--ink-dim); font: 600 12px/1 inherit; padding: 7px 11px; border-radius: 8px; cursor: pointer; }
.deploy-proc-more:hover { color: var(--ink); border-color: var(--accent); }
.deploy-proc-dormant { display: flex; flex-direction: column; gap: 0; margin-top: 4px; }
.deploy-proc-dormant[hidden] { display: none; }

/* ---- Deploy: StoaExplorer-style step-by-step progress (deploy-survivable agents W4) ---- */
.dp-progress { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; }
.dp-hd { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 8px; }
.dp-status { font-size: 13px; font-weight: 600; color: var(--ink); }
.dp-status.dp-running { color: var(--accent); }
.dp-status.dp-waiting { color: #f59e0b; }
@media (prefers-reduced-motion: no-preference) { .dp-status.dp-waiting { animation: dp-pulse 1.4s ease-in-out infinite; } }
@keyframes dp-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
.dp-status.dp-ok { color: #34d399; }
.dp-status.dp-failed { color: #f87171; }
.dp-elapsed { font-size: 11px; color: var(--ink-mute); font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; }
.dp-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.dp-step { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--ink); }
.dp-step.dp-pending { opacity: .45; }
.dp-step.dp-running { color: var(--accent); }
.dp-step.dp-failed { color: #f87171; }
.dp-mark { flex: none; width: 14px; text-align: center; }
.dp-step.dp-done .dp-mark { color: #34d399; }
.dp-label { min-width: 0; }
.dp-time { margin-left: auto; font-size: 11px; color: var(--ink-mute); font-family: ui-monospace, monospace; font-variant-numeric: tabular-nums; }
.deploy-logwrap { margin-top: 8px; }
.deploy-logwrap > summary { cursor: pointer; font-size: 12px; color: var(--ink-mute); padding: 4px 0; }
.deploy-logwrap[open] > summary { margin-bottom: 8px; }
.ws-databadge { font-size: 9.5px; color: var(--ink-mute); font-family: ui-monospace, monospace; white-space: nowrap; margin-left: auto; }
.ws-side-item .ws-databadge { margin-left: auto; }
.ws-orggroup-hd { cursor: pointer; user-select: none; }
.ws-org-chev { width: 12px; flex: 0 0 auto; font-size: 10px; color: var(--ink-mute); }
.ws-search { width: 100%; background: var(--chip); color: var(--ink); border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; font-size: 12.5px; }
.ws-search:focus { outline: none; border-color: var(--accent); }
.learn-panel { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; margin: 10px 0 16px; }
.learn-hd { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }
.learn-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.learn-usage { margin-top: 8px; font-size: 11.5px; color: var(--ink-mute); font-variant-numeric: tabular-nums; }
.mirror-list { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0; }
.mirror-bar { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: var(--panel); border: 1px solid var(--line); border-radius: 10px 10px 0 0; border-bottom: 0; }
.mirror-frame { width: 100%; height: 68vh; border: 1px solid var(--line); border-radius: 0 0 10px 10px; background: #fff; }

/* ---- LocalHost tab: the embedded aggregator ----
   Locally the body is a single iframe of the aggregator's own origin, so it needs room
   and a white backdrop (the aggregator paints its own background). Remotely the body is
   a plain table, so the same shell has to work for both. */
.lh { display: flex; flex-direction: column; gap: 10px; }
.lh-strip { display: flex; align-items: center; gap: 8px; padding: 8px 12px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px; }
.lh-frame { width: 100%; height: 74vh; border: 1px solid var(--line); border-radius: 10px; background: #fff; }
.lh-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; margin-right: 7px; flex: none; }
.lh-dot.--up { background: #34d399; }
.lh-dot.--down { background: var(--danger); }
.lh-bulk { display: flex; gap: 8px; margin-bottom: 10px; }
.lh-actions { white-space: nowrap; }
.lh-actions button { margin-right: 4px; }
.lh-empty { padding: 22px; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; }
.lh-empty h3 { margin: 0 0 8px; }
.lh-empty p { margin: 6px 0; color: var(--ink-soft); }
.lh-empty pre { background: var(--panel-2); padding: 10px 12px; border-radius: 8px; overflow-x: auto; }

/* ---- Workspace presence: which terminals are connected, and what each is viewing ---- */
.ws-presence { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 4px 2px; font-size: 11px; }
.ws-presence-lbl { color: var(--ink-mute); font-weight: 600; }
.ws-term { display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border: 1px solid var(--line); border-radius: 999px; background: var(--panel); }
.ws-term-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex: none; }
.ws-term-dot.--local { background: #34d399; }
.ws-term-dot.--relay { background: #60a5fa; }
.ws-term-on { color: var(--ink-mute); font-family: ui-monospace, monospace; }

/* Worktree selector — sits next to the repo picker; hidden until a repo is chosen. */
.wsel-wt { min-width: 0; flex: 0 1 auto; max-width: 130px; font-size: 11.5px; padding: 3px 4px; }
.wsel-wt[hidden] { display: none; }

/* ============================================================================
   MOBILE / PWA (Phase 1) — a coherent phone layout. Below this width the whole
   chrome compacts: header trims to essentials with swipeable nav, padding
   tightens, tap targets grow, and notch-safe insets are honored. The dedicated
   workspace tab layout + per-page deep polish come in later phases; here the
   goal is "installable and genuinely usable on a phone".
   ============================================================================ */
@media (max-width: 900px), (pointer: coarse) and (max-width: 1180px) {
  /* Notch-safe: pad the sticky header's top + the page's sides with safe-area insets. */
  .ph { padding-top: env(safe-area-inset-top, 0); }
  .ph-inner { padding: 0 max(12px, env(safe-area-inset-right)) 0 max(12px, env(safe-area-inset-left)); }
  .ph-l1 { height: 46px; gap: 7px; }
  .ph-name { font-size: 15px; }
  .ph-name span { max-width: 42vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Model/generation pills are informational — drop them on a phone to reclaim the row. */
  .model-pill { display: none; }
  .ph-identity { font-size: 11.5px; gap: 5px; }
  /* Keep a long email from overflowing the header: drop the "Signed in as" words + role badge,
     truncate the name, and shrink "Log out" to just the essentials. */
  .ph-id-prefix { display: none; }
  .ph-identity .ph-id-name { max-width: 34vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ph-identity .role-badge { display: none; }
  .ph-logout { padding: 4px 8px; }

  /* Tier-1 + tier-2 nav MOVE to the fixed bottom tab bar (.ph-tabbar, below) — reclaim these two
     top rows entirely instead of the old horizontally-scrolling text strip that cut off the last
     section and wasted vertical space. */
  .ph-l2, .ph-l3 { display: none; }

  /* Page body: tighter side padding, room for the notch + home-indicator. */
  main { padding: 12px max(12px, env(safe-area-inset-right)) calc(40px + env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left)); }
  .statbar { gap: 8px; margin-bottom: 12px; }
  /* Stat cards two-up (short numbers fit fine) so a long stat list isn't an endless scroll. */
  .stat { min-width: 0; flex: 1 1 calc(50% - 5px); padding: 10px 12px; }
  .stat .n { font-size: 20px; }
  .foot { padding: 10px 14px calc(10px + env(safe-area-inset-bottom)); font-size: 11px; }

  /* Card grids reflow to a single column so nothing is crushed. */
  .grid-orgs { grid-template-columns: 1fr !important; }

  /* Wide data tables scroll horizontally within their own box; sticky headers pin to the top
     (the desktop offset assumed a taller fixed header). */
  .matrix th { top: 0 !important; }
  .matrix td { min-width: 120px; }

  /* LocalHost aggregator strip: wrap its title + action buttons instead of clipping them. */
  .lh-strip { flex-wrap: wrap; }
  .lh-strip .ws-spacer { flex-basis: 100%; height: 0; }   /* force the actions onto their own row */
  .lh-frame { height: 68vh; }

  /* Workspace: the sidebar (repos/history) sits above the panes as a shallow, scrollable strip,
     and the panes stack full-width (the dedicated one-pane-at-a-time TAB layout lands in Phase 2).
     Bigger compose/send tap targets, and the controls bar wraps rather than overflowing. */
  .ws-toolbar { gap: 8px; }
  .ws-layout { display: none; }   /* the grid-size picker is meaningless on a phone — tabs replace it */
  /* 16px on every text field so a browser can't auto-zoom on focus (belt-and-suspenders with the
     viewport's user-scalable=no). */
  .ws-search, input[type="search"], input[type="text"] { font-size: 16px; }

  /* Compose row → WhatsApp-style. A full-width text box beside a VERTICAL column of round icon
     buttons (attach / stop / send). Previously all four sat in one horizontal row, so when Stop and
     Send both appeared the textarea was crushed to a sliver. Now the buttons stack in a narrow
     column and the text box owns the rest of the width — maximizing the typing area. */
  .ws-compose { align-items: stretch; gap: 8px; }
  .ws-prompt { flex: 1; min-height: 96px; font-size: 16px; }
  /* (The compose-collapse rules that sat here, and `.ws-mobile .ws-compose-btns/.ws-compose`, were
     keyed to `.ws-prompt` and `.ws-compose-btns` — Core class names the chat-shell migration replaced
     with the package's `.rg-typebox` and its own action row. They had matched nothing for months.) */
  .ws-compose-btns { flex-direction: column; justify-content: flex-end; gap: 8px; }
  .ws-compose-btns .ws-send,
  .ws-compose-btns .ws-stop,
  .ws-compose-btns .ws-attach {
    width: 46px; min-width: 46px; height: 46px; min-height: 46px; border-radius: 50%;
    padding: 0; margin: 0; align-self: center; flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center; }
  /* The Send button is wrapped in .ws-send-wrap (host for the Live/Held bulb) — carry the round-column
     centering to the wrapper so the flex item still centers, and let the 46px button inside fill it. */
  .ws-compose-btns .ws-send-wrap { align-self: center; flex: 0 0 auto; }
  /* Send + Stop carry dynamic text ("Working…", "■ Stop") — hide it and draw a single glyph so the
     round button stays clean regardless of state; the busy/deep-work background colors still show. */
  .ws-compose-btns .ws-send, .ws-compose-btns .ws-stop { font-size: 0; }
  .ws-compose-btns .ws-send::after { content: "➤"; font-size: 19px; line-height: 1; }
  .ws-compose-btns .ws-stop::after { content: "■"; font-size: 15px; line-height: 1; }
  .ws-compose-btns .ws-attach { font-size: 21px; line-height: 1; }
  /* At this narrow width the compose row is the WhatsApp-style round-icon column (above) — repo/
     worktree/lines moved here from the model row for desktop parity, but a round-icon column has no
     room for two selects and a text chip. History stays (it's a single compact icon, same footprint
     as attach/expand, which already shared this column). */
  .ws-compose-btns select, .ws-compose-btns .ws-lines-chip { display: none; }
  .wsel, .wsel-sm { font-size: 12.5px; }   /* keep selects legible + tappable */

  /* Stage-1 Core-mobile overhaul (Pact model): the per-pane compose buttons move to a global bottom
     control bar, so the textarea owns the full width — matching the Pact mobile chat. */
  /* Conversations/Bookmarks as RISER TABS glued to the footer nav's top edge (Ouronet "Controls [N]" style):
     "💬 Conversations [n]" bottom-LEFT, "★ Bookmarks" bottom-RIGHT, flanking the centered Live/Held bulb.
     align-items:flex-end + zero bottom padding drops the tabs onto the fixed footer bar below. */
  .ws-mobile .ws-mmodebar { display: flex; justify-content: space-between; align-items: flex-end; gap: 8px;
    padding: 3px max(6px, env(safe-area-inset-left)) 0 max(6px, env(safe-area-inset-right)); flex: 0 0 auto; }
  .ws-mbtn { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 4px; height: 19px; padding: 0 8px;
    border-radius: 7px 7px 0 0; border: 1px solid color-mix(in srgb, var(--accent) 48%, var(--line)); border-bottom: none;
    background: color-mix(in srgb, var(--accent) 13%, var(--panel)); color: var(--accent);
    font-size: 9.5px; font-weight: 700; cursor: pointer; white-space: nowrap; -webkit-tap-highlight-color: transparent; }
  .ws-mbtn:active { background: color-mix(in srgb, var(--accent) 24%, var(--panel)); }
  .ws-mbtn-n { font-size: 9.5px; font-weight: 800; color: var(--accent); }
  .ws-mmodebar .stick-mode { font-size: 10.5px; padding: 2px 10px 2px 8px; }
  .ws-mobile .ws-head-bulb { display: inline-flex; }   /* Live/Held bulb lives in the pane header on mobile */
  .ws-mbtn-bm { color: #fbbf24; border-color: color-mix(in srgb, #fbbf24 45%, var(--line)); background: color-mix(in srgb, #fbbf24 11%, var(--panel)); }
  /* No safe-area-inset-bottom here: body.ws-full already reserves the fixed bottom nav's height, so the
     bar sits directly above .ph-tabbar (which itself covers the home-indicator inset). */
  .ws-mobile .ws-mcbar { display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
    padding: 6px max(10px, env(safe-area-inset-right)) 6px max(10px, env(safe-area-inset-left));
    border-top: 1px solid var(--line); background: var(--panel); }
  .ws-mcbtn { min-width: 42px; min-height: 42px; padding: 0 10px; display: inline-flex; align-items: center; justify-content: center;
    background: var(--chip); border: 1px solid var(--line); color: var(--ink); border-radius: 11px; font-size: 18px; cursor: pointer; }
  .ws-mcbtn:active { filter: brightness(1.15); }
  .ws-mcbtn-send { background: var(--accent); border-color: var(--accent); color: #fff; min-width: 58px; font-size: 20px; }
  .ws-mcbtn-send.busy { background: #f59e0b; border-color: #f59e0b; color: #1a1400; }
  .ws-mcbtn-stop { background: #f87171; border-color: #f87171; color: #2a0a0a; }
  /* The bottom bar and its strip live at the very bottom of the flex column — kill ws-root's gap there
     so they hug the compose above without a floating band. And let the active pane fit the remaining
     flex space (the old min-height:calc(100vh-210px) predates the bottom bar and would push it off). */
  .ws-mobile.ws-root { gap: 8px; }
  .ws-mobile .ws-pane { min-height: 0; }

  /* Stage 2: the top tab strip and the desktop toolbar are RECLAIMED — the pane switcher moves to the
     bar's 💬 sheet, and per-pane controls move to the ⚙ sheet. The active pane now owns nearly the
     whole screen. (renderMobileTabs still runs, harmlessly, into the hidden strip.) */
  .ws-mobile .ws-toolbar { display: none; }
  /* Per-pane controls live in the ⚙ sheet on mobile — hidden while sitting in the pane. */
  /* Stage 3: New folder / New repo, relocated into the ☰ drawer on mobile (their toolbar home is hidden). */
  .ws-mobile .ws-drawer-actions { display: flex; gap: 8px; margin-bottom: 10px; }
  .ws-mobile .ws-drawer-actions .ghost { flex: 1; min-height: 42px; border-radius: 10px; }
  /* Bar tap targets never trigger a double-tap zoom. */
  .ws-mcbtn { touch-action: manipulation; }

  /* PHASE 2 — one chat box at a time via a tab strip; the sidebar becomes a slide-in drawer. */

  /* Only the active chat box is shown; the rest are just tabs. It fills most of the screen.
     CRITICAL: collapse the grid to ONE row too. The desktop template keeps `--ws-rows` explicit
     tracks (e.g. 2 for a 1×2 layout); in ws-full mode those tracks split the fixed grid height
     evenly, so the single visible pane got only 1/N of the screen and its compose box + controls
     overflowed past `.ws-pane { overflow:hidden }` and vanished — the pane was unusable. One track
     = the visible pane owns the full height. */
  .ws-grid { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr); }
  .ws-grid > .ws-pane:not(.on) { display: none; }
  .ws-pane { min-height: calc(100vh - 210px); }
  /* min-height:0 lets the transcript SHRINK when the compose box grows (the base rule pins it at
     220px). Without this, a multi-line message inside the fixed-height pane pushed the compose row
     — and the Send button at its bottom — past the pane's clipped edge and off-screen. Now the
     transcript yields its space so Send always stays in view. */
  .ws-transcript { max-height: none; flex: 1; min-height: 0; }
  .stick-wrap-ws { max-height: none; flex: 1; min-height: 0; }
  /* Keep the compose row (and its Send button) from ever being clipped: it holds its natural height
     and the transcript above it absorbs the shrink. */
  .ws-compose { flex: 0 0 auto; }

  /* The repos/history sidebar as an off-canvas drawer, opened by the ☰ tab-strip button. */
  .ws-body { position: relative; }
  .ws-side { position: fixed; top: 0; left: 0; bottom: 0; width: 86vw; max-width: 340px; z-index: 60; margin: 0;
    padding: max(12px, env(safe-area-inset-top)) 12px 12px max(12px, env(safe-area-inset-left));
    overflow-y: auto; -webkit-overflow-scrolling: touch; background: var(--panel2); border-right: 1px solid var(--line);
    transform: translateX(-102%); transition: transform .22s ease; }
  .ws-drawer-open .ws-side { transform: translateX(0); }
  .ws-side-backdrop { display: block; position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,.5);
    opacity: 0; pointer-events: none; transition: opacity .2s ease; }
  .ws-drawer-open .ws-side-backdrop { opacity: 1; pointer-events: auto; }
  .ws-drawer-close { display: block; width: 100%; margin-bottom: 8px; padding: 9px; background: var(--chip);
    border: 1px solid var(--line); color: var(--ink); border-radius: 9px; font-size: 13px; cursor: pointer; }

  /* ===== Mobile bottom navigation (tier-1 tab bar + tier-2 drawer) ===== */
  body { --cm-tabbar-h: calc(47px + env(safe-area-inset-bottom)); }   /* fallback; JS measures the real bar and overrides (measureTabbar) */
  /* Reserve room so scrolling pages (and the footer) clear the fixed bar. The full-height Workspace
     instead shrinks by the bar height so its compose row sits just above it, never under it. */
  body:not(.ws-full) { padding-bottom: var(--cm-tabbar-h); }
  /* min-height:0 is REQUIRED here: the base `body { min-height: 100% }` (full viewport) otherwise
     wins over this reduced height, so the workspace never actually shrank and the fixed bar clipped
     the compose row + Send button. Pinning both height and min-height makes the workspace end exactly
     above the bar. */
  body.ws-full { height: calc(100vh - var(--cm-tabbar-h)); height: calc(100dvh - var(--cm-tabbar-h)); min-height: 0; }
  /* Kill #main's bottom padding on the cockpit views so the workspace column reaches the footer nav's top
     edge — that's what lets the Conversations/Bookmarks riser tabs sit DIRECTLY ON the footer bar. */
  body.ws-full #main { padding-bottom: 0; }

  .ph-tabbar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
    background: color-mix(in srgb, var(--panel) 94%, transparent);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom); }
  .ph-tab {
    flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 3px;
    padding: 7px 2px 6px; background: transparent; border: none; color: var(--ink-dim); cursor: pointer;
    position: relative; -webkit-tap-highlight-color: transparent; }
  .ph-tab.--active, .ph-tab.--open { color: var(--accent); }
  /* Admin is a separate space, not a content section — a hairline sets it apart at the end of the bar. */
  .ph-tab-admin { border-left: 1px solid var(--line); }
  .ph-tab.--active::before { content: ""; position: absolute; top: 0; left: 24%; right: 24%; height: 2px;
    border-radius: 999px; background: var(--accent); }
  .ph-tab-ic { font-size: 19px; line-height: 1; }
  .ph-tab-lbl { font-size: 9px; font-weight: 600; letter-spacing: -.2px; max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  .ph-tabbar-backdrop { display: none; }
  .ph-tabbar-backdrop.--shown { display: block; position: fixed; inset: 0; z-index: 79; background: transparent; }

  .ph-tabdrawer { display: none; }
  .ph-tabdrawer.--shown { display: block; position: fixed; left: 0; right: 0; bottom: var(--cm-tabbar-h);
    z-index: 81; padding: 6px 8px 8px; animation: cmDrawerUp .16s ease; }
  .ph-tabdrawer-grid { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 4px;
    background: color-mix(in srgb, var(--panel) 97%, transparent); backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px); border: 1px solid var(--line); border-radius: 12px; padding: 6px;
    box-shadow: 0 -10px 26px rgba(0,0,0,.4); }
  .ph-tabsub { display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0;
    padding: 7px 2px; background: transparent; border: none; border-radius: 8px; color: var(--ink-dim);
    cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .ph-tabsub.--active { color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .ph-tabsub-lbl { font-size: 9px; font-weight: 600; text-align: center; line-height: 1.15; }
  @keyframes cmDrawerUp { from { transform: translateY(8px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
}
@media (prefers-reduced-motion: reduce) { .ws-side { transition: none; } .ws-side-backdrop { transition: none; } .ph-tabdrawer.--shown { animation: none; } }

/* Bottom nav exists only for the phone layout. Hidden on desktop via a width-scoped rule (NOT a
   plain rule, which — sitting after the mobile block with equal specificity — would win and hide it
   on mobile too). Same pattern the drawer's own desktop rule uses below. */
@media (min-width: 901px) and (pointer: fine), (min-width: 1181px) { .ph-tabbar, .ph-tabdrawer, .ph-tabbar-backdrop { display: none !important; } }

/* Hidden on desktop — these only exist for the phone layout above. Scoped to desktop width (not a
   plain rule) so it can't override the mobile block, which sits earlier and has equal specificity. */
@media (min-width: 901px) and (pointer: fine), (min-width: 1181px) { .ws-side-backdrop, .ws-drawer-close { display: none; } }

/* Only the tiniest phones go one-stat-per-row; normal phones keep the two-up grid above. */
@media (max-width: 330px) { .stat { flex-basis: 100%; } }

/* ===== Pact IDE (Workspace › Pact) — 3-zone full-height layout =====
   tree (fixed left) | editor 75% | right column 25% (chat top / terminal bottom). Runs inside
   body.ws-full, so #view already has a definite height and the zones scroll internally. */
.pact-ide { display: flex; gap: 10px; height: 100%; min-height: 0; }
.pact-tree { flex: 0 0 clamp(144px, 13%, 272px); width: auto; min-width: 0; display: flex; flex-direction: column; --pk-tree-font: 12.5px;
  background: var(--panel2); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
.pact-tree-hd { padding: 8px 10px; font-weight: 700; font-size: 12px; border-bottom: 1px solid var(--line); background: var(--panel); }
.pact-tree-body { flex: 1; min-height: 0; overflow: auto; padding: 6px 4px; }
.pact-node-wrap { min-width: 0; }
.pact-node { display: flex; align-items: center; gap: 5px; padding: 3px 6px; border-radius: 6px; cursor: pointer; font-size: var(--pk-tree-font, 12.5px);
  font-size: 12.5px; white-space: nowrap; }
.pact-node:hover { background: var(--chip); }
.pact-file.--active { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--ink); }
/* Reveal-in-tree (IDE auto-reveal): the row synced to the active editor file. A persistent left accent
   marks "this is the file open in the active box"; a brief flash draws the eye when it (re)reveals. */
.pact-node.pact-file.--revealed { box-shadow: inset 2px 0 0 var(--accent); }
.pact-node.pact-file.--reveal-flash { animation: pact-reveal-flash 900ms ease-out 1; }
@keyframes pact-reveal-flash { 0% { background: color-mix(in srgb, var(--accent) 40%, transparent); } 100% { background: transparent; } }
@media (prefers-reduced-motion: reduce) { .pact-node.pact-file.--reveal-flash { animation: none; } }
.pact-chev { width: 12px; flex: 0 0 12px; text-align: center; font-size: 10px; color: var(--ink-dim); }
.pact-node-ic { flex: 0 0 auto; font-size: 12px; }
.pact-node-name { overflow: hidden; text-overflow: ellipsis; }
.pact-node { position: relative; }
/* Long filename: reveal it fully on hover (a robust 'peek'; JS marquee is a later refinement). */
.pact-node:hover .pact-node-name { overflow: visible; text-overflow: clip; white-space: nowrap; }
.pact-node-kids { margin-left: 12px; }
/* VSCode-Explorer-style git coloring: tint a changed file's name + show a compact letter badge.
   modified → amber "M", new/untracked → green "U". Complements (does not replace) the scrollbar ruler. */
.pact-node--mod .pact-node-name { color: #e2b04a; }
.pact-node--new .pact-node-name { color: #6cc36c; }
.pact-node-git { flex: 0 0 auto; margin-left: auto; padding: 0 4px; border-radius: 4px; font-size: 9.5px; font-weight: 800;
  line-height: 1.5; letter-spacing: .02em; }
.pact-node-git--mod { color: #e2b04a; background: color-mix(in srgb, #e2b04a 16%, transparent); }
.pact-node-git--new { color: #6cc36c; background: color-mix(in srgb, #6cc36c 16%, transparent); }
/* Subtle "has changes below" hint on an ancestor directory row (VSCode dims the folder name). */
.pact-node--dirty-dir .pact-node-name { color: color-mix(in srgb, #e2b04a 62%, var(--ink)); }

.pact-work { flex: 1; min-width: 0; display: flex; gap: 10px; }
/* Zone A = a Save-All toolbar over rows of editor boxes (up to 8), stacked per the split ladder
   (pactEdLayout). editor:right = 4:1 → the chat+repl zone is ⅕ of the space after the tree.
   Draggable gutters resize boxes/rows. */
/* 3:1, not 4:1 — the chat column was a fifth of the work area, which is narrower than the chat shell
   wants on any real desktop ("I need the width of the Pact workspace to be a bit bigger"). The share
   only decides the DEFAULT now; `.pact-wgrip` between the two lets you set it exactly, and the choice
   is remembered (PACT_CHAT_W_KEY). */
.pact-editor-wrap { flex: 3 1 0; min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: 6px; }
/* The resize grip. Deliberately a thin visible bar rather than an invisible hot zone: a control you
   cannot see is one nobody discovers. It widens its hit area with a padded ::before so it is easy to
   grab without making the gap between the columns any bigger. */
.pact-wgrip { flex: 0 0 6px; align-self: stretch; position: relative; cursor: col-resize;
  border-radius: 3px; background: var(--line); transition: background .12s; touch-action: none; }
.pact-wgrip::before { content: ""; position: absolute; inset: 0 -5px; }
.pact-wgrip::after { content: ""; position: absolute; left: 2px; top: 50%; width: 2px; height: 26px;
  margin-top: -13px; border-radius: 1px; background: var(--ink-mute); opacity: .5; }
.pact-wgrip:hover, .pact-wgrip.--drag { background: var(--acc); }
.pact-wgrip:hover::after, .pact-wgrip.--drag::after { opacity: .9; }
/* While dragging, nothing else may take the pointer or start a text selection. */
body.pact-wgrip-dragging { cursor: col-resize; user-select: none; }
body.pact-wgrip-dragging .pact-work * { pointer-events: none; }
body.pact-wgrip-dragging .pact-wgrip { pointer-events: auto; }
.pact-ed-toolbar { flex: 0 0 auto; display: flex; flex-direction: row; align-items: center; gap: 10px; }
/* The one shared band legend sits INLINE on the toolbar row — drop its per-box divider/padding to read as
   a strip, and let it flex + scroll horizontally so it stays on one line even when the row gets tight. */
.pact-ed-toolbar .pact-legend { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; overflow-x: auto; border-bottom: 0; padding: 2px 0; }
.pact-save-all { flex: 0 0 auto; padding: 5px 14px; border-radius: 8px; font-size: 12.5px; font-weight: 700; cursor: pointer;
  border: 1px solid var(--line); background: color-mix(in srgb, var(--accent) 22%, var(--panel)); color: var(--ink); }
.pact-save-all:disabled { cursor: default; opacity: .5; background: var(--chip); color: var(--ink-dim); }
.pact-save-all:not(:disabled):hover { background: color-mix(in srgb, var(--accent) 34%, var(--panel)); }
.pact-keep-all { flex: 0 0 auto; padding: 5px 14px; border-radius: 8px; font-size: 12.5px; font-weight: 700; cursor: pointer;
  border: 1px solid color-mix(in srgb, #34d399 55%, var(--line)); background: color-mix(in srgb, #34d399 20%, var(--panel)); color: var(--ink); }
.pact-keep-all:hover { background: color-mix(in srgb, #34d399 32%, var(--panel)); }
.pact-save-status { font-size: 11.5px; color: var(--ink-mute); }
.pact-save-status.--err { color: #f87171; }
.pact-save-hint { font-size: 11px; color: var(--ink-dim); }
/* Tree column tabs: "Files" (the tree) and "Changed (N)" (the agent's changed files). The header row
   holds both tabs plus the A-/A+ font controls; the active tab gets an accent underline. */
.pact-tree-tabs { display: flex; align-items: center; gap: 4px; padding: 4px 6px; }
.pact-tree-tab { flex: 0 0 auto; padding: 4px 8px; border-radius: 7px; border: 1px solid transparent; background: transparent;
  color: var(--ink-dim); font-size: 12px; font-weight: 700; cursor: pointer; white-space: nowrap; }
.pact-tree-tab:hover { background: var(--chip); color: var(--ink); }
.pact-tree-tab.--active { color: var(--ink); background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
/* A non-active "Changed" tab with a change waiting gets a subtle accent tint (never grabs focus). */
.pact-tree-tab.--has:not(.--active) { color: var(--ink); }
.pact-tree-tab.--has:not(.--active)::after { content: ""; display: inline-block; width: 6px; height: 6px; margin-left: 5px;
  border-radius: 50%; vertical-align: middle; background: var(--accent); }

/* The "Changed" tab's body: it shares the tree column, uses its full height and scrolls. */
.pact-changed-list { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 3px; padding: 6px 6px; }
/* Acknowledge footer — per-worktree agent-change diffstat pinned at the bottom of the tree column. */
.pact-ack-footer { flex: 0 0 auto; border-top: 1px solid var(--line); background: color-mix(in srgb, var(--accent) 6%, var(--panel)); padding: 5px 6px; display: flex; flex-direction: column; gap: 4px; max-height: 34%; overflow: auto; }
.pact-ack-hd { font-size: 10.5px; font-weight: 700; letter-spacing: .02em; color: var(--ink-mute); text-transform: uppercase; padding: 0 2px 1px; }
.pact-ack-row { display: flex; align-items: center; gap: 6px; font-size: 11.5px; }
.pact-ack-wt { font-weight: 700; color: var(--ink); white-space: nowrap; }
.pact-ack-nums { display: inline-flex; gap: 5px; font: 600 11px ui-monospace, monospace; white-space: nowrap; }
.pact-ack-add { color: #34d399; }
.pact-ack-del { color: #f87171; }
.pact-ack-files { color: var(--ink-mute); }
.pact-ack-btn { flex: 0 0 auto; padding: 2px 9px; border-radius: 6px; border: 1px solid var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); font-weight: 700; font-size: 11px; cursor: pointer; }
.pact-ack-btn:hover { background: color-mix(in srgb, var(--accent) 30%, transparent); filter: brightness(1.08); }
.pcs-head { display: flex; align-items: center; gap: 8px; padding: 2px 4px 4px; }
.pcs-title { font-size: 11.5px; font-weight: 700; color: var(--ink-mute); }
.pcs-list { display: flex; flex-direction: column; gap: 3px; }
.pcs-row { display: flex; flex-direction: column; align-items: stretch; gap: 1px; width: 100%; text-align: left; cursor: pointer;
  padding: 4px 6px; border-radius: 7px; border: 1px solid transparent; background: transparent; color: var(--ink); }
.pcs-row:hover { background: var(--chip); border-color: var(--line); }
.pcs-main { display: flex; align-items: center; gap: 7px; min-width: 0; }
.pcs-st { flex: 0 0 auto; width: 30px; text-align: center; font: 700 10px ui-monospace, monospace; border-radius: 4px; padding: 1px 0; }
.pcs-st-m { color: #fbbf24; background: rgba(251, 191, 36, .16); }
.pcs-st-a, .pcs-st-new { color: #6ee7b7; background: rgba(52, 211, 153, .16); }
.pcs-st-d { color: #fca5a5; background: rgba(239, 83, 80, .16); }
/* Basename prominent; directory dimmed below. Both plain LTR (no bidi) so path digits render in order. */
.pcs-name { flex: 1 1 auto; min-width: 0; font: 12px ui-monospace, monospace; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; direction: ltr; unicode-bidi: plaintext; }
.pcs-dir { font: 11px ui-monospace, monospace; color: var(--ink-dim); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; direction: ltr; unicode-bidi: plaintext; padding-left: 37px; }
.pcs-badges { flex: 0 0 auto; display: flex; gap: 4px; }
.pact-editor { flex: 1 1 0; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.pact-ed-row { display: flex; min-width: 0; min-height: 0; }
.pact-ed-gutter-x { flex: 0 0 8px; align-self: stretch; cursor: col-resize; position: relative; }
.pact-ed-gutter-y { flex: 0 0 8px; cursor: row-resize; position: relative; }
.pact-ed-gutter-x::after { content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 2px; transform: translateX(-50%);
  background: var(--line); border-radius: 2px; transition: background .12s; }
.pact-ed-gutter-y::after { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 2px; transform: translateY(-50%);
  background: var(--line); border-radius: 2px; transition: background .12s; }
.pact-ed-gutter-x:hover::after, .pact-ed-gutter-y:hover::after { background: var(--accent); }
/* `contain: layout paint` fences each editor box's (heavy — a CodeMirror instance) internal layout off
   from the rest of the page — the SAME isolation the Core cockpit's .ws-pane gets. Without it, a forced
   reflow anywhere on the Pact page (notably the chat compose box's autosize `height:auto`→`scrollHeight`
   read on every keystroke) re-lays-out EVERY open editor grid box, which is why typing in the Pact chat
   hung while the Core chat stayed smooth. `paint` (not `size`/`strict`) — the flex parent still sizes the
   box, so CodeMirror measures a real viewport; only its internal layout+paint is isolated. */
.pact-ed-group { display: flex; flex-direction: column; min-width: 0; min-height: 0; position: relative;
  background: var(--panel2); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; contain: layout paint; }
.pact-ed-group.--active { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); }
.pact-ed-hd { display: flex; align-items: center; gap: 6px; padding: 4px 6px; min-height: 34px;
  border-bottom: 1px solid var(--line); background: var(--panel); }
.pact-tabs2 { display: flex; flex-wrap: wrap; gap: 3px; min-width: 0; }
.pact-tabs2::-webkit-scrollbar { display: none; }
.pact-tab2 { display: inline-flex; align-items: center; gap: 5px; padding: 4px 4px 4px 8px; border-radius: 7px;
  background: var(--chip); color: var(--ink-dim); font-size: 11.5px; cursor: pointer; white-space: nowrap; flex: 0 0 auto; }
.pact-tab2.--active { color: var(--ink); background: color-mix(in srgb, var(--accent) 16%, transparent); }
/* Color-code tabs by file type — know at a glance what you have open (left accent + name tint). */
.pact-tab2 { border-left: 3px solid transparent; }
.pact-tab2.pk-t-pact { border-left-color: #ceac5f; }   .pact-tab2.pk-t-pact .pact-tab2-name { color: #d7ba7d; }
.pact-tab2.pk-t-repl { border-left-color: #34d399; }   .pact-tab2.pk-t-repl .pact-tab2-name { color: #6ee7b7; }
.pact-tab2.pk-t-md   { border-left-color: #4fb6e0; }   .pact-tab2.pk-t-md   .pact-tab2-name { color: #7fc9ea; }
.pact-tab2.pk-t-json { border-left-color: #e5c07b; }   .pact-tab2.pk-t-json .pact-tab2-name { color: #e5c07b; }
.pact-tab2.pk-t-yaml, .pact-tab2.pk-t-yml { border-left-color: #e5a663; }
.pact-tab2.pk-t-txt  { border-left-color: #94a3b8; }
.pact-tab2.--active.pk-t-pact .pact-tab2-name,
.pact-tab2.--active.pk-t-repl .pact-tab2-name,
.pact-tab2.--active.pk-t-md .pact-tab2-name { color: var(--ink); }
.pact-tab2-dot { display: none; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: 0 0 auto; }
.pact-tab2.--dirty .pact-tab2-dot { display: inline-block; }
.pact-tab2-name { overflow: hidden; text-overflow: ellipsis; max-width: 140px; }
.pact-tab2-x { color: var(--ink-mute); font-size: 14px; line-height: 1; padding: 0 3px; border-radius: 4px; }
.pact-tab2-x:hover { color: var(--ink); background: var(--line); }
/* v1.4.6 — Chrome-style tab drag (reorder in a box / move between boxes). */
.pact-tab2 { cursor: grab; }
.pact-tab2.--dragging { opacity: .45; }
.pact-tab2.--drop-before { box-shadow: inset 3px 0 0 var(--accent); }   /* insertion marker to the left */
.pact-ed-hd.--dnd-over { background: color-mix(in srgb, var(--accent) 12%, var(--panel)); outline: 1px dashed color-mix(in srgb, var(--accent) 50%, transparent); outline-offset: -2px; }
/* v1.4.8 — desktop right-click tab menu (clone/move to a box, font size, find). */
.pact-ctx-menu, .pact-ctx-sub { position: fixed; z-index: 1000; min-width: 168px; padding: 4px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 9px; box-shadow: 0 10px 30px rgba(0,0,0,.5); }
.pact-ctx-sub { position: absolute; left: 100%; top: -5px; display: none; }
.pact-ctx-menu.--left .pact-ctx-sub { left: auto; right: 100%; }
.pact-ctx-item { position: relative; display: flex; align-items: center; gap: 8px; padding: 6px 9px;
  font-size: 12.5px; color: var(--ink-soft); border-radius: 6px; cursor: pointer; white-space: nowrap; }
.pact-ctx-item:hover { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--ink); }
.pact-ctx-item.--has-sub:hover > .pact-ctx-sub { display: block; }
.pact-ctx-item.--disabled { color: var(--ink-mute); cursor: default; }
.pact-ctx-item.--disabled:hover { background: transparent; color: var(--ink-mute); }
.pact-ctx-label { flex: 1 1 auto; }
.pact-ctx-arrow { flex: 0 0 auto; color: var(--ink-mute); font-size: 11px; }
.pact-ctx-sep { height: 1px; margin: 4px 6px; background: var(--line); }
.pact-ed-ico { flex: 0 0 auto; padding: 2px 8px; border-radius: 6px; background: var(--chip);
  border: 1px solid var(--line); color: var(--ink-dim); font-size: 13px; cursor: pointer; }
.pact-ed-ico:hover { color: var(--ink); }
.pact-ed-ico.--on { color: var(--bg); background: var(--accent); border-color: var(--accent); }
/* SNAP-TO-DEFAULT buttons. Disabled means "already at default" — the layout is tidy and there is
   nothing for the click to do — so they dim rather than disappear: a control that vanishes when it
   is not needed can't tell you the layout IS default, which is half of what these are for. */
.pact-snap { white-space: nowrap; }
.pact-snap:disabled { opacity: .38; cursor: default; }
.pact-snap:not(:disabled) { color: #8fc3ff; border-color: #5aa8f055; }
.pact-snap:not(:disabled):hover { color: #d8e9ff; border-color: #5aa8f0; }
/* Per-box worktree selector (Stage-1 worktree binding) — ties a box to a checkout; only shown when a
   worktree beyond main exists. A non-main binding is accented so an isolated box is obvious at a glance. */
.pact-wt-sel { flex: 0 0 auto; font-size: 11px; max-width: 130px; background: var(--chip); color: var(--ink);
  border: 1px solid var(--line); border-radius: 6px; padding: 2px 4px; cursor: pointer; }
.pact-wt-sel.--active { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); color: var(--accent); }
/* The chat-head twin: binds THIS conversation's agent to a worktree (accented when non-main, dimmed+locked
   once the conversation has started). */
.pc-wt.--active { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); color: var(--accent); }
.pc-wt:disabled { opacity: .7; cursor: not-allowed; }
/* Tree-header chip: which worktree the file tree is currently showing (the active box's); hidden on main. */
.pact-tree-wt { font-size: 10px; color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-radius: 4px; padding: 1px 5px; white-space: nowrap; align-self: center; }
/* History-row worktree badge — which checkout a saved conversation ran in. */
.pc-hist-wt { font-size: 9.5px; color: var(--accent); background: color-mix(in srgb, var(--accent) 13%, transparent);
  border-radius: 4px; padding: 0 4px; margin-left: 6px; white-space: nowrap; }
/* Worktree-migration separator: a labeled dashed line marking where the conversation moved checkouts.
   Amber-accent for a move INTO a worktree, green for a return/merge to main. */
.pc-migration { display: flex; align-items: center; gap: 8px; margin: 12px 2px; }
.pc-migration::before, .pc-migration::after { content: ""; flex: 1; border-top: 1px dashed color-mix(in srgb, var(--accent) 50%, var(--line)); }
.pc-migration-lbl { font-size: 11px; font-weight: 700; color: var(--accent); white-space: nowrap;
  background: color-mix(in srgb, var(--accent) 12%, transparent); border-radius: 999px; padding: 2px 12px; }
.pc-migration.--tomain::before, .pc-migration.--tomain::after { border-color: color-mix(in srgb, #34d399 45%, var(--line)); }
.pc-migration.--tomain .pc-migration-lbl { color: #34d399; background: color-mix(in srgb, #34d399 12%, transparent); }
/* The head migrate button (shown for a started conversation) — accented when on a non-main checkout. */
.pc-migrate.--active { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); }
/* Always-visible worktree pill in the compose bar (lower-left): the conversation's current checkout +
   a caret; click opens the bind/migrate/merge menu. Shows "⌂ main" by default so it's always discoverable. */
.pc-wtpill { flex: 0 0 auto; align-self: flex-end; display: inline-flex; align-items: center; gap: 3px;
  height: 32px; padding: 0 9px; font-size: 11px; font-weight: 600; color: var(--ink-dim); cursor: pointer;
  background: var(--chip); border: 1px solid var(--line); border-radius: 9px; white-space: nowrap; }
.pc-wtpill:hover { color: var(--ink); border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
.pc-wtpill.--active { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
  background: color-mix(in srgb, var(--accent) 10%, var(--chip)); }
.pc-wtpill-caret { font-size: 8px; opacity: .65; }
/* Per-tab worktree marker + a left accent so a conversation bound to a non-main checkout stands out. */
.pc-tab.--wt { border-left: 2px solid color-mix(in srgb, var(--accent) 65%, transparent); }
.pc-tab-wt { font-size: 9.5px; color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-radius: 4px; padding: 0 3px; max-width: 60px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pact-ed-body { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
/* v1.4.5 — per-box control strip at the bottom (font/split/close/find/replace), so the tab row stays
   full-width for file names instead of the buttons crushing them. */
.pact-ed-foot { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; padding: 3px 6px;
  border-top: 1px solid var(--line); background: var(--panel); }
.pact-ed-foot .pact-run-btn { padding: 2px 8px; }
/* Per-box "✓ Keep (N)" — accept the agent's edits to THIS box only (tied to its worktree). Green so it
   stands out from the neutral footer icons; only shown when the box has unaccepted agent edits. */
.pact-ed-keep { flex: 0 0 auto; padding: 2px 10px; border-radius: 6px; font-weight: 700; font-size: 11.5px;
  background: #22c55e; color: #052e13; border: 1px solid #16a34a; cursor: pointer; white-space: nowrap; }
.pact-ed-keep:hover { filter: brightness(1.08); }
/* Font-size stepper: ◀ <px> ▶ as one pill, the number showing this box's exact size. (v1.4.9) */
.pact-font-step { flex: 0 0 auto; display: inline-flex; align-items: center; border: 1px solid var(--line);
  border-radius: 6px; background: var(--chip); overflow: hidden; }
.pact-font-arrow { border: none; background: transparent; color: var(--ink-dim); cursor: pointer;
  padding: 2px 7px; font-size: 10px; line-height: 1; }
.pact-font-arrow:hover { color: var(--ink); background: var(--line); }
.pact-font-num { min-width: 20px; padding: 0 3px; text-align: center; font-size: 11.5px; color: var(--ink);
  font-variant-numeric: tabular-nums; border-left: 1px solid var(--line); border-right: 1px solid var(--line); }
/* Floating Find/Replace panel — anchored to THIS box (bottom-right, above the footer), driving this box's
   active file's CodeMirror. */
.pact-ed-search { position: absolute; right: 8px; bottom: 40px; z-index: 20; display: flex; flex-direction: column; gap: 5px;
  padding: 8px; min-width: 260px; max-width: calc(100% - 16px);
  background: var(--panel); border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-radius: 10px; box-shadow: 0 8px 26px rgba(0,0,0,.45); }
.pact-search-row { display: flex; align-items: center; gap: 5px; }
.pact-search-in { flex: 1 1 auto; min-width: 0; padding: 5px 8px; font-size: 12.5px; color: var(--ink);
  background: var(--chip); border: 1px solid var(--line); border-radius: 7px; }
.pact-search-in:focus { outline: none; border-color: var(--accent); }
.pact-search-count { flex: 0 0 auto; font-size: 10.5px; color: var(--ink-mute); white-space: nowrap; min-width: 62px; text-align: right; }
.pact-search-hint { font-size: 10px; color: var(--ink-mute); padding: 0 2px; }
.cm-pact-search-match { background: rgba(245,197,66,.32); border-radius: 2px; }
/* v1.4.13 — agent-diff view rendered in a READ-ONLY CodeMirror (new file): added lines green, deleted
   lines shown as red inline markers, +/− in a sign gutter. */
.pact-diff-cm .CodeMirror-linebackground.pd-add-line { background: rgba(52, 211, 153, .15); }
.pact-diff-cm .CodeMirror-gutter.pact-diff-sign { width: 15px; }
.pact-diff-cm .pd-gsign { display: inline-block; width: 15px; text-align: center; font-weight: 700; }
.pact-diff-cm .pd-gadd { color: #34d399; }
.pact-diff-cm .pd-gdel { color: #ef5350; }
.pact-diff-del-widget { background: rgba(239, 83, 80, .13); }
.pact-diff-del-line { display: flex; align-items: baseline; gap: 4px; color: #e6a9a9; white-space: pre;
  padding-left: 2px; }
.pact-diff-del-line .pd-gsign { flex: 0 0 auto; color: #ef5350; }
.pact-diff-del-line .pd-text { flex: 1 1 auto; opacity: .92; }
.pact-editor-empty { padding: 16px; }
.pact-editor-scroll { flex: 1; min-height: 0; overflow: auto; }
.pact-code { margin: 0; padding: 12px 14px; font: 12.5px/1.55 ui-monospace, "SF Mono", Menlo, monospace; white-space: pre; tab-size: 2; color: #d2d3d4; }

/* Agent-edit diff view (read-only until Keep All): Cursor-style green added / red removed lines. */
.pact-diff-hd { display: flex; align-items: center; gap: 6px; padding: 5px 12px; border-bottom: 1px solid var(--line); background: var(--panel); }
.pd-badge { font: 700 11px ui-monospace, monospace; padding: 1px 7px; border-radius: 6px; }
.pd-badge-add { color: #6ee7b7; background: rgba(52, 211, 153, .18); }
.pd-badge-del { color: #fca5a5; background: rgba(239, 83, 80, .18); }
/* inline-block view + block rows sized to content (white-space:pre) so a row's background spans the
   FULL line width — the whole line tints, not just the part that fits the viewport when scrolled. */
.pact-diff-view { margin: 0; padding: 8px 0; font: 12.5px/1.55 ui-monospace, "SF Mono", Menlo, monospace; tab-size: 2; color: #d2d3d4; display: inline-block; min-width: 100%; box-sizing: border-box; }
/* Overview ruler on the diff view's scroll container: green(add)/red(del) bands so a long diff shows
   change density at a glance (the editable view gets CodeMirror's own scrollbar ruler). */
.pact-diff-scrollwrap { position: relative; flex: 1; min-height: 0; display: flex; }
.pact-diff-scrollwrap > .pact-editor-scroll { flex: 1; min-width: 0; }
.pact-diff-scrollwrap > .pact-cm-host { flex: 1; min-width: 0; }
/* Proportional overview ruler over the diff's scrollbar: bands sized by the number of added/removed rows
   across the whole diff, positioned where they occur. Semi-transparent + a thin band at the right edge so
   the scroll thumb stays visible beside them. */
.pact-diff-ovr { position: absolute; top: 0; right: 0; bottom: 0; width: 16px; pointer-events: none; z-index: 6; }
.pact-diff-ovr > div { position: absolute; right: 0; width: 6px; min-height: 2px; border-radius: 2px; }
.pact-diff-ovr-add { background: rgba(52, 211, 153, .60); }
.pact-diff-ovr-del { background: rgba(239, 83, 80, .65); }
.pact-diff-row { display: block; white-space: pre; }
.pact-diff-row .pd-lineno { display: inline-block; min-width: var(--pk-lineno-w, 2ch); text-align: right; padding-right: 8px;
  color: var(--ink-dim); user-select: none; -webkit-user-select: none;
  position: sticky; left: 0; z-index: 2; background: var(--panel); }   /* stay fixed on horizontal scroll; code passes behind */
.pact-diff-row .pd-sign { display: inline-block; width: 24px; text-align: center; color: var(--ink-dim); user-select: none; }
.pact-diff-row .pd-text { padding-right: 12px; }
.pd-add { background: rgba(52, 211, 153, .16); }
.pd-add .pd-sign { color: #34d399; }
.pd-del { background: rgba(239, 83, 80, .15); }
.pd-del .pd-sign { color: #ef5350; }
.pd-del .pd-text { color: #c99; }


/* StoicSyntax token colors (VS Code Dark+ base; StoicSyntax prefix BANDS carry the meaning —
   unprotected reads/compute cool, protected state-changers warm/red). */
.pact-code .pk-comment { color: #6a9955; font-style: italic; }
.pact-code .pk-section { color: #c8a15a; font-weight: 600; }
.pact-code .pk-string  { color: #ce9178; }
.pact-code .pk-number  { color: #b5cea8; }
.pact-code .pk-bool    { color: #569cd6; }
.pact-code .pk-keyword { color: #6fd6c6; }                     /* native functions — lighter teal */
.pact-code .pk-def     { color: #1a8f80; font-weight: 700; }   /* def family — dark teal */
.pact-code .pk-type    { color: #4ec9b0; }
.pact-code .pk-paren   { color: #ffd700; }
.pact-code .pk-sq      { color: #da70d6; }
.pact-code .pk-brace   { color: #179fff; }
.pact-code .pk-op      { color: #56b6c2; }
/* StoicSyntax COLOUR FAMILIES — coloured by prefix per OuronetInformational/StoicSyntax-Prefixes.md §4.
   Ten families; `…x` auxiliaries share their family hue, dimmed + italic ("helper of the thing above").
   HEAVY-READ is deliberately the LOUDEST read colour (amber) so a scan on a hot path jumps out. The
   family classifier lives in pact-cm-mode.js (wraps window.pactClassifyWord). */
/* The 9 StoicSyntax bands — palette from the stoicsyntax-pact package (README/configurationDefaults), so the
   web viewer matches VS Code/Cursor and the explorer. Cool = unprotected, warm/red = protected. */
.pact-code .pk-compute  { color: #4ec9b0; }                       /* COMPUTE — UC/UCk (teal) */
.pact-code .pk-computex { color: #4ec9b0; opacity: .68; font-style: italic; }
.pact-code .pk-read     { color: #4fb6e0; }                       /* READ — UR/URC/URD/URDC (cyan) */
.pact-code .pk-readx    { color: #4fb6e0; opacity: .68; font-style: italic; }
.pact-code .pk-ctor     { color: #dcdcaa; }                       /* CONSTRUCT — UDC (yellow) */
.pact-code .pk-ctorx    { color: #dcdcaa; opacity: .68; font-style: italic; }
.pact-code .pk-enforce  { color: #e5c07b; }                       /* ENFORCE — UEV (amber) */
.pact-code .pk-cap      { color: #e6c200; font-weight: 700; }     /* CAP — CAP_ (gold, bold) */
.pact-code .pk-client   { color: #98c379; }                       /* CLIENT — C_ (green) */
.pact-code .pk-orch     { color: #e5a663; }                       /* ORCH — XI/XE/XB (orange) */
.pact-code .pk-admin    { color: #e06c75; font-weight: 700; }     /* ADMIN — A_ (salmon, bold) */
.pact-code .pk-write    { color: #ef5350; font-weight: 700; }     /* WRITE — WI/WU/WW/W (red, bold) */
/* Extra families still emitted by the editable CodeMirror mode (pact-cm-mode.js) for URH/CT/GOV etc.,
   which the package's 9-band set doesn't split out — kept so the editor doesn't lose them. */
.pact-code .pk-heavy    { color: #ff9d3c; font-weight: 700; }
.pact-code .pk-heavyx   { color: #ff9d3c; opacity: .72; font-style: italic; }
.pact-code .pk-const    { color: #8b98a5; }
.pact-code .pk-recipe   { color: #98c379; font-weight: 700; }
.pact-code .pk-struct   { color: #6f7b8a; }
.pact-code .pk-readd    { color: #4fb6e0; }

/* Band legend — a compact strip between the editor header and the code, teaching the color language. */
.pact-legend { display: flex; flex-wrap: wrap; gap: 4px 12px; padding: 6px 12px; border-bottom: 1px solid var(--line);
  background: var(--panel); font-size: 10.5px; }
.pact-legend-item { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
.pact-legend-item code { font: 700 11px ui-monospace, monospace; }
.pact-legend-desc { color: var(--ink-mute); }
/* Reuse the family colors on the legend's <code> tags (new 10-family palette). */
.pact-legend .pk-compute { color: #4ec9b0; } .pact-legend .pk-read { color: #4fb6e0; }
.pact-legend .pk-ctor { color: #dcdcaa; } .pact-legend .pk-enforce { color: #e5c07b; }
.pact-legend .pk-cap { color: #e6c200; font-weight: 700; } .pact-legend .pk-client { color: #98c379; }
.pact-legend .pk-orch { color: #e5a663; } .pact-legend .pk-admin { color: #e06c75; font-weight: 700; }
.pact-legend .pk-write { color: #ef5350; font-weight: 700; }

/* ===== CodeMirror 5 — the editable Pact editor surface (replaces the textarea overlay). The host fills
   the box; the CM instance fills the host. Dark theme matched to the app; per-box font-size is applied
   inline on the .CodeMirror wrapper (JS), so only font-family + line-height live here. ===== */
.pact-cm-host { flex: 1; min-height: 0; overflow: hidden; }
.pact-cm-host .CodeMirror {
  height: 100%; background: transparent; color: #d2d3d4;
  font-family: ui-monospace, "SF Mono", Menlo, monospace; line-height: 1.55; tab-size: 2;
}
.pact-cm-host .CodeMirror-scroll { min-height: 0; }
.pact-cm-host .CodeMirror-gutters { background: var(--panel); border-right: 1px solid color-mix(in srgb, var(--line) 60%, transparent); }
.pact-cm-host .CodeMirror-linenumber { color: var(--ink-dim); }
.pact-cm-host .CodeMirror-cursor { border-left: 1px solid #e6edf3; }
.pact-cm-host .CodeMirror-selected { background: rgba(120, 160, 255, .22); }
.pact-cm-host .CodeMirror-focused .CodeMirror-selected { background: rgba(120, 160, 255, .32); }
.pact-cm-host .CodeMirror-line::selection, .pact-cm-host .CodeMirror-line > span::selection { background: rgba(120, 160, 255, .32); }
.pact-cm-host .CodeMirror-activeline-background { background: rgba(255, 255, 255, .035); }
.pact-cm-host .CodeMirror-matchingbracket { color: #ffd700 !important; font-weight: 700; border-bottom: 1px solid #ffd700; }
.pact-cm-host .CodeMirror-nonmatchingbracket { color: #ef5350 !important; }
/* Inline fold gutter (foldgutter addon): ▾/▸ arrows + the collapsed "…" marker, dark-themed. */
.pact-cm-host .CodeMirror-foldgutter { width: 14px; }
.pact-cm-host .CodeMirror-foldgutter-open, .pact-cm-host .CodeMirror-foldgutter-folded { color: var(--ink-dim); cursor: pointer; }
.pact-cm-host .CodeMirror-foldgutter-open:hover, .pact-cm-host .CodeMirror-foldgutter-folded:hover { color: var(--ink); }
.pact-cm-host .CodeMirror-foldmarker { color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, transparent); border-radius: 4px; padding: 0 5px; margin: 0 2px; font-family: ui-monospace, monospace; cursor: pointer; }
/* CodeMirror find/replace dialog (search + dialog addons) — dark theme. */
.pact-cm-host .CodeMirror-dialog { background: var(--panel); color: var(--ink); border-bottom: 1px solid var(--line); font: 12px ui-monospace, "SF Mono", Menlo, monospace; }
.pact-cm-host .CodeMirror-dialog input { background: var(--chip); border: 1px solid var(--line); border-radius: 5px; color: var(--ink); font: 12px ui-monospace, monospace; padding: 2px 6px; outline: none; }
.pact-cm-host .CodeMirror-dialog input:focus { border-color: var(--accent); }
/* Scrollbar match annotations (matchesonscrollbar addon) — reuse the find-highlight amber. */
.pact-cm-host .CodeMirror-search-match { background: rgba(250, 214, 90, .38); border-top: 1px solid rgba(250, 214, 90, .7); border-bottom: 1px solid rgba(250, 214, 90, .7); box-sizing: border-box; }

/* Change ruler (annotatescrollbar addon): git-diff decorations painted on the native scrollbar — the
   CM-native successor to the old custom .pact-ovr strip. Green added / red removed / amber modified vs
   git HEAD, reusing the exact .pact-code hex bands. */
/* SEMI-TRANSPARENT so the scrollbar knob shows THROUGH/beside the stripes — a fully-changed file used to
   paint a solid opaque column that swallowed the knob and made the bar unusable. add=green, del=red,
   mod=amber (vs git HEAD). */
.pact-cm-host .cm-change-add { background: rgba(52, 211, 153, .55); }
.pact-cm-host .cm-change-del { background: rgba(239, 83, 80, .60); }
.pact-cm-host .cm-change-mod { background: rgba(251, 191, 36, .50); }
/* A thin band pinned at the scrollbar's right edge (KEEP the addon's right:0), narrow enough that the
   knob is always visible to its left, transparent enough that it shows through where they overlap. */
.pact-cm-host .cm-change-add, .pact-cm-host .cm-change-del, .pact-cm-host .cm-change-mod {
  width: 6px !important; border-radius: 2px; }
/* Wider vertical scrollbar + a bigger, rounder, clearly-visible knob (requested). */
.pact-cm-host .CodeMirror-vscrollbar { width: 16px !important; }
.pact-cm-host .CodeMirror-vscrollbar::-webkit-scrollbar { width: 16px; }
.pact-cm-host .CodeMirror-vscrollbar::-webkit-scrollbar-track { background: transparent; }
.pact-cm-host .CodeMirror-vscrollbar::-webkit-scrollbar-thumb {
  background: rgba(210, 214, 224, .42); border-radius: 8px; min-height: 48px;
  border: 3px solid transparent; background-clip: padding-box; }
.pact-cm-host .CodeMirror-vscrollbar::-webkit-scrollbar-thumb:hover { background: rgba(210, 214, 224, .62); }

/* StoicSyntax token colors for CodeMirror (it prefixes mode styles with `cm-`, so `pk-write` → `cm-pk-write`).
   SAME hex values as `.pact-code .pk-*` above — the editable view must look identical to the read-only one. */
.CodeMirror .cm-pk-comment { color: #6a9955; font-style: italic; }
.CodeMirror .cm-pk-section { color: #c8a15a; font-weight: 600; }
.CodeMirror .cm-pk-string  { color: #ce9178; }
.CodeMirror .cm-pk-number  { color: #b5cea8; }
.CodeMirror .cm-pk-bool    { color: #569cd6; }
.CodeMirror .cm-pk-keyword { color: #6fd6c6; }
.CodeMirror .cm-pk-def     { color: #1a8f80; font-weight: 700; }
.CodeMirror .cm-pk-type    { color: #4ec9b0; }
.CodeMirror .cm-pk-paren   { color: #ffd700; }
.CodeMirror .cm-pk-sq      { color: #da70d6; }
.CodeMirror .cm-pk-brace   { color: #179fff; }
.CodeMirror .cm-pk-op      { color: #56b6c2; }
/* StoicSyntax COLOUR FAMILIES for the editable/diff CM — MUST stay in lockstep with the package-aligned
   `.pact-code .pk-*` bands above (same hex). The editable view has to look identical to the read-only one. */
.CodeMirror .cm-pk-compute  { color: #4ec9b0; }
.CodeMirror .cm-pk-computex { color: #4ec9b0; opacity: .68; font-style: italic; }
.CodeMirror .cm-pk-read     { color: #4fb6e0; }
.CodeMirror .cm-pk-readx    { color: #4fb6e0; opacity: .68; font-style: italic; }
.CodeMirror .cm-pk-heavy    { color: #ff9d3c; font-weight: 700; }
.CodeMirror .cm-pk-heavyx   { color: #ff9d3c; opacity: .72; font-style: italic; }
.CodeMirror .cm-pk-ctor     { color: #dcdcaa; }
.CodeMirror .cm-pk-ctorx    { color: #dcdcaa; opacity: .68; font-style: italic; }
.CodeMirror .cm-pk-enforce  { color: #e5c07b; }
.CodeMirror .cm-pk-cap      { color: #e6c200; font-weight: 700; }
.CodeMirror .cm-pk-const    { color: #8b98a5; }
.CodeMirror .cm-pk-recipe   { color: #98c379; font-weight: 700; }
.CodeMirror .cm-pk-client   { color: #98c379; }
.CodeMirror .cm-pk-orch     { color: #e5a663; }
.CodeMirror .cm-pk-admin    { color: #e06c75; font-weight: 700; }
.CodeMirror .cm-pk-write    { color: #ef5350; font-weight: 700; }
.CodeMirror .cm-pk-struct   { color: #6f7b8a; }
/* Legacy name (package classifier output on deleted diff lines) → mirror the read band. */
.CodeMirror .cm-pk-readd    { color: #4fb6e0; }

/* ===== StoicSyntax MEDALLIONS (pact-medallion.js) — read-only <pre> viewer + editable CM twin ===== */
/* Per-editor-box "Read-only ⇄ Edit" ticker (Pact/Repl only). */
.pact-ed-editck { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 7px; font-size: 11px; font-weight: 700;
  cursor: pointer; background: var(--chip); border: 1px solid var(--line); color: var(--ink-dim); white-space: nowrap; }
.pact-ed-editck:hover { color: var(--ink); }
.pact-ed-editck.--on { background: color-mix(in srgb, #2b6cff 22%, transparent); border-color: #2b6cff; color: #cfe0ff; }

/* Idle "suggested next prompt" chip in the Pact chat (feature A). */
.pc-suggest { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 6px 10px; margin: 0 8px 6px;
  border: 1px solid var(--line); border-radius: 9px; background: color-mix(in srgb, #2b6cff 8%, var(--panel2)); font-size: 12px; }
.pc-suggest[hidden] { display: none; }
.pc-suggest-lbl { color: var(--ink-dim); } .pc-suggest-lbl b { color: var(--ink); font-weight: 700; }
.pc-suggest-btn { padding: 3px 10px; border-radius: 7px; font-size: 11.5px; font-weight: 700; cursor: pointer; background: var(--chip); border: 1px solid var(--line); color: var(--ink); }
.pc-suggest-btn:hover { filter: brightness(1.08); }
.pc-suggest-send { background: #2b6cff; border-color: #2b6cff; color: #fff; }
.pc-suggest-auto { display: inline-flex; align-items: center; gap: 4px; color: var(--ink-dim); cursor: pointer; user-select: none; font-weight: 600; }
.pc-suggest-auto.--on { color: #cfe0ff; }
.pc-suggest-count { color: #9ab7ff; font-variant-numeric: tabular-nums; }
.pc-suggest-x { margin-left: auto; background: transparent; border: 0; color: var(--ink-dim); cursor: pointer; font-size: 13px; line-height: 1; }
.pc-suggest-x:hover { color: var(--ink); }
/* mid-round: Auto-continue is running — highlighted bar with an always-clickable off toggle */
.pc-suggest.--running { background: color-mix(in srgb, #2b6cff 14%, var(--panel2)); border-color: #2b6cff; }
.pc-suggest.--running .pc-suggest-lbl b { color: #cfe0ff; }
.pc-suggest-stopauto { margin-left: auto; background: #7a2230; border-color: #a83549; color: #ffd9df; }
.pc-suggest-stopauto:hover { filter: brightness(1.1); }
/* MOBILE: the Auto/suggest bar shares ONE row with the worktree pill (pc-toolrow) to save a vertical row. */
.pc-toolrow { display: flex; align-items: center; gap: 8px; margin: 0 8px 6px; min-width: 0; }
.pc-toolrow .pc-suggest { flex: 1 1 auto; min-width: 0; margin: 0; padding: 5px 8px; gap: 6px; flex-wrap: nowrap; overflow: hidden; }
.pc-toolrow .pc-suggest > * { flex: 0 0 auto; }
.pc-toolrow .pc-suggest-auto { min-width: 0; }
.pc-toolrow .pc-suggest-sub { display: none; }
.pc-toolrow .pc-suggest-stopauto { margin-left: 0; }
.pc-toolrow .pc-wtpill { flex: 0 0 auto; align-self: center; }

/* Read-only medallion view (full fidelity: angled caps, padded medallions, per-type, foreign-black). */
.pact-medallion-scroll { height: 100%; min-height: 0; overflow: auto; background: #0d1226; }
.pact-medallion-pre { margin: 0; padding: 10px 14px 10px 0; tab-size: 2; font: 11px/2.05 ui-monospace, "SF Mono", Menlo, monospace; color: #c8d0e6; background: transparent; }
/* Two columns: a fixed number gutter + a WRAPPING code column (view-mode word-wrap — no horizontal scroll). */
.pact-medallion-pre .pml-row { display: flex; align-items: baseline; }
.pact-medallion-pre .pml-code { flex: 1 1 auto; min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
/* line-number gutter — fixed width (all numbers align), sticky-left, always present so blank lines keep height */
.pact-medallion-pre .pml-ln { flex: 0 0 auto; box-sizing: content-box; width: var(--pml-lnw, 3ch); padding: 0 10px; margin-right: 12px; text-align: right;
  color: #5a6675; font-weight: 400; user-select: none; position: sticky; left: 0; background: #0d1226; border-right: 1px solid #1b2440; }
/* a source line that WRAPPED onto >1 visual line — subtle wash + a left accent on the code so it's clearly one line */
.pact-medallion-pre .pml-row.--wrapped { background: rgba(120, 160, 255, .05); }
.pact-medallion-pre .pml-row.--wrapped .pml-code { box-shadow: inset 2px 0 0 rgba(120, 160, 255, .45); }
/* CLICK-TO-COPY: the line number of a module/interface opener copies that whole block. Marked so you
   can find the two numbers that do something among a thousand that don't — and only the NUMBER, so
   selecting code by hand still works everywhere else. */
.pact-medallion-pre .pml-ln.--copyblk { color: #8fc3ff; font-weight: 700; cursor: pointer; }
.pact-medallion-pre .pml-ln.--copyblk:hover { color: #d8e9ff; background: #17224a; }
/* Confirmation shows the EXTENT, not just a tick: the block's own rows wash for a moment, so you can
   see the module ended where you expected without pasting it somewhere to find out. */
.pact-medallion-pre .pml-row.--copied { background: rgba(110, 231, 168, .13); }
.pact-medallion-pre .pml-row.--copied .pml-ln { background: #10301f; color: #6ee7a8; }
.pact-medallion-pre .pml-row.--copyfail { background: rgba(248, 113, 113, .13); }
.pact-medallion-pre .pml-row.--copyfail .pml-ln { background: #3a1418; color: #f87171; }
/* read-only search: the matched line, scrolled into view + tinted */
.pact-medallion-pre .pml-row.--rohit { background: rgba(250, 214, 90, .16); }
.pact-medallion-pre .pml-row.--rohit .pml-ln { background: #2c2a17; }
.pact-medallion-pre .cmt { color: #647085; font-style: italic; } .pact-medallion-pre .bi { color: #66cfc1; } .pact-medallion-pre .bib { color: #66cfc1; font-weight: 700; }
.pact-medallion-pre .ty { color: #c2c8d2; } .pact-medallion-pre .struct { color: #737b8c; } .pact-medallion-pre .structb { color: #737b8c; font-weight: 700; }
.pact-medallion-pre .bk0 { color: #e5616e; } .pact-medallion-pre .bk1 { color: #e0b64a; } .pact-medallion-pre .bk2 { color: #5aa8f0; }
.pact-medallion-pre .form { display: inline-block; margin: 0 3px; padding: 0 8px; line-height: 1.05; font-weight: 700; border-radius: 8px; background: #332a0e; border: 2px solid #f3c81b; color: #f6d24a; }
.pact-medallion-pre .ctor { color: #f3c81b; font-weight: 700; } .pact-medallion-pre .ctorx { color: #e4c136; font-style: italic; }
.pact-medallion-pre .compute { color: #3181e9; font-weight: 700; } .pact-medallion-pre .ck { color: #4d90e8; } .pact-medallion-pre .cx { color: #9dbeea; font-style: italic; }
.pact-medallion-pre .rl { color: #e8b683; } .pact-medallion-pre .rx { color: #e8b683; font-style: italic; } .pact-medallion-pre .heavy { color: #ec8013; font-weight: 700; } .pact-medallion-pre .cost { color: #a36633; }
.pact-medallion-pre .val { color: #be274a; font-weight: 700; } .pact-medallion-pre .cap { color: #be274a; }
.pact-medallion-pre .ww { color: #e61990; font-weight: 700; } .pact-medallion-pre .wu { color: #e46db2; } .pact-medallion-pre .wi { color: #e89fca; }
.pact-medallion-pre .xi { color: #a045d5; font-weight: 700; } .pact-medallion-pre .xe { color: #ab5fd7; } .pact-medallion-pre .xb { color: #b577da; }
.pact-medallion-pre .adm { color: #1d9a4d; font-weight: 700; } .pact-medallion-pre .cli { color: #9adfb1; } .pact-medallion-pre .const { color: #9298a4; }
.pact-medallion-pre .tag { display: inline-block; margin: 0 3px; padding: 0 7px; line-height: 1.05; font-weight: 700; border-radius: 8px; background: #33132a; border: 2px solid #e61990; color: #f28cc4; }
.pact-medallion-pre .tagO { display: inline-block; margin: 0 3px; padding: 0 7px; line-height: 1.05; font-weight: 700; border-radius: 8px; background: #33220e; border: 2px solid #ec8013; color: #f0b06a; }
.pact-medallion-pre .strBlk { background: #33291a; color: #e8b06a; -webkit-box-decoration-break: clone; box-decoration-break: clone; padding: 0.42em 6px; border-radius: 6px; }
.pact-medallion-pre .capmed { display: inline-block; margin: 0 3px; padding: 2px; line-height: 1; clip-path: polygon(7px 0, calc(100% - 7px) 0, 100% 50%, calc(100% - 7px) 100%, 7px 100%, 0 50%); }
.pact-medallion-pre .capmedi { display: inline-block; padding: 0 9px; line-height: 1.05; font-weight: 700; clip-path: polygon(7px 0, calc(100% - 7px) 0, 100% 50%, calc(100% - 7px) 100%, 7px 100%, 0 50%); }
.pact-medallion-pre .capBo { background: #c9925e; } .pact-medallion-pre .capBi { background: #241a10; color: #e6b985; }
.pact-medallion-pre .capSo { background: #b7c0cf; } .pact-medallion-pre .capSi { background: #181c24; color: #d8dee8; }
.pact-medallion-pre .capGo { background: #dcb64d; } .pact-medallion-pre .capGi { background: #23200e; color: #ecca63; }
.pact-medallion-pre .capKo { background: #565b66; } .pact-medallion-pre .capKi { background: #191b22; color: #cbd0da; }
.pact-medallion-pre .fnK { display: inline-block; margin: 0 3px; padding: 0 6px; line-height: 1.05; font-weight: 700; border: 2px solid #565b66; border-radius: 6px; background: #191b22; color: #cbd0da; }

/* Editable twin — CodeMirror mode "stoicmedallion" emits cm-md-*; pills gated behind .CodeMirror.mdl. Base = coloured text. */
.CodeMirror .cm-md-cmt { color: #647085; font-style: italic; } .CodeMirror .cm-md-section { color: #8a93a8; font-weight: 600; } .CodeMirror .cm-md-op { color: #8892ad; }
.CodeMirror .cm-md-bi { color: #66cfc1; } .CodeMirror .cm-md-bib { color: #66cfc1; font-weight: 700; } .CodeMirror .cm-md-ty { color: #c2c8d2; }
.CodeMirror .cm-md-struct { color: #737b8c; } .CodeMirror .cm-md-structb { color: #737b8c; font-weight: 700; }
.CodeMirror .cm-md-bk0 { color: #e5616e; } .CodeMirror .cm-md-bk1 { color: #e0b64a; } .CodeMirror .cm-md-bk2 { color: #5aa8f0; }
.CodeMirror .cm-md-ctor { color: #f3c81b; font-weight: 700; } .CodeMirror .cm-md-ctorx { color: #e4c136; font-style: italic; } .CodeMirror .cm-md-compute { color: #3181e9; font-weight: 700; } .CodeMirror .cm-md-ck { color: #4d90e8; } .CodeMirror .cm-md-cx { color: #9dbeea; font-style: italic; }
.CodeMirror .cm-md-rl { color: #e8b683; } .CodeMirror .cm-md-rx { color: #e8b683; font-style: italic; } .CodeMirror .cm-md-heavy { color: #ec8013; font-weight: 700; } .CodeMirror .cm-md-cost { color: #a36633; }
.CodeMirror .cm-md-val { color: #be274a; font-weight: 700; } .CodeMirror .cm-md-cap { color: #be274a; } .CodeMirror .cm-md-ww { color: #e61990; font-weight: 700; } .CodeMirror .cm-md-wu { color: #e46db2; } .CodeMirror .cm-md-wi { color: #e89fca; }
.CodeMirror .cm-md-xi { color: #a045d5; font-weight: 700; } .CodeMirror .cm-md-xe { color: #ab5fd7; } .CodeMirror .cm-md-xb { color: #b577da; } .CodeMirror .cm-md-adm { color: #1d9a4d; font-weight: 700; } .CodeMirror .cm-md-cli { color: #9adfb1; } .CodeMirror .cm-md-const { color: #9298a4; }
.CodeMirror .cm-md-num-int { color: #7db0f2; } .CodeMirror .cm-md-num-dec { color: #8ab6f0; } .CodeMirror .cm-md-bool { color: #e58ba0; } .CodeMirror .cm-md-strv { color: #e8b06a; } .CodeMirror .cm-md-strblk { color: #e8b06a; } .CodeMirror .cm-md-ref { color: #aeb9c9; } .CodeMirror .cm-md-schema { color: #f6d24a; }
.CodeMirror .cm-md-form { color: #f6d24a; font-weight: 700; } .CodeMirror .cm-md-tag { color: #f28cc4; font-weight: 700; } .CodeMirror .cm-md-tagO { color: #f0b06a; font-weight: 700; }
.CodeMirror .cm-md-capB { color: #e6b985; font-weight: 700; } .CodeMirror .cm-md-capS { color: #d8dee8; font-weight: 700; } .CodeMirror .cm-md-capG { color: #ecca63; font-weight: 700; } .CodeMirror .cm-md-capK { color: #cbd0da; font-weight: 700; } .CodeMirror .cm-md-fnK { color: #cbd0da; font-weight: 700; }
.CodeMirror .cm-md-ty-integer { color: #3181e9; } .CodeMirror .cm-md-ty-decimal { color: #4d90e8; } .CodeMirror .cm-md-ty-string { color: #ec8013; } .CodeMirror .cm-md-ty-bool { color: #be274a; } .CodeMirror .cm-md-ty-time { color: #1d9a4d; } .CodeMirror .cm-md-ty-guard { color: #a045d5; } .CodeMirror .cm-md-ty-keyset { color: #b577da; } .CodeMirror .cm-md-ty-object { color: #f3c81b; } .CodeMirror .cm-md-ty-list { color: #3fbfae; } .CodeMirror .cm-md-ty-table { color: #a36633; } .CodeMirror .cm-md-ty-module { color: #8fa3bd; } .CodeMirror .cm-md-ty-value { color: #9298a4; }
/* Medallion mode ON: caret-safe pills — background + inset box-shadow (border) + radius. NO padding/border/inline-block. */
.CodeMirror.mdl .cm-md-num-int { background: color-mix(in srgb, #3181e9 20%, #0b1020); color: color-mix(in srgb, #3181e9 62%, #fff); border-radius: 6px; font-weight: 700; }
.CodeMirror.mdl .cm-md-num-dec { background: color-mix(in srgb, #4d90e8 20%, #0b1020); color: color-mix(in srgb, #4d90e8 62%, #fff); border-radius: 6px; font-weight: 700; }
.CodeMirror.mdl .cm-md-bool { background: color-mix(in srgb, #be274a 26%, #0b1020); color: color-mix(in srgb, #be274a 58%, #fff); border-radius: 6px; font-weight: 700; }
.CodeMirror.mdl .cm-md-strv { background: #33291a; color: #e8b06a; border-radius: 6px; } .CodeMirror.mdl .cm-md-strblk { background: #33291a; color: #e8b06a; }
.CodeMirror.mdl .cm-md-ref { background: color-mix(in srgb, #8fa3bd 22%, #0b1020); color: color-mix(in srgb, #8fa3bd 60%, #fff); border-radius: 6px; font-weight: 700; }
.CodeMirror.mdl .cm-md-schema { background: color-mix(in srgb, #f3c81b 18%, #0b1020); color: #f6d24a; border-radius: 6px; font-weight: 700; }
.CodeMirror.mdl .cm-md-form { background: #332a0e; box-shadow: inset 0 0 0 2px #f3c81b; color: #f6d24a; border-radius: 6px; font-weight: 700; }
.CodeMirror.mdl .cm-md-tag { background: #33132a; box-shadow: inset 0 0 0 2px #e61990; color: #f28cc4; border-radius: 7px; font-weight: 700; }
.CodeMirror.mdl .cm-md-tagO { background: #33220e; box-shadow: inset 0 0 0 2px #ec8013; color: #f0b06a; border-radius: 7px; font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-integer { background: color-mix(in srgb, #3181e9 20%, #0b1020); box-shadow: inset 0 0 0 2px #3181e9; color: color-mix(in srgb, #3181e9 60%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-decimal { background: color-mix(in srgb, #4d90e8 20%, #0b1020); box-shadow: inset 0 0 0 2px #4d90e8; color: color-mix(in srgb, #4d90e8 60%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-string { background: color-mix(in srgb, #ec8013 18%, #0b1020); box-shadow: inset 0 0 0 2px #ec8013; color: color-mix(in srgb, #ec8013 62%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-bool { background: color-mix(in srgb, #be274a 22%, #0b1020); box-shadow: inset 0 0 0 2px #be274a; color: color-mix(in srgb, #be274a 60%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-time { background: color-mix(in srgb, #1d9a4d 20%, #0b1020); box-shadow: inset 0 0 0 2px #1d9a4d; color: color-mix(in srgb, #1d9a4d 62%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-guard { background: color-mix(in srgb, #a045d5 20%, #0b1020); box-shadow: inset 0 0 0 2px #a045d5; color: color-mix(in srgb, #a045d5 62%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-keyset { background: color-mix(in srgb, #b577da 20%, #0b1020); box-shadow: inset 0 0 0 2px #b577da; color: color-mix(in srgb, #b577da 62%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-object { background: color-mix(in srgb, #f3c81b 18%, #0b1020); box-shadow: inset 0 0 0 2px #f3c81b; color: #f6d24a; font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-list { background: color-mix(in srgb, #3fbfae 18%, #0b1020); box-shadow: inset 0 0 0 2px #3fbfae; color: color-mix(in srgb, #3fbfae 62%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-table { background: color-mix(in srgb, #a36633 24%, #0b1020); box-shadow: inset 0 0 0 2px #a36633; color: color-mix(in srgb, #a36633 64%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-module { background: color-mix(in srgb, #8fa3bd 20%, #0b1020); box-shadow: inset 0 0 0 2px #8fa3bd; color: color-mix(in srgb, #8fa3bd 62%, #fff); font-weight: 700; }
.CodeMirror.mdl .cm-md-ty-value { background: color-mix(in srgb, #9298a4 20%, #0b1020); box-shadow: inset 0 0 0 2px #9298a4; color: color-mix(in srgb, #9298a4 62%, #fff); font-weight: 700; }
/* ANGLED metallic caps — hexagon painted on ::before (outer metal) / ::after (dark fill) BEHIND the text; span only gets position:relative → caret-safe. */
.CodeMirror.mdl .cm-md-capB, .CodeMirror.mdl .cm-md-capS, .CodeMirror.mdl .cm-md-capG, .CodeMirror.mdl .cm-md-capK { position: relative; z-index: 0; font-weight: 800; background: transparent; box-shadow: none; border-radius: 0; }
.CodeMirror.mdl .cm-md-capB::before, .CodeMirror.mdl .cm-md-capS::before, .CodeMirror.mdl .cm-md-capG::before, .CodeMirror.mdl .cm-md-capK::before { content: ""; position: absolute; inset: 0; z-index: -2; clip-path: polygon(5px 0, calc(100% - 5px) 0, 100% 50%, calc(100% - 5px) 100%, 5px 100%, 0 50%); }
.CodeMirror.mdl .cm-md-capB::after, .CodeMirror.mdl .cm-md-capS::after, .CodeMirror.mdl .cm-md-capG::after, .CodeMirror.mdl .cm-md-capK::after { content: ""; position: absolute; inset: 1.5px; z-index: -1; clip-path: polygon(4px 0, calc(100% - 4px) 0, 100% 50%, calc(100% - 4px) 100%, 4px 100%, 0 50%); }
.CodeMirror.mdl .cm-md-capB::before { background: #c9925e; } .CodeMirror.mdl .cm-md-capB::after { background: #241a10; }
.CodeMirror.mdl .cm-md-capS::before { background: #b7c0cf; } .CodeMirror.mdl .cm-md-capS::after { background: #181c24; }
.CodeMirror.mdl .cm-md-capG::before { background: #dcb64d; } .CodeMirror.mdl .cm-md-capG::after { background: #23200e; }
.CodeMirror.mdl .cm-md-capK::before { background: #565b66; } .CodeMirror.mdl .cm-md-capK::after { background: #191b22; }

/* A FLOOR ON THE CHAT COLUMN. `min-width: 0` let the file editor squeeze this column to ~215px
   (measured, with four editor tab columns open) — far narrower than the chat shell is designed for,
   so every header and footer row stacked vertically and the model row fell off the bottom of
   `.pact-chat`'s `overflow: hidden`. Measured across widths, holding everything else fixed:
     380px  header 235 + footer 340 in a 512px zone → transcript 0px, model row clipped by 63px
     420px  header 208 + footer 301                → transcript 2px, nothing clipped
     600px  header 181 + footer 231                → transcript 99px, and it can actually be read
   600 is the first width where the transcript — the whole point of the pane — survives, so that is
   the floor. `min(...)` keeps it from overflowing a genuinely small window; the editor yields. */
.pact-right { flex: 1 1 0; min-width: min(600px, 40vw); display: flex; flex-direction: column; gap: 10px;
  position: relative; /* containing block for .ws-pane-dialog-overlay — scopes new dialogs to THIS pane,
  not the viewport. .ws-pane gets the same effect for free via its existing `contain: layout paint`. */ }
/* Same fence for the chat/terminal zone: the compose box's per-keystroke autosize reflow now stays
   inside .pact-chat (a small, capped message list) instead of forcing a whole-page layout. Paired with
   the .pact-ed-group containment above, typing in the Pact chat no longer touches the editor grid. */
.pact-chat, .pact-term { flex: 1; min-height: 0; display: flex; flex-direction: column; contain: layout paint;
  background: var(--panel2); border: var(--seam-w, 1px) solid var(--edge, var(--line)); border-radius: 12px; overflow: hidden; }
.pact-zone-hd { display: flex; align-items: center; gap: 8px; padding: 8px 10px; font-weight: 700; font-size: 12px; border-bottom: 1px solid var(--line); background: var(--panel); }
/* Collapse a pane to just its header bar so the sibling fills the column. */
.pact-collapse { flex: 0 0 auto; }
.pact-right.pr-chat-collapsed .pact-chat { flex: 0 0 auto; }
.pact-right.pr-chat-collapsed .stick-wrap-pc, .pact-right.pr-chat-collapsed .pc-compose { display: none; }
.pact-right.pr-chat-collapsed .pact-chat .pact-zone-hd { border-bottom: 0; }
.pact-right.pr-term-collapsed .pact-term { flex: 0 0 auto; }
.pact-right.pr-term-collapsed .pact-terminal { display: none; }
.pact-right.pr-term-collapsed .pact-term .pact-zone-hd { border-bottom: 0; }

/* Editor Run button (.repl files) + the live REPL terminal. */
.pact-run-btn { flex: 0 0 auto; padding: 3px 12px; border-radius: 7px; font-size: 12px; font-weight: 700; cursor: pointer;
  background: #34d399; border: 1px solid #34d399; color: #05271b; }
.pact-run-btn:hover { filter: brightness(1.06); }
.pact-term-clear { flex: 0 0 auto; padding: 2px 8px; border-radius: 6px; font-size: 10.5px; cursor: pointer;
  background: var(--chip); border: 1px solid var(--line); color: var(--ink-dim); }
.pact-term-clear:hover { color: var(--ink); }
.pact-terminal { flex: 1; min-height: 0; overflow: auto; margin: 0; padding: 8px 10px; white-space: pre-wrap; word-break: break-word;
  font: 11.5px/1.5 ui-monospace, "SF Mono", Menlo, monospace; color: #cbd5e1; background: #0b0f14; }
.pact-terminal .pt-cmd { color: #34d399; font-weight: 700; }
.pact-terminal .pt-err { color: #f87171; }
.pact-terminal .pt-ok  { color: #34d399; }

/* Pact chat (Zone B top) — multi-tab agentic chat on the Ouronet repo. */
.pact-chat { display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.pc-head { gap: 6px; }
.pc-tabs { display: flex; gap: 4px; overflow-x: auto; scrollbar-width: none; min-width: 0; }
.pc-tabs::-webkit-scrollbar { display: none; }
.pc-tab { display: inline-flex; align-items: center; gap: 5px; padding: 3px 5px 3px 8px; border-radius: 7px;
  background: var(--chip); color: var(--ink-dim); font-size: 11px; font-weight: 600; cursor: pointer; white-space: nowrap; flex: 0 0 auto; }
.pc-tab.--active { color: var(--ink); background: color-mix(in srgb, var(--accent) 16%, transparent); }
.pc-tab-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-mute); flex: 0 0 auto; }
.pc-tab-dot.busy { background: #f59e0b; }
.pc-tab-x { color: var(--ink-mute); font-size: 13px; line-height: 1; padding: 0 2px; }
.pc-tab-x:hover { color: var(--ink); }
/* Prime conversation — always open, can't be closed. A ★ marker replaces the × close. */
.pc-tab-prime { color: var(--accent); font-size: 11px; line-height: 1; padding: 0 2px; cursor: default; }
/* "Make Master" ☆ on a non-prime tab: subtle, brightens on hover. */
.pc-tab-tail { display: inline-flex; align-items: center; gap: 3px; }
.pc-tab-mkprime { color: var(--ink-mute); font-size: 12px; line-height: 1; padding: 0 2px; cursor: pointer; opacity: .4; }
.pc-tab:hover .pc-tab-mkprime { opacity: .8; }
.pc-tab-mkprime:hover { opacity: 1; color: var(--amber, #fbbf24); }
.pactm-frow-tail { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
.pc-mode { flex: 0 0 auto; max-width: 96px; }
/* Scoped to the MOBILE head, where this readout is a small text span competing with the tab strip
 * and a 150px ellipsis is the right call. On desktop `.pc-usage` is now an alias carried by the chat
 * shell package's OWN context chip, which lives in the header stats row with room to breathe — and
 * where this cap silently ate the percentage ("167,501 / 1,000,000 tok …"), i.e. the one figure the
 * chip exists to report. */
.pc-head .pc-usage { flex: 0 0 auto; font-size: 10.5px; color: var(--ink-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 150px; }
.pc-usage[hidden] { display: none; }
/* The "· <model>" readout in the Pact head — the model THIS conversation actually runs (see prettyModel). */
.pc-model-now { flex: 0 0 auto; font-size: 10.5px; color: var(--accent, #c77dff); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 160px; font-weight: 600; }
.pc-model-now:empty { display: none; }
/* Admin → Model routing panel */
.route-card h2 { margin: 0 0 4px; }
.route-card .admin-sub { color: var(--ink-dim); font-size: 12.5px; margin: 0 0 14px; }
.route-path { border: 1px solid var(--line, #22304a); border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; background: color-mix(in srgb, var(--panel, #101827) 60%, transparent); }
.route-head { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: default; }
.route-omni-open { margin-left: auto; font-size: 12px; white-space: nowrap; }
.route-head input[type=checkbox] { cursor: pointer; }
.route-badge { font-size: 10.5px; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line, #33415c); color: var(--ink-dim); }
.route-badge.on { color: #34d399; border-color: color-mix(in srgb, #34d399 45%, transparent); }
.route-sub { color: var(--ink-dim); font-size: 12px; margin-top: 4px; }
.route-default { margin: 10px 2px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.route-default .route-label { flex: 1 1 100%; font-size: 12px; color: var(--ink-dim); }
.route-default label { display: inline-flex; align-items: center; gap: 5px; }
.route-actions { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
.route-actions .admin-note { font-size: 12.5px; color: var(--ink-dim); }
/* Admin → Model routing → the OmniRoute model TEST BENCH (viewModelTest). Reuses the routing card's own
   .route-path / .route-default / .route-badge shells so the two cards read as one panel; only the raw-text
   readouts and the sweep table need their own rules. */
.mtest-card h2 { margin: 0 0 4px; }
.mtest-card .admin-sub { color: var(--ink-dim); font-size: 12.5px; margin: 0 0 14px; }
.mtest-inline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
.mtest-sub { color: var(--ink-dim); font-size: 12px; }
.mtest-num { width: 74px; }
.mtest-model { max-width: 100%; flex: 1 1 380px; }
.mtest-prompt { flex: 1 1 100%; resize: vertical; min-height: 42px; background: var(--panel-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px; padding: 7px 9px; font-size: 13px; font-family: inherit; }
.mtest-out:empty { display: none; }
.mtest-out-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-top: 10px; font-size: 13px; }
.mtest-out-head.--ok b { color: #34d399; }
.mtest-out-head.--bad b { color: #f87171; }
.mtest-out-meta { color: var(--ink-dim); font-size: 12px; font-family: ui-monospace, monospace; }
/* Raw provider text — pre-wrap, never truncated in the DOM: an unreadable error string IS the diagnosis. */
.mtest-raw { white-space: pre-wrap; word-break: break-word; margin: 6px 0 0; padding: 8px 10px; max-height: 260px;
  overflow: auto; background: var(--bg-2); border: 1px solid var(--line); border-radius: 8px;
  font: 11.5px/1.5 ui-monospace, monospace; color: var(--ink); }
.mtest-raw.--inline { margin: 0; padding: 0; border: 0; background: none; max-height: 90px; }
.mtest-bar { height: 5px; border-radius: 999px; background: var(--panel-2); overflow: hidden; margin: 8px 0 6px; }
.mtest-bar i { display: block; height: 100%; background: var(--accent, #c77dff); transition: width .2s ease; }
.mtest-table-wrap { max-height: 420px; overflow: auto; border: 1px solid var(--line); border-radius: 8px; }
.mtest-table-wrap:empty { display: none; border: 0; }
.mtest-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.mtest-table th { position: sticky; top: 0; z-index: 1; text-align: left; font-weight: 600; color: var(--ink-dim);
  background: var(--panel); padding: 6px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
.mtest-table td { padding: 5px 8px; border-bottom: 1px solid color-mix(in srgb, var(--line) 45%, transparent); vertical-align: top; }
.mtest-table tr.--bad td { background: color-mix(in srgb, #f87171 7%, transparent); }
.mtest-td-model { font-family: ui-monospace, monospace; max-width: 260px; word-break: break-all; }
.mtest-td-ms { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.mtest-pill { font-size: 10.5px; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line); }
.mtest-pill.--ok { color: #34d399; border-color: color-mix(in srgb, #34d399 45%, transparent); }
.mtest-pill.--bad { color: #f87171; border-color: color-mix(in srgb, #f87171 45%, transparent); }
/* Compact buttons carry a text label, so let them size to content instead of the fixed icon-square. */
.ws-compact, .pact-compact { width: auto; min-width: 0; padding: 0 8px; gap: 4px; font-size: 11px; white-space: nowrap; }
/* Manual wrap — split from Compact, not styled identically: violet (the addressing/wrap accent used
   throughout this migration) rather than the neutral chip look, so the two are visually distinct
   even at a glance, matching that they carry genuinely different weight (Compact is cheap and
   reversible; Wrap respawns the session and asks first). */
.ws-wrap, .pact-wrap { width: auto; min-width: 0; padding: 0 8px; gap: 4px; font-size: 11px; white-space: nowrap;
  border-color: #9d5fd166; color: #c77dff; }
.ws-wrap:hover, .pact-wrap:hover { border-color: #c77dff; color: #e3c4ff; }
/* Compact│Wrap joined into ONE split control — a shared border, radius only on the outer edges, a
   single divider between them — so the two read as one decision with two options ("shrink now" vs.
   "start fresh"), not two unrelated buttons. Matches the chat-shell lab's original design
   (docs/work/chat-shell/migration-plan.md); Core wires this now, Pact is the next pass. */
.ws-split, .pact-split { display: inline-flex; align-items: stretch; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.ws-split > button, .pact-split > button { border: none; border-radius: 0; margin: 0; }
.ws-split > button:first-child, .pact-split > button:first-child { border-right: 1px solid var(--line); }
/* Auto-wrap checkbox + meter, built by window.ChatShell.buildWrapControls (chat-shell.js) — matches
   the Chat Shell Lab's .autolbl/.bar exactly (same visual language, shared class NAMES this time, not
   just matching hex values re-typed twice). Without this rule the meter existed in the DOM (real width,
   real percentage) but had no track/background to actually render against — invisible, not missing. */
.ws-autowrap { display: flex; align-items: center; gap: 4px; font-size: 11px; color: var(--ink-mute); white-space: nowrap; cursor: pointer; }
.ws-autowrap input { margin: 0; }
.ws-wrapmeter { display: inline-block; flex: 0 1 70px; min-width: 36px; height: 6px; border-radius: 3px; background: #0a1020; overflow: hidden; border: 1px solid var(--line); }
.ws-wrapmeter i { display: block; height: 100%; background: #34d399; }
.ws-pane-dialog-note { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); font-size: 11px; color: var(--ink-mute); }
/* Plan usage-limits badge (5h/7d rolling rate-limit utilization) — mirrors the Core cockpit's
   .ws-usage-limits, tinted amber as it nears a limit and red when nearly capped. Full breakdown in title. */
.pc-usage-limits { flex: 0 0 auto; font-size: 10.5px; color: var(--ink-dim); white-space: nowrap;
  border: 1px solid var(--line); border-radius: 6px; padding: 1px 6px; cursor: default; }
.pc-usage-limits[hidden] { display: none; }
.pc-usage-limits.--warn { color: #f59e0b; border-color: color-mix(in srgb, #f59e0b 45%, transparent); }
.pc-usage-limits.--hot { color: #f87171; border-color: color-mix(in srgb, #f87171 55%, transparent); }
.pc-tab-name { max-width: 130px; overflow: hidden; text-overflow: ellipsis; }
/* Pact chat history panel (P3) — a centered modal listing every saved Pact chat, resumable. */
.pc-hist-overlay { position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.5);
  display: flex; align-items: flex-start; justify-content: center; padding: 8vh 16px 16px; }
.pc-hist-panel { width: min(680px, 96vw); max-height: 78vh; overflow: hidden; display: flex; flex-direction: column;
  background: var(--panel, #12161c); border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 18px 50px rgba(0,0,0,.5); }
.pc-hist-hd { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--line); font-weight: 700; font-size: 13px; }
/* Open/Retired split — a currently-open chat vs one whose tab was closed (still resumable below). */
.pc-hist-tabs { display: flex; gap: 6px; padding: 8px 12px 0; }
.pc-hist-tabbtn { background: transparent; border: 1px solid var(--line); color: var(--ink-mute); border-radius: 999px;
  padding: 4px 12px; font-size: 11.5px; font-weight: 600; cursor: pointer; }
.pc-hist-tabbtn:hover { border-color: var(--accent); color: var(--ink); }
.pc-hist-tabbtn.--active { background: var(--accent); border-color: var(--accent); color: #fff; }
.pc-hist-list { overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 4px; }
.pc-hist-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px; border: 1px solid transparent; }
.pc-hist-row:hover { background: var(--chip); border-color: var(--line); }
.pc-hist-main { flex: 1; min-width: 0; }
.pc-hist-name { font-weight: 600; font-size: 12.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-hist-meta { font-size: 10.5px; color: var(--ink-mute); margin: 1px 0; }
.pc-hist-first { font-size: 11px; color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-hist-actions { display: flex; gap: 2px; flex: 0 0 auto; }
/* The prime ("Master") row's delete is disabled + redded out so it can't be removed by mistake. */
.pc-hist-actions .ws-ico.--nodelete, .pc-hist-actions .ws-ico:disabled { color: #f87171; opacity: .5; cursor: not-allowed; }
.pc-hist-actions .ws-ico.--nodelete:hover, .pc-hist-actions .ws-ico:disabled:hover { border-color: transparent; color: #f87171; }
.pc-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 8px; font-size: 12.5px; line-height: 1.5; }
.pc-msg { max-width: 92%; border-radius: 9px; padding: 7px 10px; word-break: break-word; }
/* Same fix as Core's .ws-line.ws-user (this rule was missed in the earlier passes — still the old
   fixed loud blue with no relation to either workspace's identity). Exact lab colours via --bub-u/-a,
   set per workspace above; falls back to the old fixed blue only where those tokens aren't defined. */
.pc-user { align-self: flex-end; background: var(--bub-u, #2f6feb); color: var(--bub-u-fg, #fff); border: 1px solid var(--bub-u-line, transparent); white-space: pre-wrap; position: relative; }   /* keep the line breaks / spacing the user typed */
.pc-asst { align-self: flex-start; background: var(--bub-a, var(--chip)); position: relative; border: 1px solid var(--bub-a-line, var(--line)); }   /* boxed like a prompt, so one response = one rectangle */
/* Absolute position number for each prompt (P#n) and response (R#n) — a non-interactive corner tag you can
   refer to ("re-read R#34"). Counts the whole conversation, including messages not currently on screen. */
.pc-num { position: absolute; top: -10px; z-index: 1; font: 700 11.5px ui-monospace, monospace; line-height: 1.5;
  padding: 1px 7px; border-radius: 8px; letter-spacing: .4px; pointer-events: none; white-space: nowrap; box-shadow: 0 1px 3px rgba(0,0,0,.4); }
.pc-num-p { right: 6px; background: #1b4fc4; color: #dbe6ff; border: 1px solid rgba(255,255,255,.30); }
.pc-num-r { left: 6px; background: var(--accent); color: #1a0a1e; }
/* Interrupted prompt = DARK BLUE (its turn was cut off, no reply). Discarded = RED (marked dead). Two corner
   buttons on the still-actionable one: ▶ resume (green, left) · ✕ discard (red, right). */
.pc-msg.pc-interrupted { background: #172554; box-shadow: inset 0 0 0 1px #3b5bbf; }
.pc-msg.pc-discarded { background: #3a0d0d; color: #fca5a5; box-shadow: inset 0 0 0 1px #7f1d1d; }
.pc-intr-btn { position: absolute; bottom: -13px; width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--bg, #0b0f14);
  font-size: 12px; font-weight: 800; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 5px rgba(0,0,0,.5); z-index: 2; }
.pc-intr-resume { left: 8px; background: #22c55e; color: #052e13; }
.pc-intr-discard { right: 8px; background: #ef4444; color: #2a0606; }
.pc-intr-btn:hover { filter: brightness(1.1); }
.pc-asst-body :first-child { margin-top: 0; } .pc-asst-body :last-child { margin-bottom: 0; }
.pc-asst-body p { margin: 5px 0; } .pc-asst-body pre { background: #0b0f14; border: 1px solid var(--line); border-radius: 7px; padding: 8px; overflow: auto; }
.pc-asst-body code { font: 11.5px ui-monospace, monospace; }
.pc-tool { align-self: flex-start; font-size: 11px; color: var(--ink-dim); font-family: ui-monospace, monospace; max-width: 100%; }
.pc-tool-head { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }
.pc-tool-head:hover { color: var(--ink-soft); }
.pc-tool-caret { display: inline-block; width: 10px; color: var(--ink-mute); }
.pc-tool-body { margin: 4px 0 2px 14px; display: flex; flex-direction: column; gap: 4px; }
.pc-tool-call > b { color: var(--ink-soft); }
.pc-tool-input { margin: 2px 0 0; padding: 6px 8px; background: var(--chip); border-radius: 6px;
  font-size: 10.5px; white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow: auto; }
/* Tool OUTPUT rows (Bash/REPL stdout etc.) — surfaced so `.repl` test results show in the reply view. */
.pc-tool.pc-toolres .pc-tool-head { color: #5eb389; }
.pc-tool.pc-toolres.--err .pc-tool-head { color: #f87171; }
.pc-tool-output { margin: 2px 0 0; padding: 7px 9px; background: #0b0f14; border: 1px solid var(--line); border-radius: 6px;
  font: 11px ui-monospace, monospace; color: #cbd5e1; white-space: pre-wrap; word-break: break-word; max-height: 340px; overflow: auto; }
.pc-tool-output.--err { color: #fca5a5; border-color: color-mix(in srgb, #f87171 40%, var(--line)); }
/* Core cockpit tool output (inside the collapsed tool group). */
.ws-toolres-lbl { color: #5eb389; font-size: 11px; }
.ws-tool-output { margin: 3px 0 0; padding: 6px 8px; background: #0b0f14; border: 1px solid var(--line); border-radius: 6px;
  font: 10.5px ui-monospace, monospace; color: #cbd5e1; white-space: pre-wrap; word-break: break-word; max-height: 300px; overflow: auto; }
.ws-tool-output.--err { color: #fca5a5; border-color: color-mix(in srgb, #f87171 40%, var(--line)); }
/* Response timer: live "M:SS" while thinking/streaming, and a total on the finished reply. */
.pc-timer { font-variant-numeric: tabular-nums; opacity: .85; }
.pc-live-meta { margin-top: 3px; font-size: 10.5px; color: var(--ink-mute); font-family: ui-monospace, monospace; }
/* "Thought for …" header above a finished reply — subtle, sits atop the assistant bubble. */
.pc-thought { margin-bottom: 4px; font-size: 10.5px; font-weight: 600; color: var(--ink-mute); }
.pc-err { align-self: flex-start; color: #f87171; font-size: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pc-retry { font-size: 11px; font-weight: 600; color: var(--accent); background: transparent;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent); border-radius: 6px;
  padding: 1px 8px; cursor: pointer; }
.pc-retry:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.pc-note { align-self: center; color: var(--ink-mute); font-size: 11px; }
/* Sync (↻) button feedback: spin while syncing + a transient bottom toast. */
@keyframes cmSpin { to { transform: rotate(360deg); } }
.pc-syncing { animation: cmSpin .7s linear infinite; }
.pc-flash { position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%); z-index: 20;
  background: color-mix(in srgb, var(--accent) 22%, var(--panel)); color: var(--ink);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent); border-radius: 999px;
  padding: 3px 12px; font-size: 11.5px; box-shadow: 0 4px 14px rgba(0,0,0,.4); pointer-events: none;
  animation: pcFlashIn .15s ease-out; }
@keyframes pcFlashIn { from { opacity: 0; transform: translate(-50%, 6px); } to { opacity: 1; transform: translate(-50%, 0); } }
/* A message typed while the agent was mid-turn: held (dim/pending) until the turn finishes, then
   auto-sent. Mirrors the Core cockpit's .ws-queued styling (orange, pink during deep work). */
.pc-msg.pc-queued { align-self: flex-end; background: color-mix(in srgb, #f59e0b 16%, var(--chip)); border: 1px solid #f59e0b; color: #f59e0b; white-space: pre-wrap; }
.pc-queued-tag { display: block; margin-top: 3px; font-size: 10px; font-weight: 400; opacity: .85; }
/* × to drop a still-queued (not-yet-sent) message. Floated top-right of the amber bubble. */
.pc-queued-x { float: right; margin: -2px -3px 0 6px; padding: 0 4px; background: transparent; border: none;
  color: currentColor; opacity: .7; font-size: 15px; line-height: 1.2; cursor: pointer; }
.pc-queued-x:hover { opacity: 1; }
.pc-msg.pc-queued.pc-queued-deep { background: color-mix(in srgb, #f472b6 16%, var(--chip)); border-color: #f472b6; color: #f472b6; }
.pc-think { align-self: flex-start; color: var(--ink-dim); font-size: 11.5px; animation: cmPulse 1.2s ease-in-out infinite; }
@keyframes cmPulse { 0%,100% { opacity: .5; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .pc-think { animation: none; } }
/* Chat-box loader shown while a tab's transcript is fetched (page load / history resume). An indeterminate
   bar rather than a fake percentage — we don't know the size until it lands — plus a label so it reads as
   "loading", not "empty/broken". */
.pc-loading { align-self: stretch; display: flex; flex-direction: column; gap: 8px; padding: 14px 10px; }
.pc-loading-bar { position: relative; height: 4px; border-radius: 3px; overflow: hidden;
  background: color-mix(in srgb, var(--accent) 16%, transparent); }
/* transform, not `left`: animating `left` re-runs LAYOUT every frame, which is worse again than the
   paint cost of a box-shadow. Same travel — the bar is 40% wide, so -100%…250% of ITS OWN width
   carries it from left:-40% to left:100% of the track. */
.pc-loading-bar::after { content: ""; position: absolute; top: 0; left: 0; height: 100%; width: 40%;
  border-radius: 3px; background: var(--accent); will-change: transform;
  animation: pcLoadSlide 1.15s ease-in-out infinite; }
.pc-loading-lbl { color: var(--ink-dim); font-size: 11.5px; }
@keyframes pcLoadSlide { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }
@media (prefers-reduced-motion: reduce) { .pc-loading-bar::after { animation: none; width: 100%; opacity: .5; } }
.pc-perm { align-self: stretch; display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 8px;
  background: color-mix(in srgb, #f59e0b 14%, transparent); border: 1px solid color-mix(in srgb, #f59e0b 45%, transparent); font-size: 12px; }
.pc-allow { padding: 3px 12px; border-radius: 6px; background: #34d399; border: 1px solid #34d399; color: #05271b; font-weight: 700; cursor: pointer; }
.pc-deny { padding: 3px 12px; border-radius: 6px; background: var(--chip); border: 1px solid var(--line); color: var(--ink); cursor: pointer; }
.pc-compose { display: flex; gap: 6px; align-items: flex-end; padding: 8px; border-top: 1px solid var(--line); }
.pc-compose.pc-drag { background: var(--chip); box-shadow: inset 0 0 0 1px var(--accent); }
/* ── Unified chat box (desktop). The old single header carried 12 controls and overflowed; these three
   rows split it by concern: identity on top, composing in the middle, model/session at the bottom. ── */
/* Type box is full-width on its own row; its controls live in .pc-actionbar directly beneath it. */
.pc-compose.--stacked { padding: 8px 10px 6px; border-bottom: 0; }
.pc-compose.--stacked .pc-input { width: 100%; }
/* Header: tabs left, the three chat actions right-aligned. */
.pc-head-actions { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
/* The line BENEATH the type box: upload · worktree · repo on the left, Stop + Send right. */
.pc-actionbar { display: flex; align-items: center; gap: 7px; padding: 0 10px 7px;
  border-bottom: 1px solid var(--line); }
.pc-repo-lbl { font-size: 11.5px; color: var(--ink-dim); font-weight: 600; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
/* Stop keeps its slot at all times — enabled mid-turn, dimmed otherwise — so the row never reflows
   when a turn starts or ends and the Send button never jumps under the cursor. */
.pc-actionbar .pc-stop { display: inline-flex !important; }
.pc-actionbar .pc-stop:disabled { opacity: .38; cursor: default; }
/* (The old `.pc-modelbar` rules lived here. Dead since Pact's desktop chat became the shared chat
   shell package: nothing carries that class any more — the model row is the package's `.frow
   --modelbar`, styled by chat-shell components.css, which both workspaces and the Lab share.) */
/* Agent swarm: one figure per live subagent, lighting up as they spawn. */
.pc-swarm { display: inline-flex; align-items: center; gap: 3px; min-height: 14px; }
.pc-swarm-fig { width: 5px; height: 12px; border-radius: 2px; background: var(--line);
  transition: background .18s ease, box-shadow .18s ease; }
.pc-swarm-fig.--on { background: #6ee7a8; box-shadow: 0 0 5px rgba(110,231,168,.65); }
.pc-swarm-n { font-size: 11px; color: #6ee7a8; font-weight: 700; margin-left: 2px; font-variant-numeric: tabular-nums; }
/* Repo-scoped history toggle — visibly ON so the filter is never invisible. */
.ws-ico.--on { background: var(--accent); border-color: var(--accent); color: #0b1020; }
.pc-img-input { display: none; }
.pc-attach { flex: 0 0 auto; align-self: flex-end; font-size: 15px; }
.pc-attach:disabled { opacity: .4; cursor: default; }
.pc-compose-extras { display: flex; flex-direction: column; }
.pc-img-preview { display: flex; flex-wrap: wrap; gap: 10px; padding: 6px 8px 0; }
.pc-img-preview[hidden] { display: none; }
.pc-img-chip { display: flex; align-items: center; gap: 4px; }
.pc-img-thumb { max-width: 64px; max-height: 64px; border-radius: 6px; border: 1px solid var(--line); object-fit: cover; }
.pc-img-x { background: transparent; border: 1px solid var(--line); color: var(--ink-dim); border-radius: 50%; cursor: pointer;
  width: 18px; height: 18px; line-height: 1; font-size: 12px; padding: 0; }
.pc-img-x:hover { color: var(--ink); border-color: var(--ink-dim); }
.pc-img-err { color: #f87171; font-size: 12px; padding: 4px 8px 0; }
.pc-img-err[hidden] { display: none; }
.pc-user-images { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.pc-user-image-link { display: block; }
.pc-user-image { display: block; max-width: 140px; max-height: 140px; border-radius: 6px; border: 1px solid rgba(255,255,255,.25); }
.pc-input { flex: 1; min-height: 36px; max-height: 40vh; resize: none; background: var(--chip); color: var(--ink);
  border: 1px solid var(--line); border-radius: 9px; padding: 7px 10px; font-family: inherit; font-size: 13px; }
.pc-send { flex: 0 0 auto; padding: 8px 16px; border-radius: 9px; background: var(--accent); border: 1px solid var(--accent);
  color: #fff; font-weight: 700; cursor: pointer; }
.pc-send:disabled { opacity: .6; }
/* Stop + busy/deep-work/pulse states — an EXACT mirror of the Core cockpit's .ws-send/.ws-stop
   (see above): the SAME amber busy / red deep-work / red stop colors, the SAME wsWorkPulse keyframe,
   and the SAME reduced-motion fallback, so the two send buttons never drift. */
.pc-stop { flex: 0 0 auto; align-self: flex-end; padding: 8px 14px; border-radius: 9px; font-weight: 700; font-size: 13px; cursor: pointer;
  background: #f87171; border: 1px solid #f87171; color: #2a0a0a; white-space: nowrap; }
.pc-stop:hover { filter: brightness(1.05); }
.pc-stop[hidden] { display: none; }
/* Pressed ■ Stop — the acknowledgement state. Dimmed + not-allowed while the engine's interrupt lands, so
   the press is unmistakably registered instead of looking like the button did nothing. */
.pc-stop--pending, .pactm-cbtn-stop--pending { opacity: .55; cursor: not-allowed; filter: none !important; }
.pc-stop--pending:hover { filter: none; }
/* Pact DESKTOP model picker — sits beside the read-only "· model" readout in the chat header. */
.pc-model-sel { flex: 0 0 auto; max-width: 150px; }
.pc-send.busy { background: #f59e0b; border-color: #f59e0b; color: #1a1400; }
.pc-send.busy:hover { filter: brightness(1.05); }
.pc-send.busy.deepwork { background: #f87171; border-color: #f87171; color: #2a0a0a; }
.pc-send.busy.deepwork:hover { filter: brightness(1.05); }
.pc-send.work-pulse { /* ring drawn by ::after above */ }
@media (prefers-reduced-motion: reduce) { .pc-send.work-pulse { animation: none; box-shadow: 0 0 0 2px rgba(148,163,184,.7); } }

/* Markdown viewer (.md files). */
.pact-md { padding: 14px 20px; max-width: 860px; font-size: 13.5px; line-height: 1.62; color: var(--ink); }
.pact-md .md-h { margin: 18px 0 8px; line-height: 1.3; }
.pact-md h1.md-h { font-size: 22px; border-bottom: 1px solid var(--line); padding-bottom: 6px; }
.pact-md h2.md-h { font-size: 18px; border-bottom: 1px solid var(--line); padding-bottom: 4px; }
.pact-md h3.md-h { font-size: 15.5px; } .pact-md h4.md-h { font-size: 14px; }
.pact-md .md-p { margin: 8px 0; }
.pact-md .md-ul, .pact-md .md-ol { margin: 8px 0; padding-left: 24px; }
.pact-md .md-ul li, .pact-md .md-ol li { margin: 3px 0; }
.pact-md .md-code { font: 12px ui-monospace, monospace; background: var(--chip); padding: 1px 5px; border-radius: 5px; }
.pact-md .md-pre { background: #0b0f14; border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; overflow: auto; }
.pact-md .md-pre code { font: 12px/1.5 ui-monospace, monospace; color: #cbd5e1; white-space: pre; }
/* Markdown tables (the Ouronet worksheets). Inline color spans in cells keep their own color. */
.pact-md .md-table { border-collapse: collapse; margin: 12px 0; font-size: 12.5px; max-width: 100%; }
.pact-md .md-table th, .pact-md .md-table td { border: 1px solid var(--line); padding: 5px 9px; text-align: left; vertical-align: top; white-space: nowrap; }
.pact-md .md-table th { background: var(--chip); font-weight: 700; }
.pact-md .md-table tr:nth-child(even) td { background: rgba(255,255,255,.02); }
/* ⧉ copy button anchored to a code block (Pact chat handoff windows). Anchored on the pre; always
   faintly visible (mobile has no hover) and solid on hover. `pre` scrolls, so keep the button sticky
   to the top-right of the viewport of the block via position:absolute on the (relative) pre. */
.pc-asst-body pre.ws-pre-copy, .pact-md pre.ws-pre-copy { position: relative; }
.ws-copy-abs { position: absolute; top: 6px; right: 6px; z-index: 2; padding: 2px 6px; font-size: 12px; line-height: 1;
  background: color-mix(in srgb, var(--panel) 82%, transparent); opacity: .5; backdrop-filter: blur(2px); }
.ws-pre-copy:hover .ws-copy-abs { opacity: 1; }
@media (hover: none) { .ws-copy-abs { opacity: .8; } }   /* touch: no hover, so keep it clearly visible */
.pact-md .md-bq { margin: 8px 0; padding: 2px 12px; border-left: 3px solid var(--accent); color: var(--ink-dim); }
.pact-md .md-hr { border: none; border-top: 1px solid var(--line); margin: 16px 0; }
.pact-md a { color: #4fb6e0; }

/* Phone: an IDE can't be three columns — stack the zones and scroll the whole view. */
@media (max-width: 900px), (pointer: coarse) and (max-width: 1180px) {
  .pact-ide { flex-direction: column; overflow-y: auto; gap: 8px; }
  .pact-tree { flex: 0 0 auto; width: auto; max-height: 38vh; }
  .pact-work { flex: 0 0 auto; flex-direction: column; gap: 8px; }
  .pact-editor-wrap { flex: 0 0 auto; }
  .pact-editor { flex: 0 0 auto; min-height: 55vh; }
  .pact-ed-row { flex-direction: column; }                 /* split boxes stack on a phone */
  .pact-ed-row > .pact-ed-group { min-height: 160px; }
  .pact-ed-gutter-x, .pact-ed-gutter-y { display: none; }  /* no drag-resize on touch */
  /* The columns are stacked here, so a horizontal resize grip has nothing to resize — and any width
     the desktop drag saved must not survive into the stacked layout. `!important` because that width
     is applied as an inline style. */
  .pact-wgrip { display: none; }
  .pact-right { flex: 0 0 auto !important; min-width: 0 !important; }
  .pact-chat, .pact-term { flex: 0 0 auto; min-height: 140px; }
  /* Compose buttons collapse to glyphs so Stop + Send don't crush the text box on a phone (mirrors
     the Core cockpit's mobile compose). The busy/deep-work background colors still show through. */
  .pc-send, .pc-stop { font-size: 0; padding: 8px 12px; }
  .pc-send::after { content: "➤"; font-size: 17px; line-height: 1; }
  .pc-stop::after { content: "■"; font-size: 14px; line-height: 1; }

  /* ===== Pact workspace — MOBILE re-layout (v1.3.0 track; see viewPactMobile in app.js). A fixed
     app-shell: a top bar (☰ + title), ONE full-screen stage, and a left slide-menu drawer. Fills #view,
     which is already bounded by the body.ws-full height chain — so nothing scrolls the page. The
     min-h-0 chain runs: body.ws-full (concrete height) → #main (min-h:0) → #view (flex:1 min-h:0) →
     .pactm (height:100% min-h:0) → .pactm-stage (flex:1 min-h:0) → the stage child (flex:1 min-h:0). */
  .pactm { display: flex; flex-direction: column; height: 100%; min-height: 0; position: relative; overflow: hidden; }
  .pactm-top { flex: 0 0 auto; display: flex; align-items: center; gap: 10px; padding: 6px 10px;
    border-bottom: 1px solid var(--line); background: var(--panel); }
  /* v1.3.9 — chat has its own ☰ in the control row, so drop the whole top bar in chat and give that
     height to the transcript. */
  .pactm[data-stage="chat"] .pactm-top { display: none; }
  .pactm-hb { flex: 0 0 auto; min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center;
    background: var(--chip); border: 1px solid var(--line); color: var(--ink); border-radius: 9px; font-size: 19px; cursor: pointer;
    -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
  .pactm-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; font-size: 13.5px; color: var(--ink); }
  .pactm-stage { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; position: relative; }
  .pactm-stage > * { flex: 1 1 auto; min-height: 0; }   /* whatever element the stage swaps in fills it (keeps the chain unbroken) */
  .pactm-tree { overflow: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; padding: 8px 6px; }
  .pactm-tree .pact-node { font-size: 14px; padding: 6px 8px; }   /* bigger tap targets on a phone */
  .pactm-pane { min-height: 0; }
  /* The chat pane is reused verbatim from the desktop renderer, which includes a ▾ collapse button that
     hands the REPL its space by toggling a class on `.pact-right`. There's no `.pact-right` on the mobile
     stage (chat is full-screen), so the button is dead here — hide it (M1 report flagged it). */
  .pactm .pact-collapse { display: none; }

  /* Left slide-menu drawer (Twitter/X-style): overlay + backdrop, slides from the left, closes on a pick
     or a backdrop tap. Scoped to .pactm so it overlays only the stage, never the app's bottom tab bar. */
  .pactm-backdrop { position: absolute; inset: 0; z-index: 20; background: rgba(0,0,0,.5);
    opacity: 0; pointer-events: none; transition: opacity .2s ease; }
  .pactm.pactm-open .pactm-backdrop { opacity: 1; pointer-events: auto; }
  .pactm-drawer { position: absolute; top: 0; left: 0; bottom: 0; z-index: 21; width: 82vw; max-width: 340px;
    display: flex; flex-direction: column; background: var(--panel2); border-right: 1px solid var(--line);
    transform: translateX(-102%); transition: transform .22s ease; overflow: hidden; }
  .pactm.pactm-open .pactm-drawer { transform: translateX(0); }
  .pactm-drawer-hd { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
    padding: 12px 14px; border-bottom: 1px solid var(--line); font-weight: 700; font-size: 13px; }
  .pactm-drawer-x { min-width: 34px; min-height: 34px; background: transparent; border: none; color: var(--ink-dim);
    font-size: 22px; line-height: 1; cursor: pointer; }
  .pactm-menu { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain; padding: 6px 8px 14px; }
  .pactm-cat { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    color: var(--ink-mute); padding: 12px 8px 4px; }
  .pactm-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; min-height: 44px;
    padding: 8px 10px; background: transparent; border: 1px solid transparent; border-radius: 10px; color: var(--ink);
    cursor: pointer; -webkit-tap-highlight-color: transparent; }
  .pactm-item:hover { background: var(--chip); }
  .pactm-item.--active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .pactm-item-ic { flex: 0 0 auto; width: 26px; text-align: center; font-size: 15px; font-weight: 700; color: var(--ink-dim); }
  .pactm-item-name { flex: 0 0 auto; font-size: 14px; font-weight: 600; }
  .pactm-item-sub { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    text-align: right; font-size: 11.5px; color: var(--ink-dim); }
  .pactm-empty { padding: 8px 12px; font-size: 12.5px; color: var(--ink-dim); }

  /* M2 / v1.4.0 — a full-screen VIEW BOX: the mounted editor fills the wrap; an Ouronet-style handle (a
     slim full-width bar with a centered grabber + label) sits IN-FLOW in the reserved strip above the tab
     bar and expands the "files in this box" sheet. Uses the previously-dead strip instead of floating. */
  .pactm-boxwrap { display: flex; flex-direction: column; min-height: 0; position: relative; }
  .pactm-boxwrap > .pactm-box { flex: 1 1 auto; min-height: 0; }
  .pactm-handle { flex: 0 0 auto; width: 100%; display: flex; flex-direction: column; align-items: center; gap: 3px;
    padding: 5px 10px 7px; border: none; border-top: 1px solid var(--line); background: var(--bg); color: var(--ink-soft);
    cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
  .pactm-handle:active { background: var(--chip); }
  .pactm-handle-grip { width: 42px; height: 4px; border-radius: 999px; background: var(--ink-mute); opacity: .55; }
  .pactm-handle-lbl { font-size: 11.5px; font-weight: 600; }

  /* Full-screen sheet (up-arrow lists + the M3 donut): overlays only the stage, ✕ or backdrop dismisses. */
  .pactm-sheet { position: absolute; inset: 0; z-index: 30; display: flex; flex-direction: column; }
  .pactm-sheet-back { position: absolute; inset: 0; background: rgba(0,0,0,.55); }
  .pactm-sheet-panel { position: relative; z-index: 1; margin-top: auto; display: flex; flex-direction: column;
    max-height: 88%; min-height: 0; background: var(--panel2); border-top: 1px solid var(--line);
    border-radius: 14px 14px 0 0; box-shadow: 0 -6px 24px rgba(0,0,0,.4); }
  .pactm-sheet-hd { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
    padding: 12px 14px; border-bottom: 1px solid var(--line); font-weight: 700; font-size: 13.5px; color: var(--ink); }
  .pactm-sheet-x { min-width: 34px; min-height: 34px; background: transparent; border: none; color: var(--ink-dim);
    font-size: 18px; line-height: 1; cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
  .pactm-sheet-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain; padding: 8px 10px 16px; }
  .pactm-frow { display: flex; align-items: center; gap: 8px; min-height: 46px; padding: 8px 10px;
    border: 1px solid transparent; border-radius: 10px; color: var(--ink); cursor: pointer;
    -webkit-tap-highlight-color: transparent; }
  .pactm-frow:hover { background: var(--chip); }
  .pactm-frow.--active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .pactm-frow-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 14px; font-weight: 600; }
  .pactm-frow.--active .pactm-frow-name { color: var(--accent); }
  /* Per-conversation status light — mimics the send button's colour so a glance shows which agents still
     need you: accent = idle/done (ready for your prompt), amber = working, red = deep work. Not clickable. */
  .pactm-frow-state { flex: 0 0 auto; width: 11px; height: 11px; border-radius: 50%; background: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); }
  .pactm-frow-state.--busy { background: #f59e0b; box-shadow: 0 0 0 3px color-mix(in srgb, #f59e0b 22%, transparent);
    animation: pactmStatePulse 1.15s ease-in-out infinite; }
  .pactm-frow-state.--deep { background: var(--danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 24%, transparent);
    animation: pactmStatePulse 1.15s ease-in-out infinite; }
  /* Keep the "＋ New conversation" row's text aligned with the tab names below it (which now lead with a dot). */
  .pactm-frow-new .pactm-frow-name { margin-left: 21px; }
  .pactm-frow-x { flex: 0 0 auto; min-width: 34px; min-height: 34px; background: transparent; border: none;
    color: var(--ink-dim); font-size: 20px; line-height: 1; cursor: pointer;
    -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
  .pactm-frow-mkprime { flex: 0 0 auto; min-width: 34px; min-height: 34px; background: transparent; border: none;
    color: var(--ink-dim); font-size: 18px; line-height: 1; cursor: pointer;
    -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
  .pactm-frow-mkprime:active { color: var(--accent); }
  /* Prime conversation marker in the mobile conversation sheet — no close, a ★ instead. */
  .pactm-frow-prime { flex: 0 0 auto; min-width: 34px; text-align: center; color: var(--accent);
    font-size: 15px; line-height: 1; }

  /* M3 — the double-donut box picker (tree file tap → choose/create a view box). 8 wedges around an empty
     center: 'open' boxes highlighted, the 'next' creatable box accented, higher 'disabled' wedges dimmed. */
  .pactm-donut-panel { align-items: center; }
  .pactm-donut-wrap { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 14px 12px 22px; }
  .pactm-donut { width: min(78vw, 320px, 58vh); height: auto; touch-action: manipulation; }   /* 58vh cap keeps the square donut inside the sheet in landscape */
  .pactm-wedge { stroke: var(--panel2); stroke-width: 2; }
  .pactm-wedge.--open { fill: color-mix(in srgb, var(--accent) 32%, var(--panel)); cursor: pointer; }
  .pactm-wedge.--open:hover { fill: color-mix(in srgb, var(--accent) 46%, var(--panel)); }
  .pactm-wedge.--next { fill: var(--accent); cursor: pointer; }
  .pactm-wedge.--next:hover { fill: color-mix(in srgb, var(--accent) 82%, #fff); }
  .pactm-wedge.--disabled { fill: var(--chip); opacity: .5; cursor: default; }
  .pactm-wedge-lbl { font-size: 20px; font-weight: 800; fill: var(--ink); user-select: none; -webkit-user-select: none; }
  .pactm-wedge-lbl.--next { fill: #fff; font-size: 17px; }
  .pactm-wedge-lbl.--disabled { fill: var(--ink-mute); opacity: .6; }
  .pactm-donut-hole { fill: var(--panel2); stroke: var(--line); stroke-width: 1.5; cursor: pointer; }
  .pactm-donut-hole-x { font-size: 26px; font-weight: 700; fill: var(--ink-dim); cursor: pointer;
    user-select: none; -webkit-user-select: none; }
  .pactm-donut-hint { max-width: 320px; text-align: center; font-size: 12px; color: var(--ink-dim); line-height: 1.5; }

  /* M4 — the full-screen CHAT: the reused chat pane fills the wrap; TWO up-arrow risers bulge up from the
     bottom (conversations 💬 bottom-left, history 🕐 bottom-right). The wrap reserves a bottom band so the
     compose/send row doesn't crowd into the risers. */
  .pactm-chatwrap { display: flex; flex-direction: column; min-height: 0; position: relative; }
  .pactm-chatwrap > .pact-chat { flex: 1 1 auto; min-height: 0; }
  /* v1.3.7 — a single compact control row: menu · upload · chats · history · stop · send. Replaces the two
     big floating risers + the in-compose buttons, freeing vertical space and the textarea's full width. */
  .pactm-cbar { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; padding: 6px 8px;
    border-top: 1px solid var(--line); background: var(--bg); }
  .pactm-cbtn { min-width: 40px; min-height: 40px; display: inline-flex; align-items: center; justify-content: center;
    font-size: 17px; border: 1px solid var(--line); border-radius: 10px; background: var(--chip); color: var(--ink-soft);
    cursor: pointer; position: relative; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
  .pactm-cbtn-send { min-width: 54px; font-weight: 700; background: var(--accent); border-color: var(--accent); color: #12101c; }
  .pactm-cbtn-send.busy { background: #f59e0b; border-color: #f59e0b; }
  .pactm-cbtn-stop { background: color-mix(in srgb, #ef5350 22%, var(--chip)); border-color: #ef5350; color: #ef5350; }
  /* The in-compose 📎/Send/Stop and the pc-head +/🕐/collapse are redundant on mobile (the control bar
     drives them) — hide them so the compose textarea gets the full width. */
  .pactm-chatwrap .pc-compose .pc-attach,
  .pactm-chatwrap .pc-compose .pc-send,
  .pactm-chatwrap .pc-compose .pc-stop { display: none; }
  .pactm-chatwrap .pc-head .pact-ed-ico { display: none; }
  /* On a phone the token/context readout ("N tok") and the mode select shared the tab row and could
     visually OVERLAP the chat tabs. Give the tabs the flexible remaining width (they scroll
     horizontally on their own — .pc-tabs already has overflow-x:auto), drop the phantom spacer, and pin
     the readout + mode to the right so nothing can collide. Many tabs → scroll here, or use the 💬
     Conversations sheet (control bar) which lists them all full-height. */
  .pactm-chatwrap .pc-head .ws-spacer { display: none; }
  .pactm-chatwrap .pc-head .pc-tabs { flex: 1 1 0; }
  .pactm-chatwrap .pc-head .pc-usage { margin-left: 6px; max-width: 30vw; }
  .pactm-chatwrap .pc-head .pc-head-bulb { display: inline-flex; }   /* Live/Held bulb lives in the header on mobile */
  /* The tab strip is redundant on a phone (every conversation is one tap away in the 💬 Conversations sheet
     below), so show ONLY the active conversation — as a plain TITLE. Its worktree marker (⌥name) rides along
     so you still see which checkout it's on; the status dot + close × are dropped so it reads as a heading. */
  .pactm-chatwrap .pc-head .pc-tab:not(.--active) { display: none; }
  .pactm-chatwrap .pc-head .pc-tab.--active { background: transparent; border: none; padding: 2px 0; pointer-events: none; max-width: 100%; }
  .pactm-chatwrap .pc-head .pc-tab.--active .pc-tab-dot, .pactm-chatwrap .pc-head .pc-tab.--active .pc-tab-x, .pactm-chatwrap .pc-head .pc-tab.--active .pc-tab-prime { display: none; }
  .pactm-chatwrap .pc-head .pc-tab.--active .pc-tab-name { font-weight: 800; font-size: 14.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; }
  .pactm-chatwrap .pc-input { width: 100%; }
  /* The worktree pill used to sit in the compose ROW, stealing a fat column of the textarea's width (it
     "ate into the type box"). Stack the compose on mobile so the pill sits on its OWN short line ABOVE a
     full-width textarea, and hide the (empty on mobile) send-wrap so the Live/Held bulb can't dock over the text. */
  .pactm-chatwrap .pc-compose { flex-direction: column; align-items: stretch; gap: 4px; }
  .pactm-chatwrap .pc-compose .pc-send-wrap { display: none; }
  .pactm-chatwrap .pc-compose .pc-wtpill { align-self: flex-start; height: 26px; padding: 0 8px; font-size: 10.5px; }
  /* Compose size toggle (⌃/⌄): BIG makes the typing box a tall fixed height for composing a long prompt
     (overrides pactChatAutosize's inline height); NORMAL autosizes with content. */
  .pactm-compose-big .pc-input { height: 42vh !important; min-height: 42vh !important; max-height: 42vh !important; overflow-y: auto !important; }
  .pactm-cbtn-collapse { font-size: 15px; }
  .pactm-compose-big .pactm-cbtn-collapse { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
  /* v1.3.9 — use the wasted right-side space: let bubbles run nearly full width on a phone (assistant
     bubbles were capped at 92% and left a dead strip on the right). */
  .pactm-chatwrap .pc-asst, .pactm-chatwrap .pc-tool { max-width: 100%; }
  .pactm-chatwrap .pc-user { max-width: 96%; }
  .pactm-chatwrap .pc-scroll { padding-left: 8px; padding-right: 8px; }
  .pactm-frow-new { border-color: var(--accent); color: var(--accent); font-weight: 700; }
  .pactm-frow-new .pactm-frow-name { color: var(--accent); }
  /* History rows stack name + meta + first-prompt snippet (mirrors the desktop 🕐 row content). */
  .pactm-hrow { align-items: flex-start; }
  .pactm-hrow-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
  .pactm-hrow-name { font-size: 14px; font-weight: 700; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .pactm-hrow-meta { font-size: 11.5px; color: var(--ink-dim); }
  .pactm-hrow-first { font-size: 12.5px; color: var(--ink-mute); overflow: hidden; text-overflow: ellipsis;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; white-space: normal; }
}

/* Conversation status-light pulse (mobile switcher) — defined top-level so keyframes are global and the
   reduced-motion opt-out isn't nested inside another @media (invalid). */
@keyframes pactmStatePulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
@media (prefers-reduced-motion: reduce) { .pactm-frow-state { animation: none !important; } }

/* ===== EXOCORTEX (ROADMAP-2.0 §2 / T3.1–T3.5) ==================================================
   The shared context/navigation surface mounted in BOTH the Core cockpit panes and the Pact chat.
   Deliberately built from IN-FLOW disclosure blocks (like .ws-activity / .ws-activity-log) rather
   than anchored popups: a pane clips its overflow, and an anchored popup on a narrow phone viewport
   is exactly how "panel clipped / unreachable on mobile" keeps happening. Everything here wraps,
   scrolls, and stays inside its parent's width. */
.exo { display: flex; flex-direction: column; gap: 0; min-width: 0; flex: 0 0 auto; }
/* A pane and a Pact chat both live in a FIXED-height box with overflow:hidden, and the transcript
   carries a min-height. Without releasing it, an open panel grows the column past the box and clips
   the COMPOSE ROW off the bottom — the mobile regression that keeps recurring in this app. `~` (not
   :has) so every browser honours it; .exo is always a previous sibling of the scroller. */
.exo.--open ~ .ws-transcript { min-height: 90px; }
.exo.--open ~ .pc-scroll { min-height: 0; }
/* No background/border of its own any more. This row used to carry ctx/agents/jump chips too, which
   made it read as a second, differently-styled bar stacked on the header (see CHANGELOG 1.6.2) — those
   moved into the header/search-drawer, in app.js's buildPane. What's left here (mainly ⤓ Latest,
   usually hidden) should sit in the flow, not announce itself as its own block. */
.exo-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 2px 8px; min-width: 0; }
.exo-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; line-height: 1.5;
  background: var(--chip); color: var(--ink-dim); border: 1px solid var(--line); border-radius: 999px;
  padding: 2px 9px; cursor: pointer; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.exo-chip:hover { color: var(--ink); border-color: var(--accent); }
.exo-chip.--open { color: var(--ink); border-color: var(--accent); background: var(--panel-2); }
.exo-chip.--ok { color: #34d399; border-color: color-mix(in srgb, #34d399 45%, var(--line)); }
.exo-chip.--notice { color: #7aa2f7; border-color: color-mix(in srgb, #7aa2f7 45%, var(--line)); }
.exo-chip.--warn { color: #fbbf24; border-color: color-mix(in srgb, #fbbf24 45%, var(--line)); }
.exo-chip.--danger { color: var(--danger); border-color: color-mix(in srgb, #f87171 55%, var(--line)); }
.exo-chip.--muted { color: var(--ink-mute); }
/* The jump/search box. `flex: 1 1 140px` is what makes it drop onto its own full-width row on a
   phone instead of squeezing the chips off the edge. */
.exo-jump { display: flex; align-items: center; gap: 4px; flex: 1 1 140px; min-width: 120px; }
.exo-jump-in { flex: 1 1 auto; min-width: 0; font-size: 11px; padding: 3px 8px; border-radius: 999px;
  background: var(--bg-2); color: var(--ink); border: 1px solid var(--line); }
.exo-jump-in:focus { outline: none; border-color: var(--accent); }
.exo-btn { font-size: 11px; padding: 3px 9px; border-radius: 999px; cursor: pointer;
  background: var(--chip); color: var(--ink-dim); border: 1px solid var(--line); white-space: nowrap; }
.exo-btn:hover { color: var(--ink); border-color: var(--accent); }
.exo-btn:disabled { opacity: .5; cursor: default; }
.exo-edges { display: flex; justify-content: center; padding: 3px 8px; }
.exo-edges .exo-edge { margin-bottom: 0; }
.exo-cues { display: flex; flex-direction: column; gap: 3px; padding: 4px 8px; }
.exo-cue { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 11px; color: var(--ink-dim); }
.exo-cue.--ok { color: #34d399; }
.exo-cue.--notice { color: #7aa2f7; }
.exo-cue.--warn { color: #fbbf24; }
.exo-cue.--danger { color: var(--danger); }
/* A client-side GUESS is drawn differently from something the engine reported — dashed, tagged. */
.exo-cue.--guess { border-left: 2px dashed currentColor; padding-left: 6px; font-style: italic; }
.exo-guess-tag { font-size: 9px; text-transform: uppercase; letter-spacing: .04em; font-style: normal;
  border: 1px dashed currentColor; border-radius: 4px; padding: 0 4px; opacity: .85; }
.exo-tier { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; font-size: 11px;
  border: 1px solid var(--line); border-radius: 8px; padding: 4px 8px; color: var(--ink-dim); }
.exo-tier.--notice { border-color: color-mix(in srgb, #7aa2f7 45%, var(--line)); }
.exo-tier.--warn { border-color: color-mix(in srgb, #fbbf24 55%, var(--line)); }
.exo-tier.--danger { border-color: color-mix(in srgb, #f87171 60%, var(--line));
  background: color-mix(in srgb, #f87171 8%, transparent); }
.exo-tier-lbl { color: var(--ink); white-space: nowrap; }
.exo-tier-advice { flex: 1 1 200px; min-width: 0; }
.exo-jumpline { gap: 8px; }
.exo-sub { font-size: 10.5px; color: var(--ink-mute); flex-basis: 100%; }
.exo-warn { font-size: 10.5px; color: #fbbf24; flex-basis: 100%; }
/* The disclosure panel. Bounded height + its own scroll: a 40-row MCP tool list can never push the
   compose box off a phone screen. */
.exo-panel { max-height: 42vh; overflow: auto; -webkit-overflow-scrolling: touch;
  background: var(--panel2); border-bottom: 1px solid var(--line); }
/* POPOVER MODE. The chip that opens this panel (the context readout) lives in the footer's model row
   while the panel's own bar lives in the header — so in flow the breakdown appeared at the far end of
   the pane from the click and read as "nothing happened". As a popover it opens AT the control, above
   it when there is no room below (exoMountBar's placePanel does the maths against the real rect). */
.exo-panel--pop { position: fixed; z-index: 90; max-height: 60vh; border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 14px 40px rgba(0, 0, 0, .55); background: var(--panel2); }
.exo-panel-in { display: flex; flex-direction: column; gap: 5px; padding: 8px 10px; min-width: 0; }
.exo-ctxhead { font-size: 12px; color: var(--ink); }
.exo-unavail, .exo-empty { display: flex; flex-direction: column; gap: 3px; font-size: 12px; color: var(--ink); }
.exo-unavail { border-left: 3px solid var(--ink-mute); padding-left: 8px; }
.exo-empty { border-left: 3px solid #34d399; padding-left: 8px; }
/* T3.1 — the composition bar. Colours come from the SDK verbatim (CONTRACT §1). */
.exo-bar-track { display: flex; width: 100%; height: 12px; border-radius: 6px; overflow: hidden;
  background: var(--bg-2); border: 1px solid var(--line); }
.exo-seg { display: block; height: 100%; background: var(--ink-mute); min-width: 1px; }
.exo-seg.--free { background: repeating-linear-gradient(45deg, var(--bg-2), var(--bg-2) 3px, var(--line) 3px, var(--line) 6px); }
.exo-seg.--deferred { opacity: .65; }
.exo-legend { display: flex; flex-direction: column; gap: 2px; }
.exo-legend-row { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--ink-dim); min-width: 0; }
.exo-legend-row.--free { color: var(--ink-mute); }
.exo-swatch { width: 9px; height: 9px; border-radius: 2px; background: var(--ink-mute); flex: 0 0 auto; }
.exo-swatch.--free { background: var(--line); }
.exo-legend-lbl { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.exo-legend-tok { font: 600 10.5px/1.5 ui-monospace, monospace; color: var(--ink-mute); }
.exo-legend-pct { font: 600 10.5px/1.5 ui-monospace, monospace; color: var(--ink-dim); min-width: 42px; text-align: right; }
.exo-disclose { align-self: flex-start; background: none; border: none; color: var(--ink-dim);
  font-size: 11px; cursor: pointer; padding: 2px 0; }
.exo-disclose:hover { color: var(--ink); }
.exo-detail { display: flex; flex-direction: column; gap: 1px; padding-left: 10px; }
.exo-detail-row { display: flex; gap: 8px; justify-content: space-between; font-size: 10.5px;
  color: var(--ink-mute); min-width: 0; }
.exo-detail-row > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* T3.2 — the fleet. A stalled agent has to be impossible to miss. */
.exo-agent-row { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--ink-dim);
  border: 1px solid var(--line); border-radius: 8px; padding: 3px 7px; flex-wrap: wrap; min-width: 0; }
.exo-agent-row.--stale { border-color: #fbbf24; background: color-mix(in srgb, #fbbf24 10%, transparent); }
.exo-agent-row.--error { border-color: var(--danger); background: color-mix(in srgb, #f87171 10%, transparent); }
.exo-agent-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: var(--ink-mute); }
.exo-agent-dot.--running { background: #34d399; animation: exoPulse 1.6s ease-in-out infinite; }
.exo-agent-dot.--done { background: var(--ink-mute); }
.exo-agent-dot.--error { background: var(--danger); }
.exo-agent-dot.--unknown { background: #fbbf24; }
.exo-agent-lbl { color: var(--ink); font-weight: 600; }
.exo-agent-desc { flex: 1 1 120px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.exo-agent-el { font: 600 10.5px/1.5 ui-monospace, monospace; }
.exo-agent-el.--unknown { color: var(--ink-mute); font-style: italic; }
.exo-stale { font-size: 10px; font-weight: 700; color: #fbbf24; border: 1px solid #fbbf24;
  border-radius: 5px; padding: 0 5px; }
@keyframes exoPulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .exo-agent-dot.--running, .exo-spin { animation: none !important; } }
.exo-spin { width: 10px; height: 10px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid color-mix(in srgb, currentColor 35%, transparent); border-top-color: currentColor;
  animation: exoSpin .9s linear infinite; }
@keyframes exoSpin { to { transform: rotate(360deg); } }
/* T3.5 — the recalled turn, inline in the transcript. */
.exo-recall { border: 1px solid color-mix(in srgb, #7aa2f7 45%, var(--line)); border-radius: 10px;
  background: color-mix(in srgb, #7aa2f7 7%, transparent); padding: 7px 9px; margin: 4px 0;
  display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.exo-recall-hd { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--ink); }
.exo-recall-x { background: none; border: none; color: var(--ink-mute); cursor: pointer; font-size: 15px;
  line-height: 1; padding: 0 2px; }
.exo-recall-x:hover { color: var(--ink); }
.exo-prov { font: 600 10.5px/1.5 ui-monospace, monospace; color: var(--ink-mute); }
.exo-recall-body { white-space: pre-wrap; word-break: break-word; font-size: 12px; color: var(--ink-dim);
  max-height: 30vh; overflow: auto; }
.exo-recall-imgs { display: flex; flex-wrap: wrap; gap: 6px; }
.exo-recall-img { max-width: 160px; max-height: 120px; border-radius: 6px; border: 1px solid var(--line); }
.exo-hit-row { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; font-size: 11.5px;
  color: var(--ink-dim); min-width: 0; }
.exo-hit-snip { flex: 1 1 160px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Mobile: the bar is the first thing under the chat header, so it must never be the thing that
   pushes the transcript off-screen. Chips shrink, the jump box gets its own row, panels cap lower. */
@media (max-width: 820px) {
  .exo-bar { padding: 4px 6px; gap: 5px; }
  .exo-chip, .exo-btn, .exo-jump-in { font-size: 11.5px; padding: 3px 8px; }
  .exo-jump { flex: 1 1 100%; order: 9; }
  .exo-panel { max-height: 32vh; }
  .exo-recall-body { max-height: 26vh; }
  .exo-tier-advice { flex-basis: 100%; }
}

/* ---- EXACT MODEL READOUT (Core `.ws-model-now`, Pact `.pc-model-now`) ------------------------
   "Opus" in a selector does not say WHICH Opus. These show the exact wire build (date suffix and
   all) next to the picker. Widened from the old 160px cap because an exact id like
   `opus-4-5-20250929` was being ellipsised away — which defeated the entire point. `--unknown`
   is the honest state: the engine has not reported a model yet, rendered muted + italic so it
   never reads as a model NAME. */
.ws-model-now { flex: 0 0 auto; font-size: 10.5px; color: var(--accent, #c77dff); font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 210px; letter-spacing: .1px; }
.ws-model-now:empty { display: none; }
.ws-model-now.--unknown, .pc-model-now.--unknown { color: var(--ink-dim, #7c8aa5); font-weight: 500; font-style: italic; }
.pc-model-now { max-width: 210px; }
@media (max-width: 820px) { .ws-model-now, .pc-model-now { max-width: 130px; } }

/* WHEN A TURN WAS SPAWNED — the bubble's own lower-left corner (wsWhenChip in app.js).
   A BLOCK on its own line rather than an absolutely-positioned corner: absolute would need every
   bubble to reserve bottom padding for it whether it has a stamp or not (a live-streaming row has no
   `at` yet), and it would sit on top of the last line of a message that happens to fill the width.
   Dim and small enough to read past — it answers "when was this" when you look for it, and stays out
   of the way when you do not. */
.ws-line .ws-when, .pc-msg .ws-when {
  display: block; margin-top: 5px; font-size: 10px; line-height: 1.2;
  color: var(--ink-mute); opacity: .6; font-variant-numeric: tabular-nums; letter-spacing: .02em; }
.ws-line.ws-user .ws-when { color: var(--bub-u-fg, var(--ink-mute)); opacity: .45; }

/* THE VERSION-MISMATCH BANNER — see wsVersionGateApply in app.js. Fixed to the bottom above the nav so
   it is visible on a phone without scrolling, and unmissable: a silently-wrong chat is worse than a
   loudly-refused one. */
.ws-version-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 9999; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 8px; padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, #f87171 22%, var(--panel)); border-top: 2px solid #f87171;
  color: var(--ink); font-size: 12.5px; line-height: 1.45; box-shadow: 0 -6px 18px rgba(0,0,0,.45); }
.ws-version-bar b { color: #f87171; }
/* The type box stays visible and readable — you can still SEE the conversation; only sending refuses. */
body.ws-version-blocked .ws-send, body.ws-version-blocked .pc-send { opacity: .5; }

/* The × on a conversation tab — see wsCloseConvSlot. Dim until you are on the tab or hovering it, so
   a row of tabs does not read as a row of delete buttons. The ★ master has none: closing it would
   strand every bookmark, image path and saved session that predates multi-chat. */
.ws-conv-tab-x { margin-left: 6px; opacity: .35; font-size: 13px; line-height: 1; padding: 0 2px; border-radius: 4px; }
.ws-conv-tab:hover .ws-conv-tab-x, .ws-conv-tab.--act .ws-conv-tab-x { opacity: .75; }
.ws-conv-tab-x:hover { opacity: 1; background: color-mix(in srgb, #f87171 30%, transparent); color: #fff; }
