/* ============================================================
   Design tokens — dark theme (default).
   The whole app is themed through these variables; the light
   theme below re-declares the same names. Component rules should
   reference tokens, never raw hexes, wherever possible.
   ============================================================ */
:root {
  --bg: #101014;
  --bg-1: #141419;
  --bg-2: #17171c;
  --bg-3: #232329;
  --bg-hover: #2b2b33;
  --border: #33333c;
  --text: #ececf1;
  --text-dim: #b9b9c6;
  --text-faint: #94949f;
  /* Brand accent (indigo) — used for primary actions, active nav,
     focus rings and "in progress" state. */
  --accent: #6d6af8;
  --accent-2: #a5a3ff;
  --accent-soft: rgba(109, 106, 248, 0.16);
  --on-accent: #ffffff;
  --user-bubble: #2c2c38;
  --danger: #ef4444;
  --danger-soft: rgba(239, 68, 68, 0.12);
  --ok: #34c37e;
  --ok-soft: rgba(52, 195, 126, 0.12);
  --warn: #e0a458;
  --warn-soft: rgba(224, 164, 88, 0.12);
  --info: #4f8ff7;
  --info-soft: rgba(79, 143, 247, 0.12);
  /* Every left rail — Group Chat, Company, Inbox — is this wide, and so is the app
     sidebar they each stand in for. They were 280/264/264 and the furniture moved as
     you walked between the views. See "THE RAIL STANDARD" at the end of this file. */
  --rail-w: 264px;
  --radius: 12px;
  --radius-sm: 8px;
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-2: 0 8px 28px rgba(0, 0, 0, 0.45);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: "Cascadia Code", "Fira Code", Consolas, "Courier New", monospace;
  color-scheme: dark;
}

/* Light theme — professional workspace look. Activated by
   <html data-theme="light"> (set from localStorage / OS preference
   by an inline script in index.html before first paint). */
:root[data-theme="light"] {
  --bg: #f6f6f8;
  --bg-1: #fafafc;
  --bg-2: #ffffff;
  --bg-3: #f0f0f4;
  --bg-hover: #e9e9ef;
  --border: #e2e2ea;
  --text: #1e2026;
  --text-dim: #565b66;
  --text-faint: #6f7480;
  --accent: #5a55ee;
  --accent-2: #4640c8;
  --accent-soft: rgba(90, 85, 238, 0.10);
  --on-accent: #ffffff;
  --user-bubble: #e9e9f8;
  --danger: #d92d20;
  --danger-soft: rgba(217, 45, 32, 0.08);
  --ok: #17915b;
  --ok-soft: rgba(23, 145, 91, 0.10);
  --warn: #b45309;
  --warn-soft: rgba(180, 83, 9, 0.10);
  --info: #2563eb;
  --info-soft: rgba(37, 99, 235, 0.08);
  --shadow-1: 0 1px 2px rgba(16, 18, 28, 0.06);
  --shadow-2: 0 8px 28px rgba(16, 18, 28, 0.14);
  color-scheme: light;
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: 15px;
  line-height: 1.55;
}
button { font-family: inherit; cursor: pointer; }
.hidden { display: none !important; }

/* The shell is the sidebar (or, on a full-bleed view, that view's own rail) beside the
   main column, and nothing above either. There WAS a bar across the top carrying the
   brand and the Online-mode switch; the brand belongs to whichever navigation frame is
   on screen (`.side-brand` here, `railTopHtml()` there) and Online mode belongs in
   Settings, so the bar was carrying one thing that had a home and one that was never a
   permanent control. Removing it is what gives every view the full height of the window.
   The column direction stays: #sidebar-toggle is a fixed chip and takes no row. */
#app { display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
#app-body { flex: 1; min-height: 0; display: flex; }

/* ---- Sidebar ---- */
#sidebar {
  width: var(--rail-w);
  flex-shrink: 0;
  background: var(--bg-2);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 14px;
  gap: 12px;
}
.brand { display: flex; align-items: center; gap: 9px; padding: 6px 6px 2px; }
/* The brand at the head of the sidebar — outside `.side-scroll`, so it stays put while
   the nav under it scrolls, and it is the app's identity rather than the first row of a
   list. `flex-shrink: 0` because a long Recents list would otherwise squash it. */
#sidebar .side-brand { flex-shrink: 0; padding: 2px 6px 10px; border-bottom: 1px solid var(--border); }
.brand-logo { font-size: 24px; display: inline-flex; align-items: center; flex-shrink: 0; }
/* QuorieAI mascot mark — sizes to its container's font-size (1em) so it drops
   into the brand, empty-state and auth lockups without per-place tuning. */
.qmark { width: 1em; height: 1em; display: inline-block; vertical-align: middle; overflow: visible; }
/* Wordmark: "Quor" in text colour, "ai" in the mascot's cyan (matches the eyes). */
.wordmark { letter-spacing: -0.01em; }
.wm-ai { color: var(--text-dim); }
.brand-text { display: flex; align-items: center; }
.brand-text strong { font-size: 18px; font-weight: 700; line-height: 1; }
.brand-text small { color: var(--text-faint); font-size: 11px; }

.btn-new {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: var(--radius);
  padding: 11px;
  font-size: 14px;
  font-weight: 600;
  transition: background .15s;
}
.btn-new:hover { background: var(--accent-2); }

.conversations { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.conv-item {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px;
  border-radius: 9px;
  color: var(--text-dim);
  font-size: 13.5px;
  cursor: pointer;
  white-space: nowrap;
}
.conv-item:hover { background: var(--bg-hover); color: var(--text); }
.conv-item.active { background: var(--accent-soft); color: var(--text); }
/* The conversation you're currently in — pinned to the top of Recents and marked
   with an accent rail so you can jump back to it (chat, discussion, inbox chat)
   from any view, even before it has a title. Deliberately NOT a dot: a dot reads
   as "this one is running", which is a different question with its own marker
   below. Where-you-are is a rail plus weight; still-working is a spinner. */
.conv-item.current { color: var(--text); font-weight: 600; box-shadow: inset 2px 0 0 var(--accent); }
/* The row is a title with an optional second line under it ("2m · 1 queued"), so
   the text column is its own flex child — the title alone can no longer be `flex: 1`
   or it would stretch vertically inside that column instead of horizontally. */
.conv-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.conv-title { overflow: hidden; text-overflow: ellipsis; }
.conv-meta { font-size: 11px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; }
.conv-item.working .conv-meta { color: var(--accent-2); }
/* Rename and Delete are ONE control shape, revealed together on hover — two buttons
   that sit side by side and differ only in their hover colour must not differ in
   their size, padding or reveal. Only the destructive one turns red. */
.conv-act {
  opacity: 0; border: none; background: transparent; color: var(--text-faint);
  font-size: 14px; padding: 2px 4px; border-radius: 6px;
  flex: none; cursor: pointer;
}
.conv-item:hover .conv-act { opacity: 1; }
.conv-rename:hover { color: var(--text); background: var(--bg-hover); }
.conv-del:hover { color: var(--danger); background: rgba(240,96,106,.12); }
/* A thread that's still working while you read another one. It LEADS the row (the
   eye lands there first, and it's the one question the list gets asked) and it is a
   spinner, not a dot — nothing else in Recents rotates, so "which one is running"
   is answered without comparing two similar dots. It must survive hover: pointing
   at a row to check it is exactly when the answer used to disappear. */
.conv-item.working .conv-title { color: var(--text); }
.conv-working {
  flex: none; width: 11px; height: 11px; border-radius: 50%;
  box-sizing: border-box;
  border: 2px solid var(--accent-soft);
  border-top-color: var(--accent);
  animation: conv-spin .8s linear infinite;
}
@keyframes conv-spin { to { transform: rotate(360deg); } }
/* A reply that landed while you were somewhere else. It trails the row and it is
   GREEN — the third thing this list has to say, and the only one that means "this
   is finished, go and read it". Indigo is spoken for twice over (the rail you're
   on, the spinner still going), so a third indigo mark would be the original bug
   again. Bold reads as unread the way every mail client has taught. */
.conv-item.unread .conv-title { color: var(--text); font-weight: 600; }
.conv-unread {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok);
}
/* Nothing here reorders the list. A row that must be seen — the one you're in, one
   still working, one holding an unread reply — is let THROUGH the "See more" fold
   in its own position instead, so a click never moves the thing you clicked. */

/* One message pane per open thread; only the active one is shown. Panes keep their
   own DOM so a background reply streams on and switching back is instant. */
.msg-pane { display: block; }
.msg-pane.hidden { display: none; }

/* "Still working on the server" placeholder shown when a reopened conversation ends
   on a dangling user turn (e.g. a discussion the browser was refreshed away from,
   still completing server-side). Replaced by the real reply when the poll picks it up. */
.pending-reply { display: flex; align-items: center; gap: 8px; color: var(--text-dim); font-size: 13.5px; }
.pending-dots { display: inline-flex; gap: 3px; }
.pending-dots i { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); opacity: .35; animation: pending-blink 1.2s infinite ease-in-out; }
.pending-dots i:nth-child(2) { animation-delay: .2s; }
.pending-dots i:nth-child(3) { animation-delay: .4s; }
@keyframes pending-blink { 0%, 80%, 100% { opacity: .25; } 40% { opacity: 1; } }

.btn-settings {
  background: var(--bg-3);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  font-size: 13.5px;
}
.btn-settings:hover { background: var(--bg-hover); color: var(--text); }

/* ---- Main ---- */
#main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
#chat-header {
  height: 52px; flex-shrink: 0;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; padding: 0 22px;
}
.model-badge {
  font-size: 12.5px; color: var(--text-dim);
  background: var(--bg-2); border: 1px solid var(--border);
  padding: 5px 12px; border-radius: 20px;
}
.model-badge .dot { color: var(--ok); }

/* ---- Messages ---- */
.messages { flex: 1; overflow-y: auto; padding: 26px 0; }
.msg-row { max-width: 780px; margin: 0 auto; padding: 6px 22px; display: flex; gap: 14px; }
.msg-row.user { flex-direction: row-reverse; }
.avatar {
  width: 30px; height: 30px; border-radius: 8px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; font-size: 16px;
}
/* Your own row carries the person-disc `peopleAvatar` draws everywhere else. The tile
   owns the geometry, the disc owns the colour, so this override carries only SIZE —
   the rule written at `.cn-ppl-avatar`. Background goes transparent because the disc
   IS the background; leaving the tile's fill on would ring the circle with a square. */
.avatar.user { background: none; }
.avatar.user .cn-ppl-avatar { width: 30px; height: 30px; font-size: 13px; }
.avatar.assistant { background: var(--bg-3); }
.avatar.peer { background: rgba(255,255,255,0.07); }
/* QuorieAI mascot as the assistant avatar — clipped to the rounded-square tile so
   the mark stays centered inside it (no overflow spill). */
/* Match the group-chat icon: accent mark, centered with breathing room in the tile. */
.avatar-bot { width: 62%; height: 62%; overflow: hidden; display: block; color: var(--accent); }
.msg-body { flex: 1; min-width: 0; padding-top: 2px; display: flex; flex-direction: column; align-items: flex-start; }
.msg-row.user .msg-body { align-items: flex-end; }
/* AI avatar sits on the left, so reserve the same width on the right (avatar 30 +
   gap 14) so assistant content lines up with the user bubble's right edge. */
.msg-row.assistant .msg-body { padding-right: 44px; }
.msg-role { font-size: 12px; color: var(--text-faint); margin-bottom: 3px; font-weight: 600; }
/* When the message was said — the question when it was asked, the answer when it
   landed. Sits at the end of the header line and is quieter than the name it follows:
   a stamp is there to be checked, not read. `cursor: help` because the full instant,
   to the second, is on the hover title. */
.msg-role .msg-time {
  margin-left: 8px; font-weight: 500; font-size: 11px;
  color: var(--text-faint); opacity: .72; white-space: nowrap; cursor: help;
}
.src-badge {
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 10px; font-weight: 600; line-height: 1;
  padding: 2px 6px; border-radius: 999px; margin-left: 6px;
  border: 1px solid var(--border); vertical-align: middle; cursor: help;
}
.src-badge.peer { color: var(--ok); background: var(--ok-soft); border-color: transparent; }
.src-badge.local { color: var(--warn); background: var(--warn-soft); border-color: transparent; }
.msg-content {
  word-wrap: break-word; overflow-wrap: anywhere; max-width: 100%;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 14px; padding: 9px 14px;
}
.msg-row.user .msg-content { background: var(--user-bubble); border-color: transparent; }
.msg-content.msg-skip { color: var(--text-faint); font-style: italic; background: transparent; border-style: dashed; }
.msg-row.peer .msg-content { background: var(--bg-3); border-color: var(--border); }

/* Every CARD under an answer is one width — the column's.
   `.msg-body` is a flex column with `align-items: flex-start`, so a block child
   shrinks to its own content unless it says otherwise. Only the Workbench said so:
   the downloads card stopped at 520px and the send-approval at 560px, so one answer
   that made a file and then asked to send it drew three boxes with three different
   right edges, each starting from the same left one. This is the ONE place that
   decides it — a card added later joins the list rather than picking its own width.
   Chips, badges and attachments are inline-flex and are deliberately not here: they
   size to their content because that is what a chip IS. */
.msg-body > .wb-box,
.msg-body > .files-card,
.msg-body > .send-approve,
.msg-body > .proc-box,
.msg-body > .msg-error { align-self: stretch; width: 100%; box-sizing: border-box; }
/* An approval can also land straight in the pane (a card rebuilt outside a turn has no
   row to sit in), and an approved action that produced a file drops its downloads card
   in beside it. There they are ordinary blocks — full pane width — so they take the
   message column's own measurements instead, like .discussion-learn does. */
.msg-pane > .send-approve, #messages > .send-approve,
.msg-pane > .files-card, #messages > .files-card { max-width: 780px; margin: 10px auto; }
.discussion-sep {
  max-width: 780px; margin: 12px auto 4px; padding: 0 22px;
  display: flex; align-items: center; gap: 10px;
  font-size: 12px; color: var(--text-faint); font-weight: 600;
}
.discussion-sep::before, .discussion-sep::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.dl-vote { max-width: 780px; margin: 2px auto; padding: 0 22px; font-size: 12px; color: var(--text-faint); }
.dl-vote.yes { color: var(--ok); }
.dl-vote.no { color: var(--warn); }
.discussion-learn {
  max-width: 780px; margin: 8px auto; padding: 10px 16px; border-radius: 12px;
  border: 1px solid var(--border); font-size: 13px; line-height: 1.5;
}
.discussion-learn.ok { background: var(--ok-soft); border-color: transparent; }
.discussion-learn.no { background: var(--warn-soft); border-color: transparent; }
.dl-head { font-weight: 600; }
.dl-facts { margin: 6px 0 0; padding-left: 20px; }
.dl-facts li { margin: 4px 0; }
.dl-fact-text { vertical-align: middle; }
.dl-promote {
  margin-left: 8px; vertical-align: middle; font-size: 11px; line-height: 1.6;
  padding: 1px 9px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-2); color: var(--text-faint); cursor: pointer; white-space: nowrap;
}
.dl-promote:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
.dl-promote.done { background: var(--ok-soft); color: var(--ok); border-color: transparent; }
.dl-promote:disabled { cursor: default; }
.dl-open-head { margin-top: 10px; color: var(--warn); }
.dl-open li { margin: 6px 0; }
.dl-open-why { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
.peer-process {
  background: var(--bg-3); border: 1px solid var(--border); border-left: 3px solid var(--ok);
  border-radius: 10px; padding: 8px 12px; margin-bottom: 6px; max-width: 100%;
  max-height: 260px; overflow-y: auto;
}
.peer-process-title {
  font-size: 11px; text-transform: uppercase; letter-spacing: .4px;
  color: var(--text-faint); font-weight: 700; margin-bottom: 5px;
}
.proc-step { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; margin: 2px 0; }
.proc-step.tool code { color: var(--accent-2); font-family: var(--mono); font-size: 11.5px; }

/* Collapsible "thinking" box (click header to expand) */
.proc-box { border: 1px solid var(--border); border-radius: 10px; background: var(--bg-3); margin-bottom: 6px; max-width: 100%; overflow: hidden; }
.proc-box.live { border-color: rgba(84,201,138,.5); }
.proc-head {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; cursor: pointer;
  background: none; border: none; color: var(--text-dim); padding: 8px 12px; font-size: 12.5px; font-weight: 600;
}
.proc-head:hover { background: var(--bg-hover); }
.proc-caret { transition: transform .15s; font-size: 10px; color: var(--text-faint); }
.proc-box:not(.collapsed) .proc-caret { transform: rotate(90deg); }
.proc-name { flex: 1; }
.proc-status { font-size: 11px; color: var(--text-faint); font-weight: 500; }
.proc-live { width: 8px; height: 8px; border-radius: 50%; background: transparent; flex-shrink: 0; }
.proc-box.live .proc-live { background: var(--ok); box-shadow: 0 0 7px var(--ok); animation: cn-pulse 1.1s ease-in-out infinite; }
.proc-body { max-height: 300px; overflow-y: auto; padding: 4px 12px 10px; border-top: 1px solid var(--border); }
.proc-box.collapsed .proc-body { display: none; }
.consult-thinking {
  max-width: 780px; margin: 0 auto; padding: 4px 22px 8px;
  display: flex; align-items: center; gap: 10px; color: var(--text-dim); font-size: 13.5px;
}
.dot-pulse {
  width: 9px; height: 9px; border-radius: 50%; background: var(--ok); flex-shrink: 0;
  animation: cn-pulse 1s ease-in-out infinite;
}
@keyframes cn-pulse { 0%,100% { opacity: .3; transform: scale(.8); } 50% { opacity: 1; transform: scale(1.1); } }
/* Live "the model is composing a tool call" heartbeat (writing code…). */
.tool-progress {
  display: flex; align-items: center; gap: 8px;
  margin: 6px 0 2px; font-size: 13px; color: var(--text-dim);
}
.tool-progress .tp-label { opacity: .9; }
.msg-content p { margin: 0 0 10px; }
.msg-content p:last-child { margin-bottom: 0; }
.msg-content ul, .msg-content ol { margin: 4px 0 10px; padding-left: 22px; }
.msg-content li { margin: 3px 0; }
.msg-content h1, .msg-content h2, .msg-content h3 { margin: 14px 0 8px; line-height: 1.3; }
.msg-content h1 { font-size: 1.35em; }
.msg-content h2 { font-size: 1.2em; }
.msg-content h3 { font-size: 1.08em; }
.msg-content a { color: var(--accent-2); }
.msg-content code {
  font-family: var(--mono); font-size: .88em;
  background: var(--bg-3); padding: 2px 5px; border-radius: 5px;
}
.msg-content pre {
  background: #0b0c10; border: 1px solid var(--border);
  border-radius: 10px; padding: 13px 15px; overflow-x: auto; margin: 8px 0 12px;
}
.msg-content pre code { background: none; padding: 0; font-size: .86em; line-height: 1.5; }
.msg-content blockquote {
  border-left: 3px solid var(--accent); margin: 8px 0; padding: 2px 14px; color: var(--text-dim);
}
.cursor::after { content: "▋"; color: var(--accent); animation: blink 1s steps(2) infinite; }
@keyframes blink { 50% { opacity: 0; } }

/* tool call chips */
.tool-chip {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--bg-3); border: 1px solid var(--border);
  border-radius: 9px; padding: 7px 11px; margin: 6px 0; font-size: 12.5px;
  color: var(--text-dim); cursor: pointer; max-width: 100%;
}
.tool-chip:hover { background: var(--bg-hover); }
.tool-chip .tool-icon { font-size: 14px; }
.tool-chip code { font-family: var(--mono); color: var(--accent-2); }
.tool-detail {
  font-family: var(--mono); font-size: 12px; color: var(--text-dim);
  background: #0b0c10; border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 11px; margin: 2px 0 8px; white-space: pre-wrap; word-break: break-word;
}
.msg-error {
  background: rgba(240,96,106,.1); border: 1px solid rgba(240,96,106,.35);
  color: #ffb3b8; border-radius: 9px; padding: 10px 13px; font-size: 13.5px; margin-top: 4px;
}

/* empty state */
.empty-state { max-width: 560px; margin: 8vh auto 0; text-align: center; padding: 0 22px; }
.empty-logo { font-size: 52px; margin-bottom: 6px; }
.empty-state h1 { font-size: 26px; margin: 0 0 10px; }
.empty-state p { color: var(--text-dim); }
.suggestions { display: flex; flex-wrap: wrap; gap: 9px; justify-content: center; margin-top: 22px; }
.chip-suggest {
  background: var(--bg-2); border: 1px solid var(--border); color: var(--text-dim);
  border-radius: 20px; padding: 9px 15px; font-size: 13px;
}
.chip-suggest:hover { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
.link-btn { background: none; border: none; color: var(--accent-2); text-decoration: underline; font-size: inherit; padding: 0; }

/* ---- Composer ---- */
#composer { flex-shrink: 0; padding: 12px 22px 16px; border-top: 1px solid var(--border); }
.composer-inner {
  max-width: 780px; margin: 0 auto; display: flex; flex-direction: column; gap: 6px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 18px; padding: 10px 12px 10px 14px;
}
.composer-inner:focus-within { border-color: var(--accent); }
#input {
  width: 100%; resize: none; border: none; background: none; color: var(--text);
  font-family: inherit; font-size: 15px; line-height: 1.5; max-height: 200px; outline: none; padding: 4px 0 2px;
}
/* @mention autocomplete — pops above the composer box */
.composer-inner { position: relative; }
.mention-menu {
  position: absolute; bottom: 100%; left: 0; right: 0; margin-bottom: 8px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow-2); padding: 4px; max-height: 240px; overflow-y: auto; z-index: 40;
}
.mention-menu.hidden { display: none; }
.mention-item {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 8px;
  cursor: pointer; font-size: 13.5px; color: var(--text);
}
.mention-item.active, .mention-item:hover { background: var(--bg-hover); }
/* Same vocabulary as .cn-presence on the Members page: fill = a person is there,
   colour = whether we know. Keeping the surfaces in step matters more than the
   1px size difference — see the block above .cn-presence for why. */
.mention-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; box-sizing: border-box; }
.mention-dot.active { background: var(--ok); }
.mention-dot.idle { background: var(--warn); }
.mention-dot.away { background: transparent; border: 1.5px solid var(--ok); }
.mention-dot.online { background: transparent; border: 1.5px solid var(--ok); }
.mention-dot.unknown { background: transparent; border: 1.5px dashed var(--text-faint); opacity: .8; }
.mention-dot.offline { background: var(--text-faint); }
.mention-dot.local { background: var(--accent); }
.mention-name { font-weight: 600; }
.mention-hint { margin-left: auto; font-size: 11px; color: var(--text-faint); }

.composer-controls { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.composer-controls-right { display: flex; align-items: center; gap: 8px; }
/* The two round icon buttons at the end of the composer. One selector, because they sit
   side by side and a size that drifts between them is visible at a glance. Without the
   svg rule the button paints as an empty pill: `.drop-btn` sizes text, and an inline svg
   with only a viewBox has no intrinsic size to inherit. */
#attach-btn, #emoji-btn {
  width: 32px; height: 32px; padding: 0; border-radius: 50%; flex-shrink: 0;
  font-size: 15px; line-height: 1; justify-content: center;
}
#attach-btn svg, #emoji-btn svg { width: 16px; height: 16px; display: block; }
#composer.drag .composer-inner { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* File-attachment chips — pending row in the composer + chips on a sent message. */
.attach-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.msg-attachments { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.attach-chip {
  display: inline-flex; align-items: center; gap: 6px; max-width: 260px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 999px;
  padding: 3px 10px; font-size: 12.5px; color: var(--text-dim);
}
.attach-chip .attach-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attach-chip .attach-ico { flex-shrink: 0; }
.attach-x {
  border: none; background: none; color: var(--text-faint); cursor: pointer;
  font-size: 12px; padding: 0 0 0 2px; line-height: 1;
}
.attach-x:hover { color: var(--danger); }
.attach-eye {
  border: none; background: none; color: var(--text-faint); cursor: pointer;
  font-size: 12px; padding: 0 0 0 2px; line-height: 1;
}
.attach-eye:hover { color: var(--accent); }
/* An image chip shows the image. The thumbnail replaces the 🖼️ placeholder rather than
   sitting beside it — a chip is 26px tall and two icons in it is a picture of nothing.
   Squared off with object-fit so a wide screenshot and a tall one are the same chip. */
.attach-thumb {
  width: 20px; height: 20px; flex-shrink: 0; border-radius: 4px;
  object-fit: cover; background: var(--bg-2);
}
/* "+20" on a chip that stands for a file AND what came out of it — a PDF's pages, a
   zip's photographs. Stated rather than silently folded, so nobody wonders where their
   pages went. */
.attach-count {
  flex-shrink: 0; font-size: 11px; font-variant-numeric: tabular-nums;
  color: var(--text-faint); background: var(--bg-2); border-radius: 999px;
  padding: 0 6px; line-height: 16px;
}
.attach-chip.openable { cursor: pointer; }
.attach-chip.openable:hover { border-color: var(--accent); color: var(--text); }
/* A file still going up: the same chip, with a fill along its bottom edge. A big PDF or
   a folder of photographs is a minute of waiting, and with no bar at all that minute is
   indistinguishable from a broken button — which is exactly how it was reported. */
.attach-chip.uploading { position: relative; overflow: hidden; padding-bottom: 5px; }
.attach-chip.uploading .attach-name { opacity: .8; }
.attach-pct {
  flex-shrink: 0; font-size: 11px; font-variant-numeric: tabular-nums;
  color: var(--text-faint); min-width: 30px; text-align: right;
}
.attach-bar {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--bg-2);
}
.attach-bar > i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .15s linear; }
/* Once the bytes are up, the server is extracting text and has no percentage to report.
   Freezing a full bar at 100% reads as stuck, so that phase animates instead. */
.attach-chip.uploading.indet .attach-bar > i {
  width: 40% !important; transition: none; animation: attach-indet 1.1s ease-in-out infinite;
}
@keyframes attach-indet { 0% { margin-left: -40%; } 100% { margin-left: 100%; } }
.btn-send, .btn-stop {
  width: 32px; height: 32px; border-radius: 50%; border: none; flex-shrink: 0;
  font-size: 15px; display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.btn-send { background: var(--accent); color: var(--on-accent); }
.btn-send:hover { background: var(--accent-2); }
.btn-send:disabled { background: var(--bg-3); color: var(--text-faint); cursor: default; }
.btn-stop { background: var(--bg-3); color: var(--text); border: 1px solid var(--border); }
.btn-stop:hover { background: var(--bg-hover); }
.composer-hint { text-align: center; color: var(--text-faint); font-size: 11.5px; margin-top: 8px; }

/* ---- composer controls (mode + model dropdowns) ---- */
.composer-bar { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; padding: 0 2px; }
.composer-drop { position: relative; }
.drop-btn {
  display: inline-flex; align-items: center; gap: 6px; background: var(--bg-2);
  border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px;
  color: var(--text-dim); font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.drop-btn:hover { border-color: var(--accent); color: var(--text); }
.drop-btn .caret { font-size: 10px; opacity: .7; }
.drop-menu {
  position: absolute; bottom: calc(100% + 8px); left: 0; min-width: 240px; max-height: 320px; overflow-y: auto;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 6px;
  box-shadow: 0 12px 40px rgba(0,0,0,.5); z-index: 50;
}
.drop-menu.right { left: auto; right: 0; }
.drop-item {
  display: flex; flex-direction: column; gap: 1px; width: 100%; text-align: left;
  background: none; border: none; border-radius: 8px; padding: 8px 10px; cursor: pointer; color: var(--text);
}
.drop-item b { font-size: 13px; font-weight: 600; }
.drop-item span { font-size: 11.5px; color: var(--text-faint); }
.drop-item:hover { background: var(--bg-hover); }
.drop-item.active { background: var(--accent-soft); }
.drop-item.active b { color: var(--accent-2); }

/* Emoji picker. It borrows `.drop-menu` for the shell (same card, same upward open,
   same close-on-outside-click) and overrides only what a grid needs. The base menu
   scrolls itself; here the SEARCH must stay put while the list moves under it, so the
   outer scroll is turned off and given to `.emoji-list` instead. */
.emoji-menu {
  width: 328px; min-width: 0; max-height: 340px; overflow: hidden;
  display: flex; flex-direction: column; padding: 8px;
}
.emoji-search { flex: 0 0 auto; padding-bottom: 6px; }
.emoji-search input {
  width: 100%; box-sizing: border-box;
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 7px 10px; font-size: 13px; outline: none;
}
.emoji-search input:focus { border-color: var(--accent); }
.emoji-list { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; }
.emoji-sec + .emoji-sec { margin-top: 8px; }
.emoji-sec-h {
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-faint); padding: 2px 2px 5px;
  position: sticky; top: 0; background: var(--bg-2); z-index: 1;
}
.emoji-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px; }
.emoji-cell {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1; background: none; border: none; border-radius: 8px;
  padding: 0; cursor: pointer; line-height: 1; font-size: 19px;
  /* Name the emoji fonts explicitly: --font leads with the UI stack, and on the
     platforms where that resolves to a monochrome face first, half the grid renders as
     flat outlines while the other half is in colour. */
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", var(--font);
}
.emoji-cell:hover { background: var(--bg-hover); }
.emoji-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.emoji-empty { color: var(--text-faint); font-size: 12.5px; margin: 6px 2px; }
.drop-manage { border-top: 1px solid var(--border); margin-top: 4px; border-radius: 0 0 8px 8px; color: var(--text-dim); font-size: 12.5px; flex-direction: row; }
.drop-empty { padding: 12px; font-size: 12.5px; color: var(--text-faint); text-align: center; }
/* Scope reminder under the model picker: the choice pins THIS chat, not the node. */
.drop-note { padding: 6px 10px 2px; font-size: 11px; color: var(--text-faint); }

/* ---- Settings modal ---- */
.overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.55);
  display: flex; align-items: center; justify-content: center; z-index: 50; padding: 20px;
}
.modal {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 16px;
  width: 100%; max-width: 480px; max-height: 90vh; display: flex; flex-direction: column;
}
.modal-head { display: flex; align-items: center; justify-content: space-between; padding: 18px 20px; border-bottom: 1px solid var(--border); }
.modal-head h2 { margin: 0; font-size: 17px; }
.icon-btn { background: none; border: none; color: var(--text-dim); font-size: 17px; }
.icon-btn:hover { color: var(--text); }
.modal-body { padding: 18px 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field > span { font-size: 13px; color: var(--text-dim); font-weight: 600; }
.field em { font-style: normal; color: var(--text-faint); font-weight: 400; }
.field input[type=text], .field input[type=password], .field input:not([type=range]), .field select, .field textarea {
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 9px; padding: 10px 12px; font-size: 14px; font-family: inherit; width: 100%; outline: none;
}
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--accent); }
.field input[type=range] { width: 100%; accent-color: var(--accent); }
.model-row { display: flex; gap: 8px; }
.model-row input { flex: 1; }
/* QuorieAI tier picker — stands in for the model-id input on managed nodes. */
.model-row .tier-select { flex: 1; min-width: 0; }
.btn-mini {
  background: var(--bg-3); border: 1px solid var(--border); color: var(--text);
  border-radius: 9px; padding: 0 14px; font-size: 13px; white-space: nowrap;
}
.btn-mini:hover { background: var(--bg-hover); }
/* The channel picker's "re-ask Teams" button. Sits under the <select> rather than beside
   it: the select is full-width and a channel label is long, so an inline button would
   squeeze the one control the field exists for. */
.skp-chan-refresh { margin-top: 6px; font-size: 12px; }
.skp-chan-refresh[disabled] { opacity: .6; cursor: default; }
/* Where a skill's settings actually live. Sits under the fields, above the schedule row,
   because that is the order somebody reads it in. */
.skp-save-note { margin-top: 10px; }
/* Searching 357 channels across 22 teams. The box sits ABOVE the dropdown it filters,
   because that is the order the eye needs them in; the count sits under it so a search
   that matches nothing says so rather than looking broken. */
.skp-chan-search { margin-bottom: 6px; }
.skp-chan-count { display: block; margin-top: 4px; }
/* The open-findings count on the Findings tab. A muted pill rather than a badge: it is
   how much work is waiting, not an alert — the tab is only there at all when there is
   something in it. */
.skill-tab .tab-count:not(:empty) {
  display: inline-block; margin-left: 6px; padding: 0 6px;
  border-radius: 999px; font-size: 11px; line-height: 17px;
  background: var(--bg-hover); color: var(--text-dim);
}
.skill-tab.active .tab-count:not(:empty) { color: var(--text); }
.hint { color: var(--text-faint); font-size: 12px; }
.hint.ok { color: var(--ok); }
.hint.bad { color: var(--danger); }
/* The image-model field carries an inline Test button — proving a model can see is
   the only way to tell a working one from one that ignores images and answers anyway. */
.vision-row { display: flex; gap: 8px; align-items: center; }
.vision-row input, .vision-row select { flex: 1; min-width: 0; }
.vision-row + input { margin-top: 8px; }
#vision-key-row { margin-top: 8px; }
.key-set { color: var(--ok); }
.key-unset { color: var(--text-faint); }
.privacy-note { font-size: 12px; color: var(--text-faint); background: var(--bg); border: 1px solid var(--border); border-radius: 9px; padding: 10px 12px; margin: 0; }
.privacy-note code { font-family: var(--mono); color: var(--text-dim); }
.modal-foot { padding: 14px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; }
/* ---- appDialog(): the in-app replacement for confirm()/prompt() ---- */
.dialog-modal { max-width: 460px; }
.dialog-body { font-size: 13.5px; line-height: 1.5; color: var(--text-dim); }
.dialog-body strong { color: var(--text); font-weight: 600; }
/* What is actually about to happen — the thing a browser confirm() cannot show. */
.dialog-target { margin-top: 10px; background: var(--bg); border: 1px solid var(--border);
  border-radius: 9px; padding: 9px 11px; font-family: var(--mono); font-size: 11.5px;
  color: var(--text-dim); word-break: break-all; }
.dialog-hint { font-size: 11.5px; color: var(--text-faint); }
.dialog-foot { gap: 8px; }
/* `appDialog({danger:true})` has always set this class; nothing ever styled it, so a
   "Delete" and an "OK" looked identical. Every irreversible action passes it. */
#dialog-ok.danger { background: var(--danger); border-color: var(--danger); color: #fff; }
#dialog-ok.danger:hover { filter: brightness(1.08); }
/* The permissions an extension asks for come through as a list. */
.dialog-body ul { margin: 8px 0 0; padding-left: 20px; }
.dialog-body li { margin: 3px 0; }
#dialog-input { resize: vertical; min-height: 66px; }
/* A destructive/outward confirm is visually distinct from an ordinary OK. */
.btn-primary.danger { background: var(--danger, #e03131); }
.btn-primary.danger:hover { background: color-mix(in srgb, var(--danger, #e03131) 85%, #000); }
.btn-primary { background: var(--accent); color: var(--on-accent); border: none; border-radius: 10px; padding: 10px 20px; font-size: 14px; font-weight: 600; }
.btn-primary:hover { background: var(--accent-2); }

/* ---- Marketplace ---- */
.modal-lg { max-width: 760px; }
/* The tools picker. One row per tool: what it is, then the two things you can do with
   it — keep it on the sidebar, or just go there now. */
.tools-grid { display: flex; flex-direction: column; gap: 8px; }
.tool-card {
  display: flex; align-items: center; gap: 12px; padding: 11px 13px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--bg);
}
.tool-card-main { flex: 1; min-width: 0; }
.tool-card-name { font-weight: 600; font-size: 14px; }
.tool-card-blurb { color: var(--text-dim); font-size: 12.5px; margin-top: 2px; }
/* A tool the workspace does not have is SHOWN and disabled, not hidden: "why can I not
   find Mind maps" is a question the picker should answer, and an absent row answers it
   with silence. */
.tool-card.tool-off { opacity: .62; }
.tool-chip {
  font-size: 10.5px; font-weight: 700; letter-spacing: .03em; padding: 1px 6px;
  border-radius: 5px; background: var(--bg-3); color: var(--text-faint); vertical-align: 2px;
}
.tool-pin, .tool-open {
  flex: none; padding: 6px 12px; border-radius: 9px; font: inherit; font-size: 12.5px;
  font-weight: 600; cursor: pointer; border: 1px solid var(--border);
  background: var(--bg-2); color: var(--text-dim);
}
.tool-pin:hover:not(:disabled), .tool-open:hover:not(:disabled) { color: var(--text); border-color: var(--line-2, var(--border)); }
.tool-pin.on { background: var(--accent-soft); border-color: transparent; color: var(--accent); }
.tool-pin:disabled, .tool-open:disabled { opacity: .45; cursor: not-allowed; }
/* "Show all tools" is a way OUT of the sidebar, not another destination in it — so it
   reads quieter than the tools above it and never takes the active state. */
.side-item-quiet { color: var(--text-faint); }
.side-item-quiet:hover { color: var(--text-dim); }
.modal-sub { color: var(--text-faint); font-size: 12px; display: block; margin-top: 3px; }
.market-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; }
/* Tools tab — first-class tool catalog grouped by connector / core compute */
#tools-grid { display: flex; flex-direction: column; gap: 12px; }

/* Heartbeat view — the system's rhythm (scheduled skills + proactive beat) */
.hb-section { margin-bottom: 24px; }
.hb-section-title { font-size: 12px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; }
.hb-list { display: flex; flex-direction: column; }
.hb-row { display: flex; align-items: center; gap: 12px; padding: 12px 4px; border-top: 1px solid var(--border); }
.hb-list .hb-row:first-child, .hb-section > .hb-row { border-top: none; }
.hb-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.hb-row-name { font-weight: 600; font-size: 14px; }
.hb-row-sub { font-size: 12px; color: var(--text-dim); }
.hb-row-sub.on { color: var(--accent); }
/* Heartbeat is a GRID OF CARDS, reusing the `market-card` surface the Marketplace and
   Skills grids already use. It was the only list in the app that read like a settings
   table, which is what made the page feel like a different product. `hb-card` changes
   nothing but the click affordance: a Heartbeat card is not a link — its controls are,
   so it must not take the pointer cursor or the hover-accent border a tile gets. */
.hb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 14px; }
.market-card.hb-card { cursor: default; }
.market-card.hb-card:hover { border-color: var(--border); box-shadow: var(--shadow-1); }
.hb-card-id { min-width: 0; }        /* let a long skill name ellipsize, not stretch the card */
.hb-card .market-card-name { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* The controls sit on one line and stay put as the card grows. */
.hb-card-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; width: 100%; }
.hb-card-actions .hb-select { flex: 1; min-width: 120px; }
/* Cadence reads as the card's status line — accented only when it is actually armed. */
.hb-when { font-size: 11.5px; color: var(--text-faint); }
.hb-when.on { color: var(--accent); }

/* Ownership: who a skill is FOR, and who answers for it. Described, not enforced —
   see docs/SHARED_SKILLS_PLAN.md. */
.hb-note { font-size: 12px; color: var(--text-dim); line-height: 1.5; margin: 0 0 10px; max-width: 62ch; }
.hb-chip { display: inline-block; font-size: 10.5px; font-weight: 600; letter-spacing: 0.03em;
  padding: 1px 7px; border-radius: 999px; margin-left: 6px; vertical-align: 1px;
  border: 1px solid var(--border); color: var(--text-dim); background: var(--bg-2); }
.hb-chip.scope-company { border-color: var(--accent); color: var(--accent); }
.hb-chip.scope-team    { border-color: var(--accent-2); color: var(--accent-2); }
.hb-owner { display: block; font-size: 11.5px; color: var(--text-faint); margin-top: 5px; }
.hb-owner.you  { color: var(--accent); }
/* Shared with nobody answerable is the state worth noticing, not a neutral fact. */
.hb-owner.none { color: var(--warn); }
.hb-toggle { border: 1px solid var(--border); background: var(--bg-3); color: var(--text-dim); border-radius: 999px; padding: 3px 13px; font-size: 12px; font-weight: 600; cursor: pointer; }
.hb-toggle.on { background: var(--accent-soft); color: var(--accent); border-color: transparent; }
.hb-select { background: var(--bg-3); border: 1px solid var(--border); border-radius: 8px; color: var(--text); padding: 5px 8px; font-size: 13px; }

/* Heartbeat borrows Company → Skills' card language wholesale (`skl-*`), so the only
   rules here are the ones that page has no equivalent for: a full-width field, a run's
   report panel, and the card that owns the open panel. */
.hb-section .skl-field.wide { flex: 1 1 100%; }
.hb-textarea { font-family: inherit; line-height: 1.45; resize: vertical; min-height: 46px; }
/* The panel spans the whole grid row rather than living inside its 330px card, because a
   report is long-form reading. Its card is marked `expanded` so the two read as one
   block instead of a card and an unrelated slab under it. */
.skl-card.expanded { border-color: var(--accent); border-left-color: var(--accent); }
.hb-panel { grid-column: 1 / -1; border: 1px solid var(--accent); border-radius: 12px;
  background: var(--bg-2); padding: 14px 16px; margin-top: -4px; }
/* Run now and On/Off are actions, not settings — they sit on the fields' baseline
   rather than above one, and keep their own width instead of stretching to a column. */
.skl-card-controls > .btn-mini,
.skl-card-controls > .hb-toggle { align-self: flex-end; flex: 0 0 auto; }
.hb-panel-head { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 13.5px; margin-bottom: 10px; }
.hb-panel-n { font-size: 11.5px; font-weight: 500; color: var(--text-faint); }
.hb-panel-head .btn-mini { margin-left: auto; }
/* History beside the report — the same shape a skill page uses, so a person who has read
   one report knows how to read them all. It stacks on a narrow screen. */
.hb-panel-body { display: grid; grid-template-columns: minmax(180px, 240px) 1fr; gap: 16px; }
.hb-runs { display: flex; flex-direction: column; gap: 2px; max-height: 340px; overflow-y: auto; }
.hb-report { max-height: 420px; overflow-y: auto; font-size: 13.5px; line-height: 1.6; }
.hb-report > :first-child { margin-top: 0; }
@media (max-width: 780px) { .hb-panel-body { grid-template-columns: 1fr; } }

/* Lean schedule popover (from Heartbeat) */
.sched-modal-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 20px; }
.sched-modal { background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px; width: 100%; max-width: 430px; padding: 20px; display: flex; flex-direction: column; gap: 14px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4); }
.sched-modal-head { display: flex; align-items: center; }
.sched-modal-head h2 { margin: 0; font-size: 16px; }
.sched-modal-x { margin-left: auto; background: none; border: none; color: var(--text-faint); font-size: 22px; line-height: 1; cursor: pointer; }
.sched-modal-x:hover { color: var(--text); }
.sched-modal-row { display: flex; gap: 8px; flex-wrap: wrap; }
.sched-modal-foot { display: flex; justify-content: flex-end; gap: 8px; }

/* Collapsible "About this skill" — keeps a long description out of the header */
.skill-about { margin-bottom: 14px; border: 1px solid var(--border); border-radius: 10px; padding: 0 14px; }
.skill-about summary { cursor: pointer; padding: 10px 0; font-size: 13px; font-weight: 600; color: var(--text-dim); list-style: none; }
.skill-about summary::-webkit-details-marker { display: none; }
.skill-about summary::before { content: "▸  "; }
.skill-about[open] summary::before { content: "▾  "; }
.skill-about p { margin: 0 0 12px; font-size: 13px; line-height: 1.6; color: var(--text-dim); white-space: pre-wrap; }

/* Access governance matrix (admin-only) — per-user plugin + skill grants */
#org-panel-access .access-head { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; }
.access-switch { display: flex; align-items: center; gap: 8px; font-weight: 600; cursor: pointer; }
.access-matrix { display: flex; flex-direction: column; gap: 14px; }
.access-member { border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; background: var(--bg-2); }
.access-member.is-admin { opacity: 0.75; }
.access-member-head { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.access-role { font-size: 12px; color: var(--text-dim); text-transform: capitalize; }
.access-member-head .access-save { margin-left: auto; }
.access-group { margin-top: 12px; }
.access-group-title { display: flex; align-items: center; gap: 10px; font-size: 13px; font-weight: 600; margin-bottom: 8px; }
.access-all { margin-left: auto; font-weight: 400; font-size: 12px; color: var(--text-dim); display: flex; align-items: center; gap: 5px; cursor: pointer; }
.access-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.access-chip { display: flex; align-items: center; gap: 6px; padding: 5px 11px; border: 1px solid var(--border); border-radius: 999px; font-size: 12px; cursor: pointer; user-select: none; }
.access-chip:hover { background: var(--bg-hover); }
.access-chip.core { opacity: 0.7; cursor: default; }
.access-core { font-size: 10px; color: var(--text-faint); border: 1px solid var(--border); border-radius: 4px; padding: 0 4px; }

/* Home: AI output (Router) cards — one card per type, listing its items as rows */
.home-router-toggle { grid-column: 1 / -1; padding: 9px 14px; border: 1px dashed var(--border); border-radius: 10px; }
.home-router-toggle .access-switch { font-weight: 500; font-size: 13px; color: var(--text-dim); }
.home-card-count { font-size: 12px; font-weight: 600; color: var(--text-dim); background: var(--bg-3); border-radius: 999px; padding: 1px 8px; margin-left: 4px; }
/* Two across, not a single narrow column: this feed is the busiest thing on Home and
   had the narrowest shape in a 1300px window. Each row is its own card, so the divider
   borders that made a list became one border that makes a tile. */
.home-cardlist {
  display: grid; gap: 10px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.home-cardlist .home-empty { grid-column: 1 / -1; }
.home-cardrow {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-2);
}
.home-cardrow.clickable { cursor: pointer; }
.home-cardrow.clickable:hover { background: var(--bg-hover); border-color: var(--accent); }
.home-cardrow-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.home-cardrow-title { font-weight: 600; font-size: 14px; }
.home-cardrow-preview { font-size: 13px; color: var(--text-dim); line-height: 1.4; }
.home-cardrow-x { background: none; border: none; color: var(--text-faint); font-size: 16px; line-height: 1; cursor: pointer; padding: 2px 4px; flex-shrink: 0; }
.home-cardrow-x:hover { color: var(--text); }
.tool-group { border: 1px solid var(--border); border-radius: 12px; background: var(--bg-2); overflow: hidden; }
.tool-group-head { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--border); }
.tool-group-name { font-weight: 700; font-size: 14px; }
.tool-group-count { margin-left: auto; font-size: 12px; color: var(--text-faint); flex-shrink: 0; }
.tool-badge { font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 999px; flex-shrink: 0; }
.tool-badge.core { background: var(--accent-soft); color: var(--accent-2); }
.tool-badge.on { background: var(--ok-soft); color: var(--ok); }
.tool-badge.off { background: var(--bg-3); color: var(--text-faint); }
.tool-row { display: flex; align-items: flex-start; gap: 12px; padding: 9px 14px; border-top: 1px solid var(--border); }
.tool-row:first-child { border-top: none; }
.tool-row-main { flex: 1; min-width: 0; }
.tool-name { font-family: var(--mono); font-size: 12.5px; color: var(--accent-2); background: none; border: none; padding: 0; }
.tool-desc { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
.tool-needs { flex-shrink: 0; font-size: 11px; color: var(--text-faint); white-space: nowrap; }
.tool-needs.core { color: var(--ok); }
.market-controls { display: flex; gap: 10px; margin-bottom: 14px; max-width: 660px; }
.market-search { flex: 1; background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 9px 12px; font-size: 14px; font-family: inherit; outline: none; }
.market-search:focus { border-color: var(--accent); }
.market-cat { background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 9px 12px; font-size: 14px; font-family: inherit; outline: none; }
.market-cat:focus { border-color: var(--accent); }
.market-card {
  background: var(--bg); border: 1px solid var(--border); border-radius: 12px;
  padding: 15px; display: flex; flex-direction: column; gap: 10px; cursor: pointer; transition: border-color .15s;
}
.market-card:hover { border-color: var(--accent); }
.market-card-top { display: flex; gap: 12px; align-items: flex-start; }
.market-card-icon {
  font-size: 24px; width: 44px; height: 44px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-3); border-radius: 11px;
}
.market-card-name { font-weight: 600; font-size: 15px; }
.market-card-cat { font-size: 11.5px; color: var(--text-faint); }
.market-card-sum { font-size: 13px; color: var(--text-dim); flex: 1; }
.ext-bundle { font-size: 11.5px; color: var(--text-faint); margin: -2px 0 2px; }
.ext-price { font-size: 12px; font-weight: 600; color: var(--text-dim); }
.ext-price.free { color: #6fce8f; }
.market-card-foot { display: flex; align-items: center; justify-content: space-between; }
/* Role editor: what the ticked SKILLS already connect to. Read-only — ticking these
   below would save a derived id as explicit and the two would drift. */
.cn-role-derived:not(:empty) {
  margin: -4px 0 10px; padding: 8px 10px; border-radius: var(--radius);
  background: var(--bg-2); border: 1px solid var(--border);
}
.cn-role-derived p { margin: 0; }

/* Addon policy: what allowing these skills ALSO permits. Approval without disclosure
   is not consent, so this sits next to the list being edited, not in a doc. */
.pol-implied {
  margin: 10px 0 4px; padding: 10px 12px; border-radius: var(--radius); font-size: 12.5px;
  line-height: 1.55; background: var(--bg-2); border: 1px solid var(--border);
}
.pol-imp-head { color: var(--text); margin-bottom: 6px; }
.pol-imp-row { color: var(--text-dim); padding: 2px 0; }
.pol-imp-row code { font-size: 11.5px; color: var(--text-faint); }
.pol-imp-foot { margin-top: 8px; color: var(--text-faint); font-size: 11.5px; }

/* Connections page: what depends on this connection. The reason the page exists — a
   connector that has stopped working otherwise says nothing about what stopped with it. */
.conn-usedby { font-size: 12.5px; color: var(--text-dim); flex: 1; line-height: 1.5; }
.conn-usedby.muted { color: var(--text-faint); }
.conn-advanced { margin-top: 22px; border-top: 1px solid var(--border); padding-top: 14px; }
.conn-advanced > summary {
  cursor: pointer; font-size: 13px; color: var(--text-dim); user-select: none; padding: 4px 0;
}
.conn-advanced > summary:hover { color: var(--text); }
.conn-advanced .hint { margin: 6px 0 12px; }
.cap-status {
  font-size: 11.5px; padding: 3px 9px; border-radius: 20px; border: 1px solid var(--border); color: var(--text-faint);
}
.cap-status.linked { color: var(--ok); border-color: rgba(84,201,138,.4); background: rgba(84,201,138,.1); }
/* Connected, but the member's role doesn't include it — the AI gets no tools for it. */
.cap-status.blocked {
  color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent);
  border-color: color-mix(in srgb, var(--warn) 40%, transparent);
}
.cap-role-warn {
  margin: 10px 0 4px; padding: 10px 12px; border-radius: var(--radius); font-size: 12.5px;
  line-height: 1.55; color: var(--text);
  background: color-mix(in srgb, var(--warn) 10%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--warn) 38%, var(--border));
}
.cap-status.installed { color: var(--accent-2); border-color: rgba(154,140,255,.4); background: var(--accent-soft); }
.cap-status.disabled { color: #e0a458; border-color: rgba(224,164,88,.4); background: rgba(224,164,88,.1); }
.cap-status.warn { color: #e0a458; border-color: rgba(224,164,88,.4); background: rgba(224,164,88,.1); }
.market-card-action { font-size: 12.5px; color: var(--accent-2); font-weight: 600; cursor: pointer; }
.market-card-action:hover { text-decoration: underline; }

/* Roles tab: admin staffing (AI teammate / person) */
.role-seats { font-size: 12px; color: var(--text-faint); margin: 0 0 12px; }
.role-skills { font-size: 11.5px; color: var(--text-faint); min-height: 1em; }
.role-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.role-actions .btn-primary { padding: 7px 14px; font-size: 12.5px; }
.role-actions .btn-mini { padding: 6px 12px; font-size: 12.5px; height: auto; line-height: 1.9; }
.role-actions .recruit-btn { margin-left: auto; }
.role-note { font-size: 12.5px; color: var(--text-dim); display: flex; align-items: center; gap: 8px; }
.role-note.err { color: var(--danger); }
.role-spin {
  width: 14px; height: 14px; flex-shrink: 0; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: role-spin .7s linear infinite;
}
@keyframes role-spin { to { transform: rotate(360deg); } }
.role-person-form { display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--border); padding-top: 10px; }
.role-person-form .cn-input { margin: 0; }
.role-person-form .cn-label { margin-top: 2px; }
.role-result { margin-top: 4px; }

/* capability detail modal */
.cap-head { display: flex; gap: 14px; align-items: center; }
.cap-head-icon { display: flex; flex-shrink: 0; }
.cap-head-tile { --ico-sz: 52px; }

/* ---- the detail-page head, shared by #view-capability and #view-skill ----
   A page, not a dialog: back, then the thing at full size, what it can do, and — for a
   connection, only once it is installed — where you set it up. Both pages use it so that
   arriving from either list lands on the same shape. */
.lib-back {
  display: inline-flex; align-items: center; gap: 6px; margin-bottom: 14px;
  padding: 6px 12px 6px 8px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--bg-2); color: var(--text-dim); font: inherit; font-size: 13px;
}
.lib-back:hover { background: var(--bg-hover); color: var(--text); }
.lib-back svg { width: 15px; height: 15px; }

/* Top-aligned, not centred: the description runs to three lines on some skills, and a
   centred tile then floats halfway down the paragraph instead of sitting by the name. */
.cap-page-head { display: flex; align-items: flex-start; gap: 16px; padding-bottom: 16px; }
.cap-page-head .cap-head-icon { margin-top: 2px; }
.cap-page-head .cap-head-act { align-self: center; }
.cap-head-main { min-width: 0; }
.cap-head-main h1 { margin: 0; font-size: 26px; font-weight: 600; letter-spacing: -0.01em; }
.cap-sub { display: block; margin-top: 4px; font-size: 12.5px; color: var(--text-faint); }
.cap-head-act { margin-left: auto; display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.cap-state { display: inline-flex; align-items: center; }
/* The skill page's one control while the skill is not yours yet. */
.skill-head-act { margin-left: auto; display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.skill-head-act .btn-primary { padding: 9px 22px; font-size: 14px; border-radius: 999px; }
.cap-head-act .btn-primary { padding: 9px 22px; font-size: 14px; border-radius: 999px; }

#view-capability .cap-desc { max-width: 70ch; margin: 0 0 22px; }
#view-capability .cap-section { margin-bottom: 22px; }
/* The setup box is the one part that is a FORM, so it gets a surface of its own. */
.cap-setup {
  padding: 16px 18px; border: 1px solid var(--border); border-radius: 14px;
  background: var(--bg-1);
}
.cap-setup-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cap-page-foot {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin-top: 26px; padding-top: 16px; border-top: 1px solid var(--border);
}
.cap-page-foot .btn-ghost-danger { margin-left: auto; }
#view-capability .privacy-note { margin: 14px 0 0; }
.cap-desc { color: var(--text-dim); font-size: 13.5px; line-height: 1.65; margin: 0; }
.cap-section { display: flex; flex-direction: column; gap: 8px; }
.cap-section-title { font-size: 12px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; letter-spacing: .03em; }
.cap-perms { display: flex; flex-wrap: wrap; gap: 7px; }
.cap-perm { font-size: 12px; background: var(--bg-3); border: 1px solid var(--border); border-radius: 8px; padding: 5px 10px; color: var(--text-dim); }
.cap-perm::before { content: "🔑 "; }
/* What the plugin gives the AI — one standard row per tool, ruled like a list rather
   than run together as prose. Built by capToolRows(); the connector's tools and the
   Agent Organization's both come through here. */
.cap-tools { display: flex; flex-direction: column; }
.cap-tool {
  display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 12px;
  padding: 9px 2px; border-bottom: 1px solid var(--border);
}
.cap-tool:last-child { border-bottom: 0; }
.cap-tool-ico {
  width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
  color: var(--text-faint);
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 8px;
}
.cap-tool-ico .ico { width: 15px; height: 15px; }
.cap-tool-main { min-width: 0; }
.cap-tool-name {
  font-family: var(--mono); font-size: 12.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cap-tool-desc {
  margin-top: 1px; font-size: 12px; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cap-field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 12px; }
.cap-field > label { font-size: 12.5px; color: var(--text-dim); font-weight: 600; }
.cap-field .cap-help { font-size: 11.5px; color: var(--text-faint); }
.cap-field-check { flex-direction: row; align-items: center; gap: 8px; }
.cap-field input.set::placeholder { color: var(--ok); }
.cap-test { font-size: 13px; border-radius: 8px; padding: 9px 12px; }
.cap-test.ok { background: rgba(84,201,138,.1); border: 1px solid rgba(84,201,138,.4); color: #9be9bd; }
.cap-test.err { background: rgba(240,96,106,.1); border: 1px solid rgba(240,96,106,.35); color: #ffb3b8; }
.cap-help.warn { color: #ffb088; }
/* "this connector runs on another one's connection" — sits above the empty boxes, so
   they read as inherited rather than as unfinished setup. */
.cap-inherit {
  font-size: 13px; line-height: 1.5; border-radius: 8px; padding: 9px 12px;
  background: rgba(84,201,138,.08); border: 1px solid rgba(84,201,138,.32);
  color: var(--text-2, inherit);
}
.cap-inherit strong { color: var(--ok); }

/* oauth device-code flow */
.cap-oauth { display: flex; flex-direction: column; gap: 10px; }
.cap-oauth-status { font-size: 13.5px; padding: 9px 12px; border-radius: 9px; }
.cap-oauth-status.ok { background: rgba(84,201,138,.1); border: 1px solid rgba(84,201,138,.4); color: #9be9bd; }
.cap-oauth-flow { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 13px 15px; display: flex; flex-direction: column; gap: 8px; }
.cap-oauth-flow p { margin: 0; font-size: 13.5px; }
.cap-oauth-flow a { color: var(--accent-2); }
.oauth-code { font-family: var(--mono); font-size: 19px; letter-spacing: 2px; background: var(--bg-3); border: 1px solid var(--border); border-radius: 8px; padding: 6px 12px; color: var(--accent-2); display: inline-block; }
.oauth-spin { color: var(--text-faint); }
.cap-oauth-row { display: flex; gap: 10px; align-items: center; }
/* Curated MCP directory (one-click server gallery) */
.mcp-dir-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin: 8px 0 4px; }
.mcp-dir-card { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 11px 12px; display: flex; flex-direction: column; gap: 6px; }
.mcp-dir-head { display: flex; align-items: center; gap: 7px; }
.mcp-dir-icon { font-size: 16px; }
.mcp-dir-head code { font-family: var(--mono); color: var(--accent-2); font-size: 13px; }
.mcp-dir-summary { flex: 1; }
.mcp-dir-actions { display: flex; align-items: center; }
.mcp-dir-form { display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--border); padding-top: 8px; }
.cap-advanced { margin-top: 6px; }
.cap-advanced > summary { cursor: pointer; font-size: 12.5px; color: var(--text-dim); padding: 4px 0; }
.wa-qr { width: 240px; height: 240px; align-self: center; background: #fff; border-radius: 12px; padding: 10px; }
.cap-foot { justify-content: space-between; align-items: center; }
.cap-foot-right { display: flex; align-items: center; gap: 10px; }
.enable-toggle { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-dim); cursor: pointer; }
.enable-toggle input { accent-color: var(--accent); width: 16px; height: 16px; }
.btn-ghost-danger { background: none; border: 1px solid rgba(240,96,106,.4); color: var(--danger); border-radius: 10px; padding: 9px 14px; font-size: 13px; }
.btn-ghost-danger:hover { background: rgba(240,96,106,.12); }

/* toast */
#toast {
  position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
  background: var(--bg-3); border: 1px solid var(--border); color: var(--text);
  padding: 11px 18px; border-radius: 10px; font-size: 13.5px; z-index: 100;
  opacity: 0; transition: opacity .2s, transform .2s; pointer-events: none;
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(-4px); }
#toast.err { border-color: var(--danger); color: #ffb3b8; }

/* ===== App shell: ChatGPT-style sidebar + views ===== */
.side-nav { display: flex; flex-direction: column; gap: 1px; }
.side-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%; text-align: left; background: none; border: none; cursor: pointer;
  color: var(--text); font-size: 14px; font-weight: 500; padding: 8px 10px; border-radius: 8px;
}
.side-item:hover { background: var(--bg-hover); }
.side-item.active { background: var(--bg-3); }
/* Monochrome icons: grayscale removes emoji colour; SVGs inherit currentColor. */
.side-item .si-ico {
  width: 20px; height: 18px; flex-shrink: 0; filter: grayscale(1);
  display: inline-flex; align-items: center; justify-content: center; font-size: 15px; opacity: .82;
}
.side-item .si-ico svg { width: 17px; height: 17px; display: block; }
.side-item:hover .si-ico, .side-item.active .si-ico { opacity: 1; }

/* Group Chat participants panel */
.gc-people { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px 16px; border-bottom: 1px solid var(--border); }
.gc-person { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; color: var(--text-dim); background: var(--bg-3); border-radius: 8px; padding: 3px 9px; }
.gc-tag { font-size: 9px; font-weight: 700; background: var(--accent-soft); color: var(--accent-2); border-radius: 5px; padding: 0 5px; text-transform: uppercase; }
.side-item-dim { color: var(--text-faint); font-size: 13px; }
.side-item-kbd {
  margin-left: auto; font-size: 10.5px; color: var(--text-faint); font-weight: 600;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 5px; padding: 1px 6px;
}
.nav-badge {
  margin-left: auto; background: var(--danger); color: #fff; font-size: 10px; font-weight: 700;
  border-radius: 9px; padding: 1px 6px; min-width: 16px; text-align: center; line-height: 15px;
}

.side-section { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
.side-section-title { font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-faint); padding: 6px 10px 3px; font-weight: 600; }
.side-section-title.recents-title { display: flex; align-items: center; justify-content: space-between; padding-right: 6px; }
.recents-search-btn { background: none; border: none; color: var(--text-faint); cursor: pointer;
  padding: 3px; border-radius: 5px; display: inline-flex; align-items: center; }
.recents-search-btn:hover { color: var(--text); background: var(--bg-hover); }
.recents-search-btn svg { width: 14px; height: 14px; }

/* ---- Group Chat view ---- */
.gc-body { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 14px; height: calc(100vh - 210px); min-height: 380px; }
.gc-list { display: flex; flex-direction: column; gap: 7px; overflow-y: auto; padding-right: 2px; }
.gc-item { display: flex; align-items: center; gap: 10px; text-align: left; width: 100%; background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; padding: 9px 11px; cursor: pointer; color: var(--text); font: inherit; }
.gc-item:hover { border-color: var(--accent); }
.gc-item.active { border-color: var(--accent); background: var(--accent-soft); }
.gc-item-ava { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; background: var(--bg-3); display: inline-flex; align-items: center; justify-content: center; font-size: 16px; }
/* An outline mark rather than a colour emoji. The stroke is `currentColor`, so the
   tile sets the colour and the icon cannot bring a palette of its own into a design
   that is otherwise one accent and a grey. */
.gc-item-ava.ico, .gc-head-ava.ico { background: var(--accent-soft); color: var(--accent); }
.gc-ava-ico { display: inline-flex; width: 58%; height: 58%; }
.gc-ava-ico .ico { width: 100%; height: 100%; display: block; }
.gc-item-main { display: flex; flex-direction: column; min-width: 0; flex: 1; gap: 2px; }
/* Name left, time right; preview left, unread right. The two lines of a chat row are
   the same shape, which is what makes a long list scannable — the eye follows one
   column for "who" and another for "when", instead of hunting along each row. */
.gc-item-top { display: flex; align-items: baseline; gap: 6px; }
.gc-item-name { font-weight: 600; font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; }
/* Marks a chat-rail row as an AI teammate. Beside the NAME, not in the preview line:
   the preview is the last message once there is one, so a marker that lives there
   disappears exactly when the conversation gets long enough to forget who you are
   talking to. */
.gc-item-ai {
  flex: none; margin-left: 5px; padding: 0 5px; border-radius: 4px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .04em;
  background: var(--accent-soft); color: var(--accent);
}
.gc-item-when { flex: 0 0 auto; font-size: 10.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
/* An unread row says so twice — the time takes the accent as well as the dot — because
   the dot alone is 8px in a list somebody is scanning, not reading. */
.gc-item-when.unread { color: var(--accent); font-weight: 700; }
.gc-item-bot { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* The preview is ONE line, ellipsised. A message that wrapped would make its row
   twice the height of its neighbours and the list stop being a list. */
.gc-item-sub { font-size: 11.5px; color: var(--text-faint); flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gc-unread { width: 8px; height: 8px; border-radius: 50%; background: var(--danger); box-shadow: 0 0 6px var(--danger); }
.gc-newbtn { width: 100%; text-align: left; background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; padding: 9px 12px; color: var(--text); font: inherit; font-weight: 600; cursor: pointer; }
.gc-newbtn:hover { border-color: var(--accent); background: var(--bg-3); }
.gc-sec { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); margin: 10px 4px 2px; }
.gc-pick { display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow-y: auto; margin-bottom: 6px; }
.gc-pick-row { display: flex; align-items: center; gap: 10px; padding: 7px 6px; border-radius: 8px; cursor: pointer; }
.gc-pick-row:hover { background: var(--bg-hover); }
.gc-pick-row > span { display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; }
.gc-pick-row .cn-ppl-avatar { width: 26px; height: 26px; font-size: 11px; }
/* Group chat is now its own view — the composer no longer has a mode dropdown. */
#mode-drop { display: none; }
/* Group-chat private AI copilot: suggestions + auto-send toggle + assist tag */
.gc-head-actions { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.gc-cop-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim); cursor: pointer; white-space: nowrap; }
.gc-cop-toggle input { accent-color: var(--accent); }
.gc-suggest { display: flex; flex-direction: column; gap: 8px; padding: 8px 16px 0; }
.gc-suggest:empty { display: none; }
.gc-sug { border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; background: var(--bg); }
.gc-sug-reply { border-color: var(--accent); background: var(--accent-soft); }
.gc-sug-h { font-size: 12px; font-weight: 700; color: var(--text-dim); display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.gc-sug-conf { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; border-radius: 6px; padding: 1px 6px; background: var(--bg-3); color: var(--text-faint); }
.gc-sug-conf.high { background: rgba(31,157,85,.18); color: var(--ok, #1f9d55); }
.gc-sug-private { font-size: 10px; font-weight: 600; color: var(--text-faint); background: var(--bg-3); border-radius: 6px; padding: 1px 6px; }
.gc-sug-text { width: 100%; resize: vertical; min-height: 54px; background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; color: var(--text); font: inherit; font-size: 13.5px; line-height: 1.5; }
.gc-sug-body { font-size: 13.5px; color: var(--text); line-height: 1.5; }
.gc-sug-act { display: flex; gap: 8px; margin-top: 8px; }
.cn-assist-badge { font-size: 11px; opacity: .9; }

/* ===== Group Chat — full-bleed 3-column WhatsApp hub (rail | chat | Your AI) ===== */
.gc-hub { flex: 1; min-height: 0; display: grid; grid-template-columns: 280px minmax(0, 1fr) 460px; background: var(--bg); position: relative; }
.gc-rail { border-right: 1px solid var(--border); background: var(--bg-2); display: flex; flex-direction: column; min-height: 0; }
.gc-rail-head { display: flex; align-items: center; gap: 8px; padding: 14px 12px 10px; border-bottom: 1px solid var(--border); }
.gc-rail-head .cn-icon-btn svg { width: 18px; height: 18px; }
.gc-rail-title { font-weight: 700; font-size: 15px; flex: 1; }
/* Search the rail. Notifications and sound used to sit here as a permanent row; they
   are in the head's ⋯ menu now (gcOpenRailMenu), and this is what a chat rail actually
   needs above its list. */
.gc-rail-search { position: relative; padding: 10px 12px; border-bottom: 1px solid var(--border); }
.gc-rail-search .lib-search-ico { left: 24px; }
.gc-rail-search .lib-search { height: 34px; font-size: 13px; background: var(--bg); }

/* The in-app popup: the channel that always works — no permission, no OS, no service
   worker. Bottom-right, one card per room, click to open it. */
.gc-notices { position: fixed; right: 18px; bottom: 18px; z-index: 4000;
  display: flex; flex-direction: column; gap: 8px; max-width: 340px; }
.gc-notice { display: flex; align-items: flex-start; gap: 10px; cursor: pointer;
  padding: 11px 12px; border-radius: 12px; border: 1px solid var(--accent);
  background: var(--bg-2); box-shadow: var(--shadow-2, 0 8px 28px rgba(0,0,0,.35));
  animation: gc-notice-in .22s ease-out; }
.gc-notice:hover { background: var(--bg-hover); }
.gc-notice-ico { font-size: 15px; line-height: 1.3; flex: 0 0 auto; }
.gc-notice-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.gc-notice-top { font-size: 12px; color: var(--text-dim); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.gc-notice-top b { color: var(--text); }
.gc-notice-tx { font-size: 12.5px; color: var(--text); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.gc-notice-x { flex: 0 0 auto; border: none; background: none; color: var(--text-faint);
  cursor: pointer; font-size: 12px; padding: 0 2px; }
.gc-notice-x:hover { color: var(--text); }
@keyframes gc-notice-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.gc-rail-list { flex: 1; min-height: 0; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 4px; }
/* middle: the chat */
.gc-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg); }
.gc-main-head { display: flex; align-items: center; gap: 12px; padding: 11px 18px; border-bottom: 1px solid var(--border); cursor: pointer; background: var(--bg-2); }
.gc-main-head:hover { background: var(--bg-3); }
.gc-head-ava { width: 40px; height: 40px; border-radius: 50%; background: var(--bg-3); display: inline-flex; align-items: center; justify-content: center; font-size: 19px; flex-shrink: 0; }
.gc-head-text { flex: 1; min-width: 0; }
.gc-head-name { font-weight: 700; font-size: 15px; }
.gc-head-sub { font-size: 12px; color: var(--text-faint); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Each participant carries its own presence dot. The comma is drawn by CSS so the
   dot never ends up on the wrong side of it. */
.gc-head-mem { display: inline-flex; align-items: center; gap: 4px; }
.gc-head-mem + .gc-head-mem::before { content: ", "; margin-right: 4px; color: var(--text-faint); }
.gc-head-mem .cn-presence { width: 7px; height: 7px; }
.gc-head-chev { color: var(--text-faint); font-size: 22px; flex-shrink: 0; }
.gc-thread { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 22px; display: flex; flex-direction: column; gap: 10px; }
.gc-composer { display: flex; gap: 8px; padding: 12px 18px 16px; border-top: 1px solid var(--border); align-items: flex-end; }
/* The message box is a TEXTAREA, not an input: a colleague has to be able to send two
   lines, and a single-line input cannot hold one. It grows with `autoGrow` (shared with
   chat) up to 200px and then scrolls; `align-items: flex-end` above is what keeps the
   round buttons sitting on the LAST line as it grows instead of floating in the middle. */
.gc-composer .cn-input { margin: 0; flex: 1; border-radius: 22px; padding: 11px 16px;
  resize: none; overflow-y: auto; max-height: 200px; min-height: 42px; line-height: 1.4;
  font-family: inherit; }
/* Drag a file anywhere over the composer. Without a target here the page-level guard in
   `initWiring` swallowed the drop and nothing happened at all. */
.gc-composer.drag { background: var(--accent-soft); }
.gc-comp-btn { flex-shrink: 0; width: 38px; height: 38px; border: none; background: none; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--text-dim); }
.gc-comp-btn svg { width: 20px; height: 20px; }
.gc-comp-btn:hover { background: var(--bg-hover); color: var(--text); }
/* Emoji picker: the SAME `.emoji-menu` panel chat uses, anchored to this composer's
   button. It replaced a full-width strip pinned above the composer — which pushed the
   thread up every time it opened, so the last message moved while you were reading it.
   The button is wrapped in `.composer-drop` purely to give the panel something to be
   positioned against; the wrapper must be a flex item in its own right or the round
   button loses its 38px box. */
.gc-composer .composer-drop { display: flex; flex: 0 0 auto; }
/* Attachments waiting to be sent. A ROW of them, wrapping: 📎, a drop and a paste can
   each hand over several files at once, and taking one and silently dropping the rest is
   the bug this row exists to make impossible to ship. */
.gc-att-preview { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 16px; border-top: 1px solid var(--border); background: var(--bg-2); }
.gc-att-item { display: flex; align-items: center; gap: 8px; padding: 4px 6px 4px 4px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); max-width: 240px; }
.gc-att-thumb { width: 34px; height: 34px; object-fit: cover; border-radius: 7px; flex-shrink: 0; }
.gc-att-preview .gc-att-ico { font-size: 22px; flex-shrink: 0; }
.gc-att-pname { flex: 1; min-width: 0; font-size: 12.5px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gc-att-x { flex-shrink: 0; border: none; background: var(--bg-3); color: var(--text-dim); width: 22px; height: 22px; border-radius: 50%; cursor: pointer; font-size: 11px; line-height: 1; }
.gc-att-x:hover { color: var(--text); }
/* attachment inside a message bubble */
.gc-att-imgwrap { display: block; margin: 2px 0 4px; }
.gc-att-img { max-width: min(280px, 100%); max-height: 340px; width: auto; height: auto; border-radius: 12px; display: block; }
.gc-att-file { display: flex; align-items: center; gap: 9px; margin: 2px 0 4px; padding: 8px 10px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-2); text-decoration: none; color: var(--text); max-width: 280px; }
.gc-att-file:hover { border-color: var(--accent); }
.gc-att-file .gc-att-ico { font-size: 20px; flex-shrink: 0; }
.gc-att-file .gc-att-meta { display: flex; flex-direction: column; min-width: 0; }
.gc-att-name { font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gc-att-sub { font-size: 11px; color: var(--text-faint); }
/* "Editing message" bar above the composer (shown while editing your own message) */
.gc-editbar { display: flex; align-items: center; gap: 9px; padding: 9px 18px; border-top: 1px solid var(--border);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-2)); font-size: 12.5px; }
.gc-editbar-i { color: var(--accent); font-size: 13px; }
.gc-editbar-t { flex: 1; font-weight: 600; color: var(--text); }
.gc-editbar-x { border: none; background: none; color: var(--text-dim); cursor: pointer; font-size: 14px; line-height: 1; padding: 3px 7px; border-radius: 7px; }
.gc-editbar-x:hover { background: var(--bg-hover); color: var(--text); }
.gc-composer .btn-send { flex: 0 0 auto; width: 42px; height: 42px; border-radius: 50%; border: none; background: var(--accent); color: #fff; font-size: 15px; cursor: pointer; }
.gc-composer .btn-send:hover { filter: brightness(1.08); }
.gc-empty-main { flex: 1; display: flex; align-items: center; justify-content: center; color: var(--text-faint); font-size: 14px; padding: 40px; text-align: center; }
/* right: Your AI panel — modern copilot chat */
.gc-ai { border-left: 1px solid var(--border); background: linear-gradient(180deg, var(--bg-2), var(--bg)); display: flex; flex-direction: column; justify-content: flex-start; align-items: stretch; min-height: 0; padding: 16px 14px 14px; }
.gc-ai-center { display: flex; flex-direction: column; align-items: center; width: 100%; min-height: 0; max-height: 100%; }
/* The Inbox panel's head, exactly: a small mark, the name, and the line that says what
   it is doing. It was a centred hero — eyebrow, 104px mark, caption — which spent the
   top third of the column saying "Your AI" to someone already looking at Your AI, and
   made the two panels read as two different features. */
.gc-ai-head {
  display: flex; flex-direction: row; align-items: center; gap: 10px; flex-shrink: 0;
  padding-bottom: 12px; border-bottom: 1px solid var(--border);
}
.gc-ai-id { min-width: 0; }
.gc-ai-head .gc-ai-logo { width: 38px; height: 38px; }
.gc-ai-head .gc-ai-logo svg { width: 30px; height: 30px; }
/* The big mark still exists — in the empty body, which is the one place there is room
   for it and the one time it is the thing to look at. */
.gc-ai-body .ibx-ai-empty .gc-ai-logo { width: 132px; height: 132px; }
.gc-ai-body .ibx-ai-empty .gc-ai-logo svg { width: 96px; height: 96px; }
.gc-ai-tip { font-size: 11.5px; color: var(--text-faint); text-align: center; max-width: 240px; margin: 10px auto 0; line-height: 1.5; }
.gc-ai-tip b { color: var(--accent-2); font-weight: 700; }
.gc-ai-hero { padding: 18px 16px 8px; display: flex; flex-direction: column; align-items: center; }
.gc-ai-eyebrow { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--text-faint); margin-bottom: 6px; }
.gc-ai-logo { position: relative; width: 104px; height: 104px; display: grid; place-items: center; }
.gc-ai-logo::before { content: ""; position: absolute; inset: 12px; border-radius: 50%; background: radial-gradient(circle, color-mix(in srgb, var(--accent) 55%, transparent), transparent 70%); filter: blur(12px); animation: gc-glow 4.5s ease-in-out infinite; }
.gc-ai-logo.thinking::before { animation-duration: 1.6s; }
.gc-ai-logo svg { position: relative; width: 76px; height: 76px; display: block; color: var(--accent); }
/* new-chat hero: the spinning logo, larger, no wordmark */
.empty-hero-logo { width: 150px; height: 150px; margin: 0 auto; }

/* The one card row on an empty chat — skills this role can run, and ways in for someone
   who does not know what to type. Two rows in two places until 2026-08-20.

   ⚠️ It sits at the BOTTOM of the viewport, below the composer, and the logo and composer
   stay centred above it. That needs auto margins on TWO items, not one: `.chat-main` is a
   flex column and a single `margin-top: auto` on this row would absorb ALL the free space
   and shove the logo to the very top. With the same auto margin on #messages the free
   space splits evenly between them, so the hero group keeps its centred feel and the
   cards still land on the floor.

   ⚠️ A GRID on the composer's own column (the same 780px/22px as `.composer-inner` and
   `.msg-row`). As a centred flex wrap each card sized to its own sentence, so a set came
   out at several widths with some two lines tall, reading as a ragged pile. auto-fit
   handles narrow screens with no breakpoint to keep in step with the max-width. */
.empty-cards {
  display: none; grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  gap: 10px; width: 100%; max-width: 780px; margin: 0 auto; padding: 0 22px 18px;
}
.chat-main.chat-empty #empty-cards { display: grid; margin-top: auto; }
.chat-main.chat-empty #messages { margin-top: auto; }

/* One shape for every card, whether it is a skill or a prompt. Nothing is optional — a
   card without its description collapses to a different height and the set stops being a
   set. */
.empty-card {
  display: flex; align-items: flex-start; gap: 11px; text-align: left; width: 100%;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px;
  padding: 13px 14px; cursor: pointer; color: var(--text);
  transition: border-color .14s ease, background .14s ease, transform .14s ease;
}
.empty-card:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, var(--bg-2));
  transform: translateY(-1px);
}
.empty-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The tile is the constant; what sits IN it says which kind of card this is — the
   product's own ring mark for a prompt, the skill's own icon for a skill. That is the
   only difference between them, which is what lets them share a row without a heading
   explaining the split.
   ⚠️ `flex-shrink: 0`, because a long title must eat the text column and never the mark:
   a tile that changes size card to card is the raggedness this was meant to remove. */
.empty-card-ico {
  flex-shrink: 0; width: 26px; height: 26px; border-radius: 9px;
  display: grid; place-items: center; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.empty-card-ico svg { width: 15px; height: 15px; display: block; }
.empty-card-ico.is-skill { font-size: 14px; line-height: 1; background: var(--bg-3, var(--bg-2)); }
.empty-card:hover .empty-card-ico {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* min-width: 0 so a long line ellipses inside the card instead of widening its column
   and pushing the grid out of alignment. A connector name lands in a title and they run
   long ("Ask about Microsoft Teams"), so the title wraps to two lines at most and the
   description takes one. */
.empty-card-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.empty-card-title {
  font-size: 13px; font-weight: 600; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.empty-card-desc {
  font-size: 11.5px; line-height: 1.4; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.empty-hero-logo svg { width: 108px; height: 108px; }
.gc-ai-status { text-align: center; font-size: 12.5px; color: var(--text-dim); margin-top: 10px; min-height: 16px; }
.gc-ai-feed { width: 100%; flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 10px 2px 0; margin-top: 10px; display: flex; flex-direction: column; gap: 10px; }
/* ===== AI threads panel (server-side jobs as status cards) ================== */
.gc-ai-body { width: 100%; flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 14px 2px 6px; }
.gc-th-empty { color: var(--text-faint); font-size: 12.5px; line-height: 1.5; text-align: center; padding: 24px 10px; }
.gc-th-loading { display: flex; justify-content: center; padding: 26px 0; }
.gc-th-list { display: flex; flex-direction: column; gap: 8px; }
.gc-th-card { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; border: 1px solid var(--border);
  background: var(--bg-2); border-radius: 12px; padding: 11px 12px; cursor: pointer; transition: border-color .12s, background .12s; }
.gc-th-card:hover { border-color: var(--accent); background: var(--accent-soft); }
.gc-th-card.waiting { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
/* the card body opens the thread; the mini buttons beside it stop / dismiss it */
.gc-th-open { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; border: none; background: none;
  padding: 0; text-align: left; cursor: pointer; font: inherit; color: inherit; }
.gc-th-mini { flex-shrink: 0; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: var(--bg-3); color: var(--text-faint); border-radius: 7px;
  font-size: 10px; line-height: 1; cursor: pointer; transition: color .12s, border-color .12s; }
.gc-th-mini:hover { color: var(--danger, #e03131); border-color: var(--danger, #e03131); }
.gc-th-mini.stop:hover { color: var(--text); border-color: var(--accent); }
.gc-th-title { flex: 1; min-width: 0; font-size: 13px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gc-th-badge { flex-shrink: 0; font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 999px; background: var(--bg-3); color: var(--text-dim); white-space: nowrap; }
.gc-th-badge.thinking { background: var(--accent-soft); color: var(--accent-2); }
.gc-th-badge.waiting { background: rgba(224,168,0,.18); color: #e0a800; }
.gc-th-badge.complete { background: rgba(31,157,85,.18); color: var(--ok, #1f9d55); }
.gc-th-badge.error { background: rgba(224,49,49,.16); color: var(--danger, #e03131); }
.gc-th-badge.stopped { background: var(--bg-3); color: var(--text-faint); }
.gc-th-badge.sent { background: rgba(31,157,85,.14); color: var(--ok, #1f9d55); }
/* thread detail */
.gc-th-detail { display: flex; flex-direction: column; gap: 10px; }
.gc-th-dhead { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.gc-th-back { border: none; background: none; color: var(--accent-2); font-size: 13px; font-weight: 600; cursor: pointer; padding: 2px 2px; }
.gc-th-back:hover { text-decoration: underline; }
.gc-th-dtitle { font-size: 14px; font-weight: 700; color: var(--text); line-height: 1.35; }
.gc-th-steps { display: flex; flex-direction: column; gap: 5px; }
.gc-th-step { font-size: 11.5px; color: var(--text-dim); font-family: var(--mono); }
.gc-th-result.msg-content { background: var(--bg-3); border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; font-size: 13px; }
.gc-th-working { display: inline-flex; align-items: center; gap: 8px; color: var(--accent-2); font-size: 12.5px; font-weight: 600; }
.gc-th-err { color: var(--danger, #e03131); font-size: 12.5px; }
.gc-th-actions { display: flex; flex-direction: column; gap: 6px; margin-top: 2px; }
.gc-th-actions .gcx-choice, .gc-th-actions .gcx-a-send, .gc-th-actions .gcx-a-copy, .gc-th-actions .gcx-a-dismiss,
.gc-th-actions .gcx-a-stop {
  border: 1px solid var(--border); background: var(--bg-2); color: var(--text); border-radius: 9px; padding: 8px 11px;
  font-size: 12.5px; font-weight: 600; cursor: pointer; text-align: left; }
.gc-th-actions .gcx-choice:hover { border-color: var(--accent); background: var(--accent-soft); }
.gc-th-actions .gcx-a-stop:hover { border-color: var(--danger, #e03131); color: var(--danger, #e03131); }
.gc-th-actions .gcx-a-send.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.gc-th-actions .gcx-a-send.primary:hover { filter: brightness(1.08); }
/* "Other" — type a custom instruction */
.gc-th-other-wrap { margin-top: 2px; }
.gcx-a-other { width: 100%; border: 1px dashed var(--border); background: transparent; color: var(--text-dim);
  border-radius: 9px; padding: 8px 11px; font-size: 12.5px; font-weight: 600; cursor: pointer; text-align: left; }
.gcx-a-other:hover { border-style: solid; border-color: var(--accent); color: var(--text); }
.gc-th-other { display: flex; gap: 6px; }
.gc-th-other-input { flex: 1; min-width: 0; background: var(--bg-2); border: 1px solid var(--border); border-radius: 9px;
  padding: 8px 11px; color: var(--text); font: inherit; font-size: 12.5px; outline: none; }
.gc-th-other-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.gc-th-other-go { flex-shrink: 0; border: none; background: var(--accent); color: #fff; border-radius: 9px;
  padding: 8px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; }
.gc-th-other-go:hover { filter: brightness(1.08); }
/* footer: quick-action chips + a pinned "Ask your AI" input */
/* Matches .ibx-ai-act: the things you can do, under what it has to say, divided by one
   line. Left-aligned like the rest of the panel — centred chips read as a landing page. */
.gc-ai-foot { flex-shrink: 0; padding-top: 12px; margin-top: 10px; border-top: 1px solid var(--border); }
.gc-ai-chips { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-start; margin-bottom: 9px; }
.gc-chip { border: 1px solid var(--border); background: var(--bg-2); color: var(--text-dim); border-radius: 999px; padding: 5px 12px; font-size: 12px; font-weight: 600; cursor: pointer; transition: all .12s; }
.gc-chip:hover { border-color: var(--accent); color: var(--text); background: var(--accent-soft); }
.gc-ai-ask { display: flex; align-items: center; gap: 8px; }
.gc-ai-askinput { flex: 1; min-width: 0; background: var(--bg-2); border: 1px solid var(--border); border-radius: 20px; padding: 9px 14px; color: var(--text); font: inherit; font-size: 13px; outline: none; transition: border-color .12s, box-shadow .12s; }
.gc-ai-askinput:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.gc-ai-asksend { flex-shrink: 0; width: 36px; height: 36px; border: none; border-radius: 50%; background: var(--accent); color: #fff; font-size: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.gc-ai-asksend:hover { filter: brightness(1.08); }
/* generated-file download card inside an AI feed item */
.gcx-file { display: flex; align-items: center; gap: 9px; margin-top: 8px; padding: 8px 10px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-2); text-decoration: none; color: var(--text); transition: border-color .12s, background .12s; }
.gcx-file:hover { border-color: var(--accent); background: var(--accent-soft); }
.gcx-file-ico { font-size: 18px; flex-shrink: 0; }
.gcx-file-meta { display: flex; flex-direction: column; min-width: 0; }
.gcx-file-name { font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gcx-file-sub { font-size: 11px; color: var(--text-faint); }
/* footer is tap-only (no typing) */
.gc-ai-foot-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.gc-ai-foot-label { font-size: 10.5px; color: var(--text-faint); }
.gc-ai-depth { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--text-dim); cursor: pointer; user-select: none; }
.gc-ai-depth input { accent-color: var(--accent); cursor: pointer; }
/* AI clarification rendered as tap-choices */
.gcx-choices { margin-top: 9px; display: flex; flex-direction: column; gap: 6px; }
.gcx-choices-h { font-size: 11px; font-weight: 700; color: var(--text-faint); }
.gcx-choice { text-align: left; border: 1px solid var(--border); background: var(--bg-2); color: var(--text); border-radius: 10px; padding: 8px 11px; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: border-color .12s, background .12s; }
.gcx-choice:hover { border-color: var(--accent); background: var(--accent-soft); }
/* proactive "suggested reply" draft card */
.gcx-conf.go { background: var(--accent-soft); color: var(--accent-2); }
.gcx-draft .gcx-body { border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--border)); }
.gcx-a-send.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.gcx-a-send.primary:hover { filter: brightness(1.08); }
/* small "or:" select-only menu of other things the AI can do */
.gcx-more { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; }
.gcx-more-lbl { font-size: 11px; color: var(--text-faint); }
.gcx-m { border: 1px dashed var(--border); background: transparent; color: var(--text-dim); border-radius: 999px; padding: 3px 10px; font-size: 11.5px; cursor: pointer; }
.gcx-m:hover { border-style: solid; border-color: var(--accent); color: var(--text); }
/* proactive wizard: a few tap-choices the AI detected */
.gcx-wizard .gcx-body { border: 1px solid color-mix(in srgb, var(--accent) 38%, var(--border)); }
.gcx-wz-list { display: flex; flex-direction: column; gap: 6px; margin: 7px 0 3px; }
.gcx-wz { text-align: left; border: 1px solid var(--border); background: var(--bg-2); color: var(--text); border-radius: 10px; padding: 8px 11px; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: border-color .12s, background .12s; }
.gcx-wz:hover { border-color: var(--accent); background: var(--accent-soft); }
/* live workbench streamed into the panel: give it room, keep the answer plain */
.gcx-run .gcx-body { max-width: none; flex: 1; min-width: 0; }
.gcx-run .msg-content { background: transparent; border: none; padding: 2px 0; border-radius: 0; font-size: 13px; }
.gcx-run .tool-chip { font-size: 11.5px; }
.gcx-run .files-card, .gcx-run .tool-chip { margin-top: 8px; }
.gc-ai-foot { border-top: 1px solid var(--border); padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 9px; background: var(--bg-2); }
.gc-ai-quick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.gc-chip { border: 1px solid var(--border); background: var(--bg); color: var(--text-dim); border-radius: 999px; padding: 5px 12px; font-size: 12px; font-weight: 600; cursor: pointer; }
.gc-chip:hover { border-color: var(--accent); color: var(--text); }
.gc-switch { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; cursor: pointer; font-size: 11px; color: var(--text-dim); white-space: nowrap; }
.gc-switch input { display: none; }
.gc-switch-tr { width: 30px; height: 17px; border-radius: 999px; background: var(--bg-3); position: relative; transition: background .15s; flex-shrink: 0; }
.gc-switch-tr::after { content: ""; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px; border-radius: 50%; background: #fff; transition: transform .15s; }
.gc-switch input:checked + .gc-switch-tr { background: var(--accent); }
.gc-switch input:checked + .gc-switch-tr::after { transform: translateX(13px); }
.gc-ai-hint { font-size: 11.5px; color: var(--text-faint); text-align: center; line-height: 1.5; }
.gc-ai-hint b { color: var(--accent-2); font-weight: 700; }
.gc-ai-ask { display: flex; gap: 8px; align-items: center; }
.gc-ai-askin { flex: 1; border: 1px solid var(--border); background: var(--bg); color: var(--text); border-radius: 999px; padding: 9px 14px; font: inherit; font-size: 13px; outline: none; }
.gc-ai-askin:focus { border-color: var(--accent); }
.gc-ai-asksend { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; border: none; background: var(--accent); color: #fff; cursor: pointer; font-size: 13px; }
/* feed bubbles (personal-chat style) */
.gcx { display: flex; gap: 8px; max-width: 100%; }
.gcx-me { justify-content: flex-end; }
.gcx-ava { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 8px; background: var(--bg-3); display: grid; place-items: center; align-self: flex-end; }
.gcx-ava svg { width: 16px; height: 16px; color: var(--accent); }
.gcx-body { border-radius: 15px; padding: 9px 12px; font-size: 13px; line-height: 1.5; max-width: 84%; }
.gcx-ai .gcx-body { background: var(--bg-3); color: var(--text); border-bottom-left-radius: 5px; }
.gcx-me .gcx-body { background: var(--accent-soft); color: var(--text); border-bottom-right-radius: 5px; }
.gcx-h { font-size: 11px; font-weight: 700; color: var(--text-dim); margin-bottom: 5px; display: flex; align-items: center; gap: 6px; }
.gcx-text { white-space: pre-wrap; }
.gcx-act { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.gcx-act button { border: 1px solid var(--border); background: var(--bg-2); color: var(--text); border-radius: 8px; padding: 4px 10px; font-size: 12px; font-weight: 600; cursor: pointer; }
.gcx-act button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.gcx .gc-sug-text { width: 100%; min-height: 46px; resize: vertical; background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px; padding: 7px 9px; color: var(--text); font: inherit; font-size: 13px; margin-top: 2px; }
.gcx-conf { font-size: 9.5px; font-weight: 700; text-transform: uppercase; background: var(--bg-2); color: var(--text-faint); border-radius: 5px; padding: 1px 5px; }
.gcx-conf.high { background: rgba(31,157,85,.2); color: var(--ok, #1f9d55); }
.gcx-dots { display: inline-flex; gap: 4px; padding: 3px 0; }
.gcx-dots i { width: 6px; height: 6px; border-radius: 50%; background: var(--text-faint); animation: gc-td 1.2s ease-in-out infinite; }
.gcx-dots i:nth-child(2){animation-delay:.2s}.gcx-dots i:nth-child(3){animation-delay:.4s}
@keyframes gc-td { 0%,100%{opacity:.3;transform:translateY(0)} 50%{opacity:1;transform:translateY(-3px)} }
/* composer @ / search drawer */
.gc-main { position: relative; }
.gc-drawer { position: absolute; left: 18px; right: 18px; bottom: 72px; background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 14px 44px rgba(0,0,0,.42); max-height: 260px; overflow-y: auto; z-index: 30; padding: 6px; }
.gc-drawer.hidden { display: none; }
.gc-drawer-h { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); padding: 7px 9px 4px; }
.gc-drawer-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: none; border: none; border-radius: 9px; padding: 8px 10px; color: var(--text); font: inherit; cursor: pointer; }
.gc-drawer-item:hover, .gc-drawer-item.active { background: var(--bg-hover); }
.gc-di-cmd { font-weight: 700; color: var(--accent-2); }
.gc-di-desc { font-size: 12px; color: var(--text-faint); }
.gc-mention { color: var(--accent-2); font-weight: 600; }
/* message options button + read-receipt ticks (WhatsApp-style: all inside the bubble) */
.cn-smsg { position: relative; }
.gc-msg-btn { position: absolute; top: 0; right: 0; width: 38px; height: 26px; border: none; border-top-right-radius: 14px;
  background: linear-gradient(270deg, var(--bg-3) 58%, transparent); color: var(--text-dim); cursor: pointer;
  opacity: 0; font-size: 13px; line-height: 1; display: flex; align-items: center; justify-content: flex-end;
  padding: 0 7px 0 0; transition: opacity .12s; z-index: 2; }
.cn-smsg.mine .gc-msg-btn { background: linear-gradient(270deg, var(--user-bubble) 58%, transparent); }
.cn-smsg-body:hover .gc-msg-btn, .gc-msg-btn:focus-visible { opacity: 1; }
.gc-msg-btn:hover { color: var(--text); }
.gc-ticks { font-size: 11px; color: var(--text-faint); letter-spacing: -2px; }
.gc-ticks.seen { color: #53bdeb; }
.gc-msg-menu { position: fixed; z-index: 200; background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 14px 44px rgba(0,0,0,.45); padding: 5px; min-width: 168px; }
.gc-msg-menu button { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; background: none; border: none; border-radius: 8px; padding: 9px 11px; color: var(--text); font: inherit; font-size: 13.5px; cursor: pointer; }
.gc-msg-menu button:hover { background: var(--bg-hover); }
.gc-mm-i { width: 18px; text-align: center; color: var(--text-dim); font-size: 14px; flex-shrink: 0; }
/* alive logo — exact motion from the Claude Design export: centre pulses, inner ring
   spins CW, outer ring spins CCW, the between-ring dots breathe. */
.gc-ai-logo .core, .gc-ai-logo .r1, .gc-ai-logo .r2, .gc-ai-logo .r3 { transform-origin: 60px 60px; transform-box: view-box; }
.gc-ai-logo .core { animation: qPulse 3.4s ease-in-out infinite; }
.gc-ai-logo .r1 { animation: qSpin 26s linear infinite; }
.gc-ai-logo .r2 { animation: qSpinRev 38s linear infinite; }
.gc-ai-logo .r3 { animation: qBreathe 4.2s ease-in-out infinite; }
.gc-ai-logo.thinking .core { animation-duration: 1.4s; }
.gc-ai-logo.thinking .r1 { animation-duration: 8s; }
.gc-ai-logo.thinking .r2 { animation-duration: 12s; }
.gc-ai-logo.thinking .r3 { animation-duration: 1.8s; }
@keyframes qSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes qSpinRev { from { transform: rotate(360deg); } to { transform: rotate(0deg); } }
@keyframes qPulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.14); } }
@keyframes qBreathe { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .55; transform: scale(.86); } }
@keyframes gc-glow { 0%,100% { opacity: .3; transform: scale(.9); } 50% { opacity: .65; transform: scale(1.05); } }
@media (prefers-reduced-motion: reduce) { .gc-ai-logo .core, .gc-ai-logo .r1, .gc-ai-logo .r2, .gc-ai-logo .r3 { animation: none !important; } }
/* group info slide-over */
.gc-info-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 60; display: flex; justify-content: flex-end; }
.gc-info-overlay.hidden { display: none; }
.gc-info-panel { width: 360px; max-width: 92vw; height: 100%; background: var(--bg-2); border-left: 1px solid var(--border); overflow-y: auto; padding: 16px 18px 22px; display: flex; flex-direction: column; gap: 12px; }
.gc-info-sub { font-size: 12.5px; color: var(--text-faint); }
.gc-info-members { display: flex; flex-direction: column; gap: 2px; }
.gc-info-mrow { display: flex; align-items: center; gap: 10px; padding: 7px 4px; border-radius: 8px; font-size: 14px; }
.gc-info-mrow .cn-ppl-avatar { width: 30px; height: 30px; font-size: 12px; }
.gc-info-mrow > span:first-of-type { display: inline-flex; align-items: center; gap: 6px; }
/* Push the state text to the right edge, same reading order as a Members card. */
.gc-info-mstate { margin-left: auto; font-size: 11.5px; color: var(--text-faint); white-space: nowrap; }
@media (max-width: 1040px) { .gc-hub { grid-template-columns: 260px minmax(0, 1fr); } .gc-ai { display: none; } }
.gc-view { min-width: 0; display: flex; flex-direction: column; background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.skill-nav { display: flex; flex-direction: column; gap: 2px; max-height: 30vh; overflow-y: auto; }
.skill-nav:empty { display: none; }
.skill-nav .side-item.active { background: var(--accent-soft); }

.side-recents { flex: 1; min-height: 0; }
.side-recents .conversations { max-height: none; }
.conv-empty { color: var(--text-faint); font-size: 12.5px; padding: 8px 10px; }
.recents-search {
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 7px 10px; font-size: 13px; margin: 2px 0 4px; outline: none;
}
.recents-search:focus { border-color: var(--accent); }

.account {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: none; border: none; border-top: 1px solid var(--border);
  padding: 12px 8px 4px; margin-top: 4px; color: var(--text); border-radius: 0;
}
.account:hover { background: var(--bg-hover); border-radius: 9px; }
.avatar-mini {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  background: linear-gradient(135deg, #f0606a, #d64550); color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700;
}
.account-text { display: flex; flex-direction: column; line-height: 1.25; }
.account-text strong { font-size: 13.5px; }
.account-text small { font-size: 11px; color: var(--text-faint); }

/* views */
.view { flex: 1; min-height: 0; display: flex; flex-direction: column; min-width: 0; }
.view.hidden { display: none; }

/* full-page header/body (marketplace + skill pages) */
.page-head { padding: 26px 30px 0; border-bottom: 1px solid var(--border); }
.page-head h1 { margin: 0 0 4px; font-size: 24px; }
.page-head > p { margin: 0 0 16px; color: var(--text-dim); font-size: 14px; max-width: 680px; }
.page-head-row { display: flex; align-items: center; gap: 14px; padding-bottom: 18px; }
.page-head-icon { font-size: 30px; width: 56px; height: 56px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center; background: var(--bg-3); border-radius: 14px; }
.page-head-row h1 { margin: 0; }
.page-head-row p { margin: 4px 0 0; color: var(--text-dim); font-size: 13.5px; max-width: 620px; }
.page-body { flex: 1; overflow-y: auto; padding: 22px 30px 40px; }

.market-tabs { display: flex; gap: 6px; }
.market-tab {
  background: none; border: none; color: var(--text-dim); font-size: 14px; font-weight: 600;
  padding: 9px 14px; border-radius: 9px 9px 0 0; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.market-tab:hover { color: var(--text); }
.market-tab.active { color: var(--text); border-bottom-color: var(--accent); }

/* skill page */
/* The setup checklist — what this skill still needs, and a button that connects it
   here. It sits ABOVE the Run bar on the Summary tab (it used to be a row of chips on
   Settings, which is not the tab you land on). */
.skill-requires { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.skill-requires .setup-head {
  font-size: 13px; line-height: 1.5; color: var(--text);
  padding: 10px 12px; border-radius: var(--radius);
  background: color-mix(in srgb, var(--warn) 10%, var(--bg-2));
  border: 1px solid color-mix(in srgb, var(--warn) 38%, var(--border));
}
.skill-requires .setup-ok { font-size: 12.5px; color: var(--text-faint); }
.setup-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-2);
}
.setup-row.has-note { align-items: flex-start; }
.setup-row .setup-ico { --ico-sz: 28px; font-size: 16px; flex-shrink: 0; }
.setup-row .setup-main { display: flex; flex-direction: column; gap: 2px; }
.setup-row .setup-name { font-weight: 600; font-size: 13.5px; }
/* What the SKILL says it needs from this connection (frontmatter `setup:`). */
.setup-row .setup-note { font-size: 12px; color: var(--text-dim); line-height: 1.45; max-width: 42ch; }
.setup-row .setup-why { flex: 1; font-size: 12px; color: var(--text-faint); text-align: right; }
/* Not fixable from here — the member's role withholds it, so there is no button. */
.setup-row .setup-why-blocked { font-size: 12px; color: var(--warn); }
/* Run, while the skill cannot work. Cosmetic — the server refuses it too. */
#skill-run.is-blocked { opacity: .5; cursor: not-allowed; }
/* The Settings tab is one form column, and everything in it shares its edge — the params
   were 620 wide and the schedule card beside them 820, which is most of why this page
   read as a pile of boxes rather than a page. */
#skill-panel-settings { max-width: 720px; }
.skill-params { display: flex; flex-direction: column; gap: 14px; }
.skill-param { display: flex; flex-direction: column; gap: 5px; }
.skill-param label { font-size: 13px; color: var(--text-dim); font-weight: 600; }
.skill-param input, .skill-param textarea, .skill-param select {
  background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 9px; padding: 10px 12px; font-size: 14px; font-family: inherit; outline: none; width: 100%;
}
.skill-param input:focus, .skill-param textarea:focus, .skill-param select:focus { border-color: var(--accent); }
.skill-param .cap-help { font-size: 11.5px; color: var(--text-faint); }
/* checkbox + multiselect params */
.skill-param .skp-check, .skill-param .skp-chk { flex-direction: row; display: inline-flex; align-items: center; gap: 7px; font-weight: 500; color: var(--text); cursor: pointer; }
.skill-param input[type="checkbox"] { width: auto; margin: 0; accent-color: var(--accent); }
.skp-multi { display: flex; flex-wrap: wrap; gap: 6px 16px; padding: 2px 0; }
.skp-multi .skp-chk { font-size: 13px; }
.skill-actions { display: flex; align-items: center; gap: 10px; margin: 18px 0; }
.btn-ghost { background: none; border: 1px solid var(--border); color: var(--text-dim); border-radius: 10px; padding: 9px 14px; font-size: 13px; cursor: pointer; }
.btn-ghost:hover { background: var(--bg-hover); color: var(--text); }
/* schedule card */
.skill-schedule { border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px; margin: 18px 0; background: var(--bg-2); }
.sched-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.sched-title { font-weight: 600; font-size: 14px; }
.sched-status { font-size: 12px; color: var(--text-faint); }
.sched-status.on { color: #6fce8f; }
.sched-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.sched-input { background: var(--bg-1, var(--bg-2)); border: 1px solid var(--border); color: var(--text); border-radius: 9px; padding: 8px 10px; font-size: 13px; font-family: inherit; outline: none; }
.sched-input:focus { border-color: var(--accent); }
.sched-hint { font-size: 11.5px; color: var(--text-faint); margin: 10px 0 0; }
/* skill page tabs (Summary / Settings) */
/* The same pill tabs as Files, Memory, Skills and Heartbeat — a fourth tab style on the
   page you land on from any of them is how the app stopped looking like one product. */
.skill-tabs { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; margin-bottom: 18px; }
.skill-tab {
  display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 14px;
  border: 0; border-radius: 999px; background: none; color: var(--text-dim);
  font: inherit; font-size: 13.5px; font-weight: 600; white-space: nowrap; cursor: pointer;
  transition: background .12s, color .12s;
}
.skill-tab:hover { background: var(--bg-2); color: var(--text); }
.skill-tab.active { background: var(--bg-3); color: var(--text); }
:root[data-theme="light"] .skill-tab.active { background: var(--bg-hover); }
.skill-summary-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.skill-summary-meta { font-size: 12px; color: var(--text-faint); }
/* Findings: the run's structured half, as a list you work through. Statuses here
   survive the next run, so it is a working set and not a report. */
.skill-rows { border: 1px solid var(--border); border-radius: 12px; padding: 14px 16px;
  background: var(--bg-2); }
.rows-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 10px; }
.rows-title { font-size: 14px; font-weight: 700; }
.rows-filters { display: flex; gap: 6px; flex-wrap: wrap; }
.rowfilter { border: 1px solid var(--border); background: transparent; color: var(--text-dim);
  border-radius: 999px; padding: 3px 10px; font-size: 12px; cursor: pointer; }
.rowfilter:hover { color: var(--text); }
.rowfilter.active { background: var(--bg-3); color: var(--text); border-color: var(--accent); }
.rowfilter-n { opacity: .65; margin-left: 3px; }
.rows-scroll { overflow-x: auto; }
.rows-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.rows-table th { text-align: left; font-weight: 600; color: var(--text-faint); font-size: 11px;
  text-transform: uppercase; letter-spacing: .04em; padding: 6px 10px 6px 0; border-bottom: 1px solid var(--border); }
.rows-table td { padding: 8px 10px 8px 0; border-bottom: 1px solid var(--border-soft, var(--border));
  vertical-align: top; }
.rows-table tr.row-done td, .rows-table tr.row-dismissed td, .rows-table tr.row-resolved td { opacity: .55; }
.rows-table .row-actions { white-space: nowrap; text-align: right; }
.rows-table .row-actions .btn-mini { margin-left: 6px; }
.row-status { font-size: 11px; color: var(--text-faint); }
.row-note { font-size: 11px; color: var(--text-faint); margin-top: 3px; font-style: italic; }
.rows-foot { margin-top: 10px; font-size: 11.5px; }
.skill-result {
  border: 1px solid var(--border); border-radius: 12px; padding: 18px 20px; min-height: 120px;
  background: var(--bg-2);
}
.skill-result .msg-content { word-wrap: break-word; }
.skill-result .hint { color: var(--text-faint); }
/* digest: clean, readable rendering of the final result */
.digest { line-height: 1.62; font-size: 14.5px; color: var(--text); }
.digest h1, .digest h2 { font-size: 18px; margin: 20px 0 8px; font-weight: 700; letter-spacing: -.01em; }
.digest h1:first-child, .digest h2:first-child { margin-top: 2px; }
.digest h3 { font-size: 15px; margin: 16px 0 6px; font-weight: 700; }
.digest p { margin: 9px 0; }
.digest ul, .digest ol { margin: 8px 0; padding-left: 20px; }
.digest li { margin: 5px 0; }
.digest li::marker { color: var(--text-faint); }
.digest a { color: var(--accent); text-decoration: none; }
.digest a:hover { text-decoration: underline; }
.digest strong { color: var(--text); font-weight: 700; }
.digest hr { border: none; border-top: 1px solid var(--border); margin: 16px 0; }
.digest img { max-width: 100%; border-radius: 8px; margin: 6px 0; }
.run-error { display: flex; flex-direction: column; gap: 8px; }
.run-error-title { font-weight: 700; color: #ffb088; font-size: 15px; }
.run-error-msg { color: var(--text-dim); font-size: 13.5px; white-space: pre-wrap;
  background: rgba(255,160,90,.08); border: 1px solid rgba(255,160,90,.25); border-radius: 8px; padding: 10px 12px; }
/* ---- Summary: runs down the left, the run you picked on the right ----
   Every box on this tab used to set its own width (620 / 820 / 980 / none), so a page
   with four of them had four right-hand edges and read as broken. Width comes from the
   column now; nothing here declares its own. */
.skill-split {
  display: grid; grid-template-columns: minmax(190px, 240px) minmax(0, 1fr);
  gap: 20px; align-items: start;
}
/* No history yet (a skill that has never run) — the column track would otherwise stay,
   indenting the report by 210px against nothing. */
.skill-split:has(> .skill-runs.hidden) { grid-template-columns: minmax(0, 1fr); }
.skill-main { min-width: 0; display: flex; flex-direction: column; gap: 14px; }
/* The list follows you down a long report rather than scrolling away above it. */
.skill-runs {
  position: sticky; top: 0; display: flex; flex-direction: column; gap: 2px;
  max-height: calc(100vh - 220px); overflow-y: auto;
}
@media (max-width: 900px) {
  .skill-split { grid-template-columns: 1fr; }
  /* Stacked, the history goes UNDER the report — it is the older thing. */
  .skill-runs { order: 2; position: static; max-height: 320px; }
}
.skill-runs .runs-title {
  position: sticky; top: 0; z-index: 1; background: var(--bg);
  padding: 2px 12px 8px; margin: 0;
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-faint); font-weight: 700;
}
.run-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; cursor: pointer;
  background: none; border: 1px solid transparent; border-radius: 9px; padding: 8px 12px; color: var(--text-dim); font-size: 13px; }
.run-row:hover { background: var(--bg-2); }
.run-row.active { background: var(--bg-2); border-color: var(--border); color: var(--text); }
.run-when { font-weight: 600; }
.run-mark { color: var(--text-faint); font-size: 12px; }
.run-mark.error { color: #ffb088; }
.run-dot { margin-left: auto; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: none; }

/* marketplace cards: install/open buttons */
.market-card .market-card-foot .btn-mini, .market-card .market-card-foot .btn-primary { padding: 6px 14px; font-size: 12.5px; }
.market-card-actions { display: flex; gap: 8px; align-items: center; }

/* ===== Task Manager ===== */
.tasks-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.tasks-actions { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
.tasks-toolbar { display: flex; align-items: center; gap: 10px; padding: 4px 0 16px; flex-wrap: wrap; }
.seg-group { display: inline-flex; background: var(--bg-2); border: 1px solid var(--border); border-radius: 9px; overflow: hidden; }
.seg { background: none; border: none; color: var(--text-dim); font-size: 13px; padding: 7px 13px; }
.seg.active { background: var(--accent-soft); color: var(--text); }
.tasks-select, .tasks-filter-input {
  background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 9px; padding: 7px 11px; font-size: 13px; font-family: inherit; outline: none;
}
.tasks-filter-input { flex: 1; min-width: 140px; max-width: 320px; }
.tasks-select:focus, .tasks-filter-input:focus { border-color: var(--accent); }
.tasks-count { color: var(--text-faint); font-size: 12.5px; margin-left: auto; }
.tasks-empty { color: var(--text-faint); text-align: center; padding: 50px 20px; }

/* board */
.tasks-board { display: flex; gap: 14px; align-items: flex-start; overflow-x: auto; padding-bottom: 10px; }
.task-col { flex: 1; min-width: 250px; background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 10px; }
.task-col-head { display: flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 600; color: var(--text-dim); padding: 4px 6px 10px; }
.task-col-head .dotmark { width: 9px; height: 9px; border-radius: 50%; }
.task-col-head .col-count { margin-left: auto; color: var(--text-faint); font-weight: 500; }
.task-col-body { display: flex; flex-direction: column; gap: 8px; min-height: 12px; }

.task-card { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 11px 12px; }
.task-card:hover { border-color: var(--bg-hover); }
.task-card-title { font-size: 14px; font-weight: 600; margin-bottom: 6px; line-height: 1.35; }
.task-meta { display: flex; flex-wrap: wrap; gap: 6px 10px; font-size: 12px; color: var(--text-dim); margin-bottom: 8px; }
.task-meta .tm { display: inline-flex; align-items: center; gap: 4px; }
.task-meta .tm-due { color: #e0a458; }
.tm-overdue { color: var(--danger); font-weight: 600; }
.home-overdue { color: var(--danger); font-weight: 600; }
.task-detail { font-size: 12.5px; color: var(--text-dim); margin: 4px 0 8px; }
/* AI suggested-reply modal */
.reply-task { background: var(--bg-2); border: 1px solid var(--border); border-radius: 9px; padding: 10px 12px; }
.reply-task-title { font-weight: 600; font-size: 13.5px; }
.reply-task-sub { font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }
.reply-text { background: var(--bg-2); border: 1px solid var(--border); color: var(--text); border-radius: 9px;
  padding: 11px 13px; font-size: 14px; font-family: inherit; line-height: 1.5; outline: none; resize: vertical; width: 100%; }
.reply-text:focus { border-color: var(--accent); }
.reply-steer { background: var(--bg-2); border: 1px solid var(--border); color: var(--text); border-radius: 9px;
  padding: 9px 12px; font-size: 13px; font-family: inherit; outline: none; width: 100%; }
.reply-steer:focus { border-color: var(--accent); }
.task-card-foot { display: flex; align-items: center; gap: 8px; }
.task-status-sel { background: var(--bg-2); border: 1px solid var(--border); color: var(--text); border-radius: 7px; padding: 4px 7px; font-size: 12px; font-family: inherit; }
.task-link { font-size: 12px; color: var(--accent-2); text-decoration: none; }
.task-link:hover { text-decoration: underline; }
.task-card-actions { margin-left: auto; display: flex; gap: 4px; }
.task-ico-btn { background: none; border: none; color: var(--text-faint); font-size: 13px; padding: 3px 5px; border-radius: 6px; }
.task-ico-btn:hover { background: var(--bg-hover); color: var(--text); }

/* status pills / dots */
.st-todo { color: var(--text-dim); } .bg-todo { background: #6b7280; }
.st-in_progress { color: var(--accent-2); } .bg-in_progress { background: var(--accent); }
.st-ready { color: #7bb5ff; } .bg-ready { background: #4f8ff7; }
.st-blocked { color: #f0a060; } .bg-blocked { background: #e0a458; }
.st-done { color: var(--ok); } .bg-done { background: var(--ok); }

/* AI follow-up outcome on a task card */
.task-res { border-radius: 8px; padding: 8px 10px; margin: 2px 0 8px; font-size: 12.5px; border: 1px solid var(--border); }
.task-res.draft { background: rgba(79, 143, 247, 0.10); border-color: rgba(79, 143, 247, 0.35); }
.task-res.done { background: rgba(80, 200, 120, 0.08); border-color: rgba(80, 200, 120, 0.28); }
.task-res.blocked { background: rgba(224, 164, 88, 0.08); border-color: rgba(224, 164, 88, 0.28); }
.task-res-head { font-weight: 600; display: block; }
.task-res-body { color: var(--text-dim); margin-top: 4px; white-space: pre-wrap; line-height: 1.45; }
.task-res-btn { margin-top: 8px; }

/* Auto follow-up modal */
.fu-toggle { display: flex; align-items: center; gap: 9px; font-size: 13.5px; margin: 12px 0; cursor: pointer; }
.fu-toggle input { width: 16px; height: 16px; }
.fu-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; }
.fu-status { font-size: 12px; color: var(--text-faint); margin-right: auto; }
.fu-tz-note { font-size: 12px; color: var(--text-dim); margin-top: 12px; }
.field-sub { color: var(--text-faint); font-weight: 400; font-size: 11.5px; font-style: normal; }
.tz-device-btn { display: inline-block; margin-top: 7px; font-size: 12px; text-align: left; }

/* saved-view tabs — each one is a row in `views`, not a hardcoded screen */
.tasks-views { display: flex; gap: 4px; flex-wrap: wrap; padding: 10px 0 0; }
.tasks-view-tab {
  background: none; border: 1px solid transparent; color: var(--text-dim);
  font-size: 13px; font-weight: 500; padding: 6px 13px; border-radius: 9px;
}
.tasks-view-tab:hover { background: var(--bg-2); color: var(--text); }
.tasks-view-tab.active { background: var(--accent-soft); border-color: var(--border); color: var(--text); }

/* project rail + board area */
.tasks-workspace { display: flex; gap: 16px; align-items: flex-start; }
.tasks-main { flex: 1; min-width: 0; }
.tasks-rail { width: 190px; flex-shrink: 0; display: flex; flex-direction: column; gap: 2px; }
.rail-head { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-faint); padding: 4px 8px 8px; }
.rail-item { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; color: var(--text-dim); font-size: 13px;
  padding: 7px 9px; border-radius: 8px; }
.rail-item:hover { background: var(--bg-2); color: var(--text); }
.rail-item.active { background: var(--accent-soft); color: var(--text); font-weight: 600; }
.rail-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-count { color: var(--text-faint); font-size: 11.5px; font-weight: 500; }
.rail-muted .rail-label { font-style: italic; }
.rail-new { background: none; border: none; color: var(--text-faint); font-size: 12.5px;
  text-align: left; padding: 8px 9px; border-radius: 8px; margin-top: 4px; }
.rail-new:hover { background: var(--bg-2); color: var(--text); }
.rail-trash { display: flex; align-items: center; gap: 8px; padding: 6px 9px;
  font-size: 12.5px; color: var(--text-dim); }

/* Command palette (Ctrl+K) */
#palette-overlay { align-items: flex-start; }
.palette {
  width: 620px; max-width: 92vw; margin-top: 12vh;
  background: var(--bg); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: 0 22px 60px rgba(0, 0, 0, .38); overflow: hidden;
  display: flex; flex-direction: column;
}
.palette-input {
  background: none; border: none; border-bottom: 1px solid var(--border);
  color: var(--text); font-size: 15px; font-family: inherit;
  padding: 15px 18px; outline: none; width: 100%;
}
.palette-results { max-height: 52vh; overflow-y: auto; padding: 6px; }
.palette-row {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  background: none; border: none; color: var(--text); padding: 9px 12px;
  border-radius: 9px; font-size: 13.5px;
}
.palette-row:hover, .palette-row.active { background: var(--accent-soft); }
.palette-ico { flex-shrink: 0; color: var(--text-faint); font-size: 13px; }
.palette-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.palette-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-hint { font-size: 11.5px; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-tag { font-size: 11px; color: var(--text-faint); flex-shrink: 0; font-family: var(--mono); }
.palette-empty { color: var(--text-faint); font-size: 12.5px; padding: 18px 14px; text-align: center; }
.palette-foot {
  border-top: 1px solid var(--border); padding: 8px 14px;
  font-size: 11px; color: var(--text-faint); display: flex; gap: 6px; align-items: center;
}
.palette-foot kbd {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 5px;
  padding: 1px 5px; font-family: var(--mono); font-size: 10.5px;
}
.tm-label { background: var(--accent-soft); border-radius: 6px; padding: 1px 7px; font-size: 11px; }
.tm-shared { color: var(--accent-2); font-weight: 600; }
@media (max-width: 860px) {
  .tasks-workspace { flex-direction: column; }
  .tasks-rail { width: 100%; flex-direction: row; flex-wrap: wrap; }
  .rail-head { display: none; }
  .rail-item { width: auto; }
}

/* a subtask card sits under its parent, in the same column */
.task-card-sub { margin-left: 14px; border-left: 2px solid var(--accent-soft); }
.task-parent-hint { font-size: 11px; color: var(--text-faint); margin-bottom: 4px; }
.task-card-title { cursor: pointer; }
.task-meta .tm-proj { background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 6px; padding: 1px 6px; font-size: 11px; }

/* The task number the drawer, the Inbox and the AI all refer to ("#217"). It was only
   ever on the panel, so a card the AI named by number was unfindable on the board. */
.task-num { color: var(--text-faint); font-weight: 600; font-variant-numeric: tabular-nums;
  font-size: .88em; margin-right: 6px; }
/* A description is Markdown; the card shows a stripped, capped preview of it, so keep
   it to two lines rather than letting one long paragraph own the card. */
.task-detail { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }
.tt-sub { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; }

/* Reveal: a row opened from a link is scrolled to and flashed, so the drawer and the
   board agree about which task is being talked about. */
@keyframes task-flash-ring {
  0%   { box-shadow: 0 0 0 0 var(--accent); }
  35%  { box-shadow: 0 0 0 3px var(--accent); }
  100% { box-shadow: 0 0 0 0 rgba(124, 140, 255, 0); }
}
.task-card.task-flash { animation: task-flash-ring 2.4s ease-out; border-color: var(--accent); }
tr.task-flash td { background: var(--accent-soft); }
tr.task-flash { animation: task-flash-ring 2.4s ease-out; }
/* Pinned = the running view does not select this row; it is on the page because a link
   asked for it. Dashed, so it never reads as a view that quietly stopped filtering. */
.task-card-pinned { border-style: dashed; }
tr.tt-pinned td { border-top: 1px dashed var(--accent-soft); }
.task-meta .tm-pinned { color: var(--accent-2); }

/* ---- task detail drawer: the thread, history and structure of one task */
.task-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: 440px; max-width: 92vw; z-index: 60;
  background: var(--bg); border-left: 1px solid var(--border);
  display: flex; flex-direction: column; box-shadow: -18px 0 40px rgba(0, 0, 0, .28);
}
.task-drawer.hidden { display: none; }
.task-drawer-head { display: flex; align-items: flex-start; gap: 10px;
  padding: 16px 16px 12px; border-bottom: 1px solid var(--border); }
.task-drawer-title { flex: 1; min-width: 0; }
.task-drawer-title h2 { font-size: 16px; line-height: 1.35; margin: 2px 0 0; }
.td-id { font-size: 11.5px; color: var(--text-faint); font-weight: 600; }
.task-drawer-head-actions { display: flex; gap: 2px; flex-shrink: 0; }
.task-drawer-body { flex: 1; overflow-y: auto; padding: 14px 16px; }
.td-props { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 14px; }
.td-prop { display: flex; flex-direction: column; gap: 4px; font-size: 11.5px; color: var(--text-faint); }
.td-prop select {
  background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 6px 8px; font-size: 12.5px; font-family: inherit; outline: none;
}
.td-prop select:focus { border-color: var(--accent); }
.td-value { color: var(--text); font-size: 12.5px; padding: 6px 0; }
/* The description is rendered Markdown now, so it must not also collapse its own
   whitespace (pre-wrap would double every gap the <p> margins already provide). */
.td-details { font-size: 13px; color: var(--text-dim); line-height: 1.6; margin-bottom: 16px; }
.td-details > *:first-child { margin-top: 0; }
.td-details > *:last-child { margin-bottom: 0; }
.td-details p { margin: 0 0 9px; }
.td-details ul, .td-details ol { margin: 0 0 9px; padding-left: 20px; }
.td-details li { margin: 3px 0; }
.td-details strong { color: var(--text); font-weight: 600; }
.td-details code {
  font-family: var(--mono); font-size: 12px; background: var(--bg-2);
  border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px;
}
.td-details pre {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 11px; overflow-x: auto; margin: 0 0 9px;
}
.td-details pre code { background: none; border: none; padding: 0; }
.td-details a { color: var(--accent-2); }
.td-details h1, .td-details h2, .td-details h3, .td-details h4 {
  font-size: 12.5px; font-weight: 600; color: var(--text); margin: 12px 0 5px;
  text-transform: uppercase; letter-spacing: .04em;
}
.td-details blockquote {
  margin: 0 0 9px; padding-left: 10px; border-left: 2px solid var(--border);
  color: var(--text-faint);
}
.td-details table.md-table { font-size: 12px; border-collapse: collapse; margin-bottom: 9px; }
.td-details table.md-table th, .td-details table.md-table td {
  border: 1px solid var(--border); padding: 4px 8px; text-align: left;
}
.td-section { margin-bottom: 18px; }
.td-section-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.td-section-head h3 { font-size: 11.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-faint); margin: 0; }
.td-empty { font-size: 12.5px; color: var(--text-faint); line-height: 1.5; }
.td-links { display: flex; flex-direction: column; gap: 5px; }
.td-link { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
.td-link .dotmark { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.td-link-title { flex: 1; text-align: left; background: none; border: none; color: var(--text);
  font-size: 12.5px; padding: 3px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.td-link-title:hover { color: var(--accent-2); text-decoration: underline; }
.td-link-who { color: var(--text-faint); font-size: 11.5px; flex-shrink: 0; }
.td-file-ico { flex-shrink: 0; font-size: 12px; }
a.td-link-title { text-decoration: none; }
/* A whole Inbox row is the target, so it needs the affordance a bare link has. */
.td-dupe-note { font-size: 12px; color: var(--text-faint); line-height: 1.5; margin: -2px 0 6px; }
.td-dupe-row { cursor: pointer; padding: 5px 6px; margin: 0 -6px; border-radius: 7px; }
.td-dupe-row:hover { background: var(--bg-hover); }
.td-dupe-row:hover .td-link-title { color: var(--accent-2); }
.td-inbox-row { cursor: pointer; padding: 5px 6px; margin: 0 -6px; border-radius: 7px; }
.td-inbox-row:hover { background: var(--bg-hover); }
.td-inbox-row:hover .td-link-title { color: var(--accent-2); }
.td-inbox-row.td-done .td-link-title { color: var(--text-dim); }
.td-inbox-row.td-done .td-file-ico { opacity: .5; }
/* Where the task came from — one quiet line under the description, not a field row. */
.td-source { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; margin: -6px 0 16px; }
.td-source-k { color: var(--text-faint); text-transform: uppercase; font-size: 10.5px; letter-spacing: .06em; }
.td-source-v { color: var(--text-dim); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.td-source-v { color: var(--accent-2); text-decoration: none; }
a.td-source-v:hover { text-decoration: underline; }
.td-tabs { margin-bottom: 4px; }
.td-thread { display: flex; flex-direction: column; gap: 10px; }
.td-msg { background: var(--bg-2); border: 1px solid var(--border); border-radius: 9px; padding: 9px 11px; }
.td-msg-ai { border-color: var(--accent-soft); }
.td-msg-head { display: flex; align-items: center; gap: 6px; font-size: 12px;
  font-weight: 600; color: var(--text-dim); margin-bottom: 4px; }
.td-msg-time { margin-left: auto; font-weight: 400; color: var(--text-faint); font-size: 11px; }
/* Markdown now, so the renderer owns the line breaks — pre-wrap on top of <p> tags
   would double every blank line. */
.td-msg-body { font-size: 13px; line-height: 1.55; }
.td-msg-body > *:first-child { margin-top: 0; }
.td-msg-body > *:last-child { margin-bottom: 0; }
.td-msg-body p { margin: 0 0 8px; }
.td-msg-body ul, .td-msg-body ol { margin: 0 0 8px; padding-left: 20px; }
.td-msg-body li { margin: 3px 0; }
.td-msg-body strong { color: var(--text); font-weight: 600; }
.td-msg-body a { color: var(--accent-2); text-decoration: none; font-weight: 600; }
.td-msg-body a:hover { text-decoration: underline; }
.td-history { display: flex; flex-direction: column; gap: 2px; }
.td-event { display: flex; align-items: baseline; gap: 8px; font-size: 12px;
  color: var(--text-dim); padding: 5px 0; border-bottom: 1px solid var(--border); }
.td-event:last-child { border-bottom: none; }
.td-event-what { flex: 1; }
.td-event-who { color: var(--text-faint); font-size: 11.5px; }
.td-event-time { color: var(--text-faint); font-size: 11px; flex-shrink: 0; }
.task-drawer-foot { display: flex; gap: 8px; align-items: flex-end;
  padding: 12px 16px; border-top: 1px solid var(--border); }
.task-drawer-foot.hidden { display: none; }
.task-drawer-foot textarea {
  flex: 1; background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 9px; padding: 9px 11px; font-size: 13px; font-family: inherit;
  line-height: 1.45; outline: none; resize: none;
}
.task-drawer-foot textarea:focus { border-color: var(--accent); }

/* table */
.tasks-table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 12px; }
table.tasks-table { width: 100%; border-collapse: collapse; font-size: 13px; }
table.tasks-table th { text-align: left; color: var(--text-faint); font-weight: 600; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em; padding: 10px 12px; border-bottom: 1px solid var(--border); }
table.tasks-table td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
table.tasks-table tr:last-child td { border-bottom: none; }
table.tasks-table tr:hover td { background: var(--bg-2); }
.tt-title { font-weight: 600; cursor: pointer; }
.tt-title:hover { color: var(--accent-2); }
.tt-sub { color: var(--text-faint); font-size: 11.5px; }
.field-row { display: flex; gap: 12px; }
.field-row .field { flex: 1; }

/* markdown tables in chat */
.md-table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 8px 0; }
.md-table th { text-align: left; color: var(--text-faint); font-weight: 600; font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em; padding: 8px 10px; border-bottom: 1px solid var(--border); background: var(--bg-2); }
.md-table td { padding: 8px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
.md-table tr:last-child td { border-bottom: none; }
.md-table tr:hover td { background: var(--bg-2); }
/* A quoted block — a draft email, a message, a snippet the AI wants read as itself —
   and the rule a model writes to fence one off. `.msg-content blockquote` (top of this
   file) was styled for years for something that could never render: renderMarkdown
   escaped `>` to `&gt;` before its own line-anchored blockquote rule looked at the line.
   Now that quotes reach the page, every OTHER markdown surface needs the same styling,
   or one draft reads as a quote in chat and as unindented prose in an Inbox card. */
.md blockquote, .digest blockquote, .inbox-result blockquote, .pl-body blockquote,
.thr-tx blockquote, .thr-post-tx blockquote {
  border-left: 3px solid var(--accent); margin: 8px 0; padding: 2px 14px; color: var(--text-dim);
}
/* A quote now HOLDS paragraphs, so its first child's top margin is a gap above the
   quote's own first line. */
.msg-content blockquote > :first-child, .md blockquote > :first-child,
.digest blockquote > :first-child, .inbox-result blockquote > :first-child,
.pl-body blockquote > :first-child { margin-top: 0; }
.msg-content hr, .md hr, .inbox-result hr, .pl-body hr,
.thr-tx hr, .thr-post-tx hr {
  border: none; border-top: 1px solid var(--border); margin: 14px 0;
}


/* ===== Router Pool ===== */
.router-pool-entry { display: flex; align-items: center; gap: 8px; padding: 8px; background: var(--bg-2); border-radius: 8px; margin-bottom: 6px; }
.router-pool-entry .priority { font-weight: 600; color: var(--accent); min-width: 20px; }
.router-pool-entry .provider { font-size: 12px; color: var(--text-faint); }
.router-pool-entry .model { flex: 1; font-weight: 500; }
.router-pool-entry .tags { font-size: 11px; color: var(--text-faint); }
.router-pool-entry .controls { display: flex; gap: 4px; }
.router-pool-entry button { background: transparent; border: none; cursor: pointer; padding: 2px 6px; border-radius: 4px; }
.router-pool-entry button:hover { background: var(--bg-3); }
.router-controls { display: flex; gap: 8px; margin-top: 10px; align-items: center; }

/* ===== Model Badge ===== */
.model-badge { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-faint); background: var(--bg-2); padding: 2px 8px; border-radius: 10px; margin-bottom: 6px; }
.model-badge::before { content: "🤖"; }

/* ===== Auth / onboarding ===== */
.auth-overlay {
  position: fixed; inset: 0; z-index: 200; background: var(--bg);
  display: flex; align-items: center; justify-content: center; padding: 24px; overflow-y: auto;
}
.auth-card {
  width: 100%; max-width: 420px; background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 18px; padding: 26px 26px 22px; display: flex; flex-direction: column; gap: 14px;
}
.auth-brand { display: flex; align-items: center; gap: 11px; }
.auth-brand .brand-logo { font-size: 30px; }
.auth-brand strong { font-size: 16px; display: block; }
.auth-brand small { color: var(--text-faint); font-size: 12px; }
.auth-pane { display: flex; flex-direction: column; gap: 13px; }
.auth-pane h2 { margin: 4px 0 0; font-size: 19px; }
.wiz-step { display: flex; flex-direction: column; gap: 13px; }
.auth-card .field > span { font-size: 12.5px; }
.auth-card input, .auth-card select, .auth-card textarea {
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 9px; padding: 11px 12px; font-size: 14px; font-family: inherit; width: 100%; outline: none;
}
.auth-card input:focus, .auth-card select:focus, .auth-card textarea:focus { border-color: var(--accent); }
.auth-btn { width: 100%; padding: 12px; font-size: 15px; margin-top: 4px; }
.auth-error { background: rgba(240,96,106,.1); border: 1px solid rgba(240,96,106,.35); color: #ffb3b8; border-radius: 9px; padding: 9px 12px; font-size: 13px; }
.auth-switch { text-align: center; color: var(--text-dim); font-size: 13px; margin: 4px 0 0; }
.wiz-nav { display: flex; gap: 10px; align-items: center; }
.wiz-nav .btn-mini { padding: 11px 16px; }
.wiz-nav .auth-btn { flex: 1; margin-top: 0; }
.auth-dots { display: flex; gap: 6px; justify-content: center; }
.auth-dots .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--bg-hover); }
.auth-dots .dot.on { background: var(--accent); }

/* reachability toggle. It has been in three places — the sidebar, then the top bar,
   now a field in Settings → General — so the base rule is the shape it takes anywhere
   and `#reach-field` below is the only place that adjusts it. */
.reach-switch {
  display: flex; justify-content: space-between; width: calc(100% - 8px);
  margin: 4px 4px 12px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--bg-2);
  transition: border-color .2s ease, background .2s ease;
}
.reach-switch:hover { background: var(--bg-hover); }
/* In Settings it sits inside a `.field`, under its own label — so it is as wide as its
   content and never stretches across the pane the way a full-width form control does. */
#reach-field { gap: 6px; }
#reach-field .reach-switch { align-self: flex-start; width: auto; margin: 0; gap: 10px; padding: 6px 12px; border-radius: 999px; }
.field .aios-switch input[type=checkbox] {
  position: absolute; opacity: 0; width: 0; height: 0; padding: 0; border: 0; background: none;
}
.reach-switch .sw-label { font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
/* ON = online (green) */
.reach-switch.on { background: rgba(34, 197, 94, .1); border-color: rgba(34, 197, 94, .4); }
.reach-switch.on .sw-label { color: #4ade80; }
/* starting / error = amber (overrides the checked-green track) */
.reach-switch.warn { border-color: rgba(245, 158, 11, .35); }
.reach-switch.warn input:checked + .track { background: #f59e0b; }
.reach-switch.warn .sw-label { color: #fbbf24; }

/* account row + logout */
.account-row { display: flex; align-items: center; gap: 6px; border-top: 1px solid var(--border);
  flex-shrink: 0; margin: 0 -14px -14px; padding: 8px 14px 14px; background: var(--bg-2); }
.account-row .account { flex: 1; border-top: none; margin-top: 0; padding-top: 6px; }
.logout-btn { background: none; border: 1px solid var(--border); color: var(--text-faint); border-radius: 9px; padding: 8px 11px; font-size: 15px; flex-shrink: 0; }
.logout-btn:hover { color: var(--danger); border-color: rgba(240,96,106,.4); background: rgba(240,96,106,.1); }
/* Persistent rail chrome (Company / Group Chat / Inbox / Tasks / Calendar): the brand
   pinned at the top and the user profile at the bottom. These views hide the app
   sidebar, which is where the brand lives, so the rail standing in for it carries the
   same lockup in the same corner. `:empty` is kept as a guard — a rail that mounts the
   hook and renders nothing must take no room rather than draw an empty bordered strip. */
.rail-chrome-top:empty { display: none; }
.rail-chrome-top { position: sticky; top: 0; z-index: 3; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 12px 10px; border-bottom: 1px solid var(--border); background: var(--bg-2); flex-shrink: 0; }
.rail-chrome-top .brand-logo { font-size: 22px; display: inline-flex; align-items: center; }
.rail-chrome-top .wordmark { font-size: 15px; }
.rail-foot.account-row { position: sticky; bottom: 0; z-index: 3; margin: 0; border-top: 1px solid var(--border);
  background: var(--bg-2); padding: 8px 12px 12px; flex-shrink: 0; }
.cn-rail .rail-chrome-top { margin: -18px -14px 4px; }
.cn-rail .rail-foot.account-row { margin: 4px -14px -18px; }
/* recents "see more / show less" toggle */
.conv-more { width: 100%; text-align: left; background: none; border: none; color: var(--accent-2);
  font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 7px 10px; border-radius: 8px; }
.conv-more:hover { background: var(--bg-hover); }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 8px; border: 2px solid var(--bg-2); }
::-webkit-scrollbar-thumb:hover { background: var(--bg-hover); }

/* ---- Agent Organization (P2P v2) ---- */
.org-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 22px; align-items: start; }
@media (max-width: 880px) { .org-grid { grid-template-columns: 1fr; } }
.org-chart { display: flex; flex-direction: column; gap: 6px; }
.org-node { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 8px 11px; background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; }
.org-node-name { font-weight: 600; }
.org-node-lvl { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); background: rgba(122,108,255,.12); padding: 2px 7px; border-radius: 20px; }
.org-node-dom { font-size: 12px; color: var(--text-faint); }
.org-consult-form { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.org-consult-form label { font-size: 12px; color: var(--text-faint); margin-top: 4px; }
.org-consult-form select, .org-consult-form textarea { width: 100%; background: var(--bg-2); border: 1px solid var(--border); color: var(--text); border-radius: 9px; padding: 9px 11px; font: inherit; }
.org-consult-form textarea { resize: vertical; }
.org-consult-actions { display: flex; gap: 8px; margin-top: 8px; }
.org-consult-head { font-size: 13px; color: var(--text-faint); border-bottom: 1px solid var(--border); padding-bottom: 8px; margin-bottom: 10px; }
.org-consult-head b { color: var(--text); }
.org-peers { margin-top: 26px; border-top: 1px solid var(--border); padding-top: 18px; }
.peers-self { background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin-bottom: 12px; }
.peers-self-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.peers-label { font-size: 12px; color: var(--text-faint); margin-bottom: 4px; }
.peer-token { font-family: ui-monospace, monospace; font-size: 12px; color: var(--accent); word-break: break-all; }
.peers-token-wrap { flex: 1; min-width: 180px; }
.peer-token-input { width: 100%; margin-top: 4px; background: var(--bg-3); border: 1px solid var(--border); color: var(--text); border-radius: 8px; padding: 7px 10px; font: 12px ui-monospace, monospace; }
.peers-self-actions { display: flex; gap: 6px; }
.peers-hint { font-size: 12px; color: var(--text-faint); margin-top: 8px; }
.peers-hint code { color: var(--text); }
.peers-add { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.peers-add input { flex: 1; min-width: 130px; background: var(--bg-2); border: 1px solid var(--border); color: var(--text); border-radius: 9px; padding: 9px 11px; font: inherit; }
.peers-list { display: flex; flex-direction: column; gap: 6px; }
.peer-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 8px 11px; background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; }
.peer-row .peer-name { font-weight: 600; }
.peer-row .peer-url { font-size: 12px; color: var(--text-faint); flex: 1; }
.peer-state { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint); }
.peer-state.on { color: #46c98b; }
.peers-discovered { margin-bottom: 12px; }
.peers-disc-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.peer-row.found { border-style: dashed; }
.peer-row.found .btn-mini { flex-shrink: 0; }

/* Agent Organization onboarding + members */
.org-onboarding { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 22px; }
.org-choice-card { background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px; padding: 22px; }
.org-choice-ico { width: 44px; height: 44px; border-radius: 12px; background: var(--accent); color: var(--on-accent); display: flex; align-items: center; justify-content: center; font-size: 22px; margin-bottom: 14px; }
.org-choice-card h2 { margin: 0 0 8px; font-size: 18px; }
.org-choice-card p { margin: 0 0 18px; color: var(--text-faint); font-size: 13px; }
.org-choice-form { display: flex; flex-direction: column; gap: 6px; }
.org-choice-form label { font-size: 12px; color: var(--text-faint); margin-top: 4px; }
.org-choice-form input { width: 100%; background: var(--bg-1); border: 1px solid var(--border); color: var(--text); border-radius: 9px; padding: 9px 11px; font: inherit; }
.org-choice-form button { margin-top: 10px; }
.org-members { display: flex; flex-direction: column; gap: 8px; }
.org-member-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 12px; background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; }
.org-member-name { font-weight: 600; }
.org-member-role { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; padding: 2px 7px; border-radius: 20px; }
.org-member-role.owner { color: var(--on-accent); background: var(--accent); }
.org-member-role.admin { color: var(--accent); background: rgba(122,108,255,.12); }
.org-member-role.member { color: var(--text-faint); background: rgba(128,128,128,.12); }
.org-member-extra { font-size: 12px; color: var(--text-faint); margin-left: auto; }

/* Simplified Agent Organization: profile + network + skills */
.org-profile { margin-bottom: 10px; }
.org-profile-card { background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px; padding: 18px; max-width: 520px; }
.org-profile-head { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
.org-avatar { width: 52px; height: 52px; border-radius: 50%; background: var(--accent); color: var(--on-accent); display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; flex-shrink: 0; }
.org-avatar-mini { width: 38px; height: 38px; border-radius: 50%; background: rgba(122,108,255,.15); color: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; flex-shrink: 0; }
.org-profile-name { font-size: 17px; font-weight: 600; }
.org-profile-meta { font-size: 13px; color: var(--text-faint); }
.org-network { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.org-network-card { background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }
.org-network-card.remote { border-left: 3px solid var(--accent); }
.org-network-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.org-network-name { font-weight: 600; }
.org-network-meta { font-size: 12px; color: var(--text-faint); }
.org-skill-list { display: flex; flex-wrap: wrap; gap: 6px; }
.org-skill { font-size: 12px; background: rgba(122,108,255,.12); color: var(--accent); padding: 4px 10px; border-radius: 20px; }
.org-skill.none { background: rgba(128,128,128,.1); color: var(--text-faint); }

/* Agent Organization tabs */
.org-tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--border); margin-bottom: 22px; }
.org-tab { background: transparent; border: none; color: var(--text-faint); padding: 10px 16px; font: inherit; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.org-tab:hover { color: var(--text); }
.org-tab.active { color: var(--text); border-bottom-color: var(--accent); }

/* Agent Organization messages */
.org-messages-layout { display: flex; gap: 14px; height: 520px; max-height: 70vh; }
.org-message-peers { width: 220px; flex-shrink: 0; display: flex; flex-direction: column; gap: 6px; overflow-y: auto; }
.org-message-peer { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; cursor: pointer; }
.org-message-peer:hover { background: var(--bg-hover); }
.org-message-peer.active { border-color: var(--accent); background: var(--accent-soft); }
.org-message-peer .peer-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); }
.org-message-peer.enabled .peer-dot { background: var(--ok); }
.org-message-peer-name { font-weight: 600; font-size: 13px; }
.org-message-peer-url { font-size: 11px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; }
.org-message-thread { flex: 1; display: flex; flex-direction: column; background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
.org-message-header { padding: 12px 14px; border-bottom: 1px solid var(--border); font-weight: 600; }
.org-message-header small { display: block; color: var(--text-faint); font-weight: 400; font-size: 12px; margin-top: 2px; }
.org-message-list { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.org-message-empty { flex: 1; display: flex; align-items: center; justify-content: center; color: var(--text-faint); font-size: 14px; }
.org-message-bubble { max-width: 78%; padding: 10px 12px; border-radius: 12px; font-size: 13.5px; line-height: 1.45; }
.org-message-bubble.sent { align-self: flex-end; background: var(--user-bubble); border-bottom-right-radius: 4px; }
.org-message-bubble.received { align-self: flex-start; background: var(--bg-3); border-bottom-left-radius: 4px; }
.org-message-bubble .msg-time { display: block; font-size: 11px; color: var(--text-faint); margin-top: 6px; cursor: help; }
.org-message-bubble.delivery-fail { opacity: .7; border: 1px solid var(--danger); }
.org-message-input { display: flex; gap: 8px; padding: 12px 14px; border-top: 1px solid var(--border); }
.org-message-input input { flex: 1; background: var(--bg-3); border: 1px solid var(--border); color: var(--text); border-radius: 9px; padding: 9px 11px; font: inherit; }
.org-message-input button { flex-shrink: 0; }

/* Admin-controlled org settings */
.org-settings { display: flex; flex-direction: column; gap: 22px; }
.org-settings-section { background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
.org-settings-section .side-section-title { margin: 0 0 10px; }
.invite-create { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.invite-create select,
.invite-create input { background: var(--bg-3); border: 1px solid var(--border); color: var(--text); border-radius: 8px; padding: 8px 10px; font: inherit; }
.invite-create input { width: 90px; }
.invite-create button { flex-shrink: 0; }
.invites-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.invites-table th { text-align: left; color: var(--text-faint); font-weight: 500; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.invites-table td { padding: 10px; border-bottom: 1px solid var(--border); }
.invites-table code { font-size: 12px; background: var(--bg-3); padding: 3px 6px; border-radius: 5px; }
.org-members-list { display: flex; flex-direction: column; gap: 8px; }
.org-member-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 12px; background: var(--bg-3); border: 1px solid var(--border); border-radius: 10px; }
.org-member-row select { background: var(--bg-2); border: 1px solid var(--border); color: var(--text); border-radius: 6px; padding: 5px 8px; font: inherit; }
.btn-danger { background: var(--danger); color: #fff; border: none; }
.btn-danger:hover { filter: brightness(1.1); }

/* ---- Agent Swarm (Kimi-style agent boxes) ---- */
.swarm-head { margin-bottom: 14px; }
.org-swarm { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
.swarm-card {
  background: linear-gradient(145deg, var(--bg-2), #1a1c24);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: transform .12s, box-shadow .12s, border-color .12s;
  position: relative;
  overflow: hidden;
}
.swarm-card:hover { transform: translateY(-3px); border-color: var(--accent); box-shadow: 0 8px 24px rgba(0,0,0,.35); }
.swarm-card.remote { border-left: 3px solid var(--accent); }
.swarm-card .swarm-glow {
  position: absolute; top: -30px; right: -30px; width: 80px; height: 80px;
  border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,.12), transparent 70%);
  pointer-events: none;
}
.swarm-head-row { display: flex; align-items: center; gap: 12px; }
.swarm-avatar {
  width: 44px; height: 44px; border-radius: 12px;
  background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  font-size: 20px; flex-shrink: 0;
}
.swarm-title { font-weight: 600; font-size: 15px; }
.swarm-peer { font-size: 11px; color: var(--text-faint); }
.swarm-type {
  align-self: flex-start; font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--accent); background: rgba(122,108,255,.12); padding: 3px 8px; border-radius: 20px;
}
.swarm-skills { display: flex; flex-wrap: wrap; gap: 5px; min-height: 28px; }
.swarm-skill { font-size: 11px; background: var(--bg-3); color: var(--text-dim); padding: 3px 8px; border-radius: 20px; }
.swarm-actions { display: flex; gap: 6px; margin-top: auto; }
.swarm-actions button { flex: 1; font-size: 12px; padding: 7px 0; border-radius: 8px; }

/* ---- A2A Feed (observed external consultations) ---- */
.org-feed { display: flex; flex-direction: column; gap: 10px; }
.feed-empty { color: var(--text-faint); padding: 30px; text-align: center; }
.feed-item {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px;
  overflow: hidden; cursor: pointer; transition: border-color .12s;
}
.feed-item:hover { border-color: var(--accent); }
.feed-item.incoming { border-left: 3px solid var(--ok); }
.feed-item.outgoing { border-left: 3px solid var(--accent); }
.feed-summary { display: flex; align-items: center; gap: 12px; padding: 12px 14px; }
.feed-icon { font-size: 18px; flex-shrink: 0; }
.feed-main { flex: 1; min-width: 0; }
.feed-task { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.feed-meta { font-size: 12px; color: var(--text-faint); }
.feed-time { font-size: 11px; color: var(--text-faint); flex-shrink: 0; }
.feed-transcript {
  background: #0b0c10; border-top: 1px solid var(--border);
  padding: 14px; font-size: 13px; line-height: 1.55;
  max-height: 360px; overflow-y: auto; white-space: pre-wrap;
}

/* ---- Connections sidebar section ---- */
.connection-row {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 7px 8px;
  border-radius: 9px;
  color: var(--text);
  font-size: 13.5px;
  transition: background .15s;
}
.connection-row:hover { background: var(--bg-hover); }
.connection-row.online .connection-dot { background: var(--ok); }
.connection-info {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  cursor: pointer;
}
.connection-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-faint);
  flex-shrink: 0;
}
.connection-meta { flex: 1; min-width: 0; }
.connection-name {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.connection-url {
  font-size: 11px;
  color: var(--text-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.connection-badge {
  background: var(--danger);
  color: #fff;
  font-size: 10px;
  min-width: 16px;
  height: 16px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
  flex-shrink: 0;
}
.connection-disconnect {
  opacity: 0;
  background: none;
  border: none;
  color: var(--text-faint);
  font-size: 13px;
  padding: 3px 6px;
  border-radius: 6px;
  cursor: pointer;
  flex-shrink: 0;
}
.connection-row:hover .connection-disconnect { opacity: 1; }
.connection-disconnect:hover { color: var(--danger); background: rgba(240,96,106,.12); }
.connection-empty {
  color: var(--text-faint);
  font-size: 12px;
  padding: 8px 10px;
}

/* ---- Memory view ----
   Library layout (see "LIBRARY SHELL" further down, shared with Files): the type
   chips are the tabs, groups are sections of one table, and a memory is a ROW —
   text, type, when. The categories are unchanged; only the furniture is. */
.mem-count { font-size: 13px; color: var(--text-faint); white-space: nowrap; }
.mem-expand { flex-shrink: 0; }

/* Column template for a memory row. `.mem-acts` is a fixed slot so the columns do
   not jump sideways when the hover actions appear. */
/* The icon track and the tile are ONE number — a track narrower than the tile is how
   the tile ends up sitting in the text's column. */
.mem-list.lib-table { --lib-cols: 26px minmax(0, 1fr) 168px 96px 64px; }
.mem-groups-hint { font-size: 12px; color: var(--text-faint); padding: 0 0 8px; }

/* collapsible groups — flat sections, not cards */
.mem-group { border-bottom: 1px solid var(--border); }
.mem-group:last-child { border-bottom: 0; }
.mem-group-head {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: none; border: none; color: var(--text-dim); font-size: 12.5px; font-weight: 600;
  padding: 12px 10px; cursor: pointer; border-radius: 10px;
}
.mem-group-head:hover { background: var(--bg-2); color: var(--text); }
.mem-caret { color: var(--text-faint); font-size: 15px; transition: transform .15s ease; flex-shrink: 0; }
.mem-group.open .mem-caret { transform: rotate(90deg); }
.mem-group-ico { font-size: 14px; flex-shrink: 0; display: inline-flex; align-items: center; }
.mem-group-ico .ico { width: 15px; height: 15px; }
.mem-group-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mem-group-count {
  flex-shrink: 0; font-size: 11.5px; font-weight: 600; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.mem-group-body { display: none; flex-direction: column; padding: 0 0 6px; }
.mem-group.open .mem-group-body { display: flex; }

.mem-item {
  display: grid; grid-template-columns: var(--lib-cols); align-items: center; gap: 12px;
  padding: 9px 10px; border-radius: 10px; border-bottom: 1px solid var(--border);
  transition: background .12s;
}
.mem-item:last-child { border-bottom: 0; }
.mem-item:hover { background: var(--bg-2); }
/* Editing turns the row back into a block: a textarea, two selects and two buttons do
   not belong in a 1fr table cell. */
.mem-item:has(.mem-edit-input) { display: block; background: var(--bg-2); }
.mem-icon { --ico-sz: 26px; font-size: 15px; }
.mem-icon:not(.ico-tile) { display: inline-flex; align-items: center; justify-content: center; }
.mem-body { min-width: 0; }
.mem-text { font-size: 13.5px; line-height: 1.45; overflow-wrap: anywhere; }
.mem-cell {
  font-size: 12px; color: var(--text-faint); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mem-when { font-variant-numeric: tabular-nums; }
/* The stacked meta line only exists on narrow screens, where the columns fold away. */
.mem-meta { display: none; font-size: 11.5px; color: var(--text-faint); margin-top: 3px; }
.mem-acts { display: flex; align-items: center; justify-content: flex-end; gap: 2px; }
.mem-score { font-size: 11px; color: var(--accent-2); font-weight: 700; flex-shrink: 0; }
.mem-del, .mem-edit { flex-shrink: 0; opacity: 0; }
.mem-item:hover .mem-del, .mem-item:hover .mem-edit { opacity: 1; }
.mem-item:focus-within .mem-del, .mem-item:focus-within .mem-edit { opacity: 1; }
.mem-src { text-transform: none; opacity: .85; }
.mem-status { text-transform: capitalize; color: var(--warn); }

/* Coarse pointers never hover, so actions that fade in are actions they cannot reach. */
@media (hover: none) {
  .mem-del, .mem-edit { opacity: .55; }
}
/* Narrow: drop the columns and let the row carry its own meta line. */
@media (max-width: 760px) {
  .mem-list.lib-table { --lib-cols: 24px minmax(0, 1fr) 64px; }
  .mem-item > .mem-cell, .lib-thead > .mem-cell { display: none; }
  .mem-meta { display: block; }
}
/* The skill a memory was learned in — the one piece of provenance worth reading at a
   glance, so it gets the accent rather than the muted treatment `.mem-src` has. */
.mem-skill { text-transform: none; color: var(--accent); font-weight: 600; }
/* Type + skill pickers, shared by the edit row and the "+ Remember" form. */
.mem-edit-class { display: flex; gap: 8px; margin: 6px 0; flex-wrap: wrap; }
.mem-edit-class select {
  background: var(--bg-2); color: var(--fg); border: 1px solid var(--border);
  border-radius: 6px; padding: 4px 8px; font-size: 13px; max-width: 100%;
}
.mem-add {
  background: var(--bg-1); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px; margin-bottom: 10px;
}
.mem-add .mem-edit-input { width: 100%; }
.mem-edit-input { width: 100%; font: inherit; font-size: 14px; padding: 6px 8px; resize: vertical;
  background: var(--bg-1); color: var(--text); border: 1px solid var(--accent); border-radius: 8px; }
.mem-edit-actions { display: flex; gap: 6px; margin-top: 6px; }

/* ============================================================================
   AI OS Community Network — P2P community hub
   ========================================================================= */
.cn-hub {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr) 320px;
  background: var(--bg);
}
.cn-hub:has(#cn-detail.hidden) { grid-template-columns: var(--rail-w) minmax(0, 1fr) 0; }
.cn-empty { color: var(--text-faint); font-size: 13px; padding: 16px; line-height: 1.6; }
.cn-label { display: block; font-size: 12px; color: var(--text-faint); margin: 12px 0 5px; }
.cn-input {
  width: 100%; padding: 9px 11px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg); color: var(--text); font-size: 14px; margin-bottom: 4px;
  font-family: inherit; box-sizing: border-box;
}
.cn-input:focus { outline: none; border-color: var(--accent); }
.cn-input.cn-mono { font-family: var(--mono); font-size: 12.5px; }
.cn-icon-btn {
  background: none; border: 1px solid transparent; color: var(--text-dim);
  width: 28px; height: 28px; border-radius: 7px; cursor: pointer; font-size: 14px;
  display: inline-flex; align-items: center; justify-content: center;
}
.cn-icon-btn:hover { background: var(--bg-hover); color: var(--text); }

/* ---- left rail ---- */
.cn-rail {
  border-right: 1px solid var(--border); background: var(--bg-2);
  display: flex; flex-direction: column; padding: 18px 14px; gap: 14px; overflow-y: auto;
}
.cn-rail-head { display: flex; align-items: center; gap: 10px; }
/* "‹ Company" nav box at the top of the rail (mirrors group chat's "‹ Chats") */
.cn-rail-nav-head { display: flex; align-items: center; gap: 8px; margin-top: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--border); }
.cn-rail-nav-title { font-weight: 700; font-size: 15px; color: var(--text); }
.cn-logo {
  font-size: 20px; width: 40px; height: 40px; flex-shrink: 0; border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-3); border: 1px solid var(--border);
}
.cn-rail-title { font-weight: 700; font-size: 14.5px; }
.cn-rail-sub { font-size: 11.5px; color: var(--text-faint); }
.cn-identity-card { background: var(--bg-3); border: 1px solid var(--border); border-radius: 10px; padding: 11px 12px; }
.cn-id-name { font-weight: 600; font-size: 13.5px; display: flex; align-items: center; gap: 7px; }
.cn-id-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 8px var(--ok); }
.cn-id-row { font-size: 11.5px; color: var(--text-dim); margin-top: 5px; }
.cn-id-row code { background: var(--bg); padding: 1px 5px; border-radius: 5px; font-size: 11px; }
.cn-rail-section {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--text-faint); font-weight: 700;
  padding: 0 2px;
}
.cn-rail-list { display: flex; flex-direction: column; gap: 4px; }
.cn-rail-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 9px; border-radius: 9px;
  background: none; border: 1px solid transparent; cursor: pointer; text-align: left; width: 100%; color: var(--text);
}
.cn-rail-item:hover { background: var(--bg-hover); }
.cn-rail-item.active { background: var(--accent-soft); border-color: var(--accent); }
.cn-rail-item-avatar {
  width: 32px; height: 32px; flex-shrink: 0; border-radius: 9px; background: var(--bg-3);
  display: flex; align-items: center; justify-content: center; font-weight: 700; color: var(--accent-2);
}
.cn-rail-item-text { display: flex; flex-direction: column; min-width: 0; }
.cn-rail-item-name { font-weight: 600; font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cn-rail-item-meta { font-size: 11px; color: var(--text-faint); }
.cn-rail-actions { display: flex; flex-direction: column; gap: 7px; margin-top: auto; padding-top: 10px; }
.cn-btn-block {
  width: 100%; padding: 9px; border-radius: 9px; border: 1px solid var(--accent);
  background: var(--accent); color: var(--on-accent); font-weight: 600; font-size: 13px; cursor: pointer;
}
.cn-btn-block:hover { background: var(--accent-2); }
.cn-btn-block.ghost { background: transparent; color: var(--text-dim); border-color: var(--border); }
.cn-btn-block.ghost:hover { background: var(--bg-hover); color: var(--text); }
.cn-btn-block.danger { background: transparent; color: var(--danger); border-color: rgba(239,68,68,.45); }
.cn-btn-block.danger:hover { background: rgba(239,68,68,.12); }

/* ==== Company redesign: full-bleed space + sidebar nav + admin + org chart ==== */
/* The Company view REPLACES the app sidebar with its own rail (a ← Back returns). */
body.cn-fullbleed #sidebar { display: none; }
body.cn-fullbleed .sidebar-toggle { display: none; }

.cn-back-btn {
  display: inline-flex; align-items: center; gap: 7px; align-self: flex-start;
  background: none; border: none; color: var(--text-dim); font-size: 12.5px; font-weight: 600;
  padding: 4px 8px 4px 4px; margin: -2px 0 2px; border-radius: 8px; cursor: pointer;
}
.cn-back-btn:hover { background: var(--bg-hover); color: var(--text); }
.cn-back-arrow { font-size: 15px; line-height: 1; }
.cn-rail-head-text { flex: 1; min-width: 0; }
.cn-rail-refresh { flex: 0 0 auto; margin-left: auto; }

/* Primary + admin navigation in the rail */
.cn-nav { display: flex; flex-direction: column; gap: 2px; }
.cn-nav-item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: none; border: none; border-radius: 9px; padding: 8px 10px; cursor: pointer;
  color: var(--text-dim); font-size: 13.5px; font-weight: 500;
}
.cn-nav-item:hover { background: var(--bg-hover); color: var(--text); }
.cn-nav-item.active { background: var(--accent-soft); color: var(--text); font-weight: 600; }
.cn-nav-ico { width: 18px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; color: var(--text-dim); }
.cn-nav-item.active .cn-nav-ico { color: var(--text); }
.cn-nav-badge {
  margin-left: auto; background: var(--bg-3); color: var(--text-dim); border-radius: 999px;
  min-width: 18px; height: 18px; padding: 0 6px; font-size: 11px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
.cn-nav-item.active .cn-nav-badge { background: var(--accent); color: var(--on-accent); }
.cn-nav-subaction {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: none; color: var(--accent); font-size: 12.5px; font-weight: 600;
  text-align: left; padding: 7px 10px; cursor: pointer; border-radius: 9px;
}
.cn-nav-subaction .cn-nav-ico { color: var(--accent); }
.cn-nav-subaction:hover { background: var(--bg-hover); }
/* Teams-you're-in sub-list nested under the Teams nav item */
.cn-nav-teams { display: flex; flex-direction: column; gap: 1px; margin: 1px 0 1px 0; }
.cn-nav-team {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; border-radius: 8px; padding: 6px 10px 6px 34px; cursor: pointer;
  color: var(--text-faint); font-size: 12.5px;
}
.cn-nav-team:hover { background: var(--bg-hover); color: var(--text); }
.cn-nav-team.active { color: var(--text); background: var(--bg-3); }
.cn-nav-team-ico { flex: 0 0 auto; font-size: 12px; }
.cn-nav-team-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cn-nav-admin { display: flex; flex-direction: column; gap: 2px; padding-top: 8px; margin-top: 4px; border-top: 1px solid var(--border); }
.cn-nav-section-title {
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-faint); padding: 4px 10px 2px;
}

/* Admin sub-page header (one focused screen per admin nav item) */
.cn-admin-pagehead { margin: 2px 2px 14px; }
.cn-admin-pagehead h2 { margin: 0; font-size: 18px; }
.cn-admin-pagehead p { margin: 4px 0 0; color: var(--text-dim); font-size: 13px; max-width: 620px; line-height: 1.5; }

/* ---- Overview: the org hierarchy ---- */
.cn-overview { display: flex; flex-direction: column; gap: 18px; max-width: 960px; }
.cn-org-legend {
  display: flex; flex-wrap: wrap; gap: 14px; align-items: center; font-size: 12px;
  color: var(--text-dim); background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 12px; padding: 10px 14px;
}
.cn-org-leg { display: inline-flex; align-items: center; gap: 6px; }
.cn-org-legend .ico, .cn-org-tier-h .ico, .cn-org-team-h .ico {
  width: 14px; height: 14px; flex-shrink: 0; color: var(--text-faint);
}
.cn-org-tier { display: flex; flex-direction: column; gap: 9px; }
.cn-org-tier-h {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-dim);
}
.cn-org-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 10px; }
.cn-org-team .cn-org-row { grid-template-columns: 1fr; }
.cn-org-card { cursor: default; }
.cn-org-card:hover { border-color: var(--border); background: var(--bg-2); }
.cn-org-card.active:hover { border-color: var(--accent); background: var(--accent-soft); }
.cn-org-node {
  display: inline-flex; align-items: center; gap: 8px; background: var(--bg-2);
  border: 1px solid var(--border); border-radius: 11px; padding: 8px 12px; min-width: 0;
}
.cn-org-node.you { border-color: var(--accent); background: var(--accent-soft); }
.cn-org-kind { font-size: 15px; flex: 0 0 auto; }
.cn-org-name { font-weight: 600; font-size: 13.5px; white-space: nowrap; }
.cn-org-job { color: var(--text-faint); font-size: 12px; border-left: 1px solid var(--border); padding-left: 8px; }
.cn-org-teams { display: flex; flex-wrap: wrap; gap: 12px; }
.cn-org-team { background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px; padding: 12px 14px; min-width: 240px; }
.cn-org-team-h { display: flex; align-items: center; gap: 7px; font-weight: 700; font-size: 13.5px; margin-bottom: 9px; }
.cn-org-team .cn-org-node { background: var(--bg); }

/* ---- People & roles: Odoo-style directory → detail → org chart ---- */
.cn-people-wrap { display: flex; flex-direction: column; }
.cn-people-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.cn-people-head > div { min-width: 0; }
.cn-people-addbtn { flex: 0 0 auto; white-space: nowrap; }
.cn-ppl-addcol { max-width: 520px; }
.cn-ppl-addcol .cn-ppl-addsubmit { margin-top: 14px; }
/* Empty seat slots (People & roles) */
.cn-ppl-slot { border-style: dashed; align-items: center; }
.cn-ppl-slot:hover { border-color: var(--accent); background: var(--bg-hover); }
.cn-ppl-slot-plus {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 9px; display: inline-flex;
  align-items: center; justify-content: center; font-size: 22px; color: var(--text-dim);
  border: 1px dashed var(--border);
}
/* Person / AI segmented toggle on the add form */
.cn-seg { display: inline-flex; gap: 4px; background: var(--bg-3); border-radius: 10px; padding: 3px; margin-bottom: 16px; }
.cn-seg-btn { display: inline-flex; align-items: center; gap: 7px; border: none; background: none; padding: 7px 14px; border-radius: 8px; cursor: pointer; color: var(--text-dim); font-size: 13px; font-weight: 600; }
.cn-seg-btn .ico { width: 15px; height: 15px; flex-shrink: 0; }
.cn-seg-btn.active { background: var(--bg-2); color: var(--text); }
.cn-ppl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
.cn-ppl-card {
  display: flex; gap: 12px; text-align: left; background: var(--bg); border: 1px solid var(--border);
  border-radius: 12px; padding: 12px 14px; cursor: pointer; align-items: flex-start; width: 100%;
}
.cn-ppl-card:hover { border-color: var(--accent); background: var(--bg-hover); }
/* Circles, everywhere a person is shown. `50%` and not a pixel radius on purpose:
   these tiles are drawn at 24, 26, 30, 34, 38, 40 and 56px across seven contexts, and a
   fixed corner has to be re-tuned for each — which is how five of them came to disagree.
   A circle is the same shape at every size, so the size overrides carry only size. */
.cn-ppl-avatar {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; font-weight: 700; font-size: 16px;
  color: #fff; background: hsl(var(--ava-h, 260), 48%, 48%);
}
.cn-ppl-avatar.lg { width: 56px; height: 56px; font-size: 22px; }

/* A real picture rides INSIDE the disc rather than replacing it. The disc keeps the
   size, the circle and the hue, so a member with a photo and one without line up
   exactly at all seven sizes — and if the image 404s, the initial is still behind it
   instead of a broken-image glyph. `object-fit: cover` because a face is rarely
   square and letterboxing one inside a circle looks like a mistake. */
.cn-ppl-avatar { position: relative; overflow: hidden; }
.cn-ppl-avatar .av-img {
  /* No border-radius here on purpose. The parent is `position: relative` with
     `overflow: hidden`, so it already clips this to the circle — and `tests/
     test_member_avatars.py` reads the resolved cascade for anything naming
     `.cn-ppl-avatar`, where a second radius (even `inherit`) reads as a size override
     re-declaring a corner. That guard exists because five of these tiles once
     disagreed about their shape; a redundant declaration here is exactly the drift
     it is watching for. */
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
}
/* Drop the tinted background only once a picture is actually there — otherwise a
   slow image shows a hole where the initial used to be. */
.cn-ppl-avatar.has-img { background: var(--bg-3); }

/* ---- Settings -> Profile ---- */
.set-sep { border: none; border-top: 1px solid var(--border); margin: 22px 0 18px; }
.field-row { display: flex; align-items: center; gap: 12px; margin: 10px 0 4px; flex-wrap: wrap; }

.av-editor { display: flex; gap: 22px; align-items: flex-start; flex-wrap: wrap; }
.av-main { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.av-preview .cn-ppl-avatar { width: 88px; height: 88px; font-size: 34px; }
.av-main-actions { display: flex; gap: 8px; }

/* One tile per reaction. Fixed columns rather than auto-fill: the set is a known
   seven, and a grid that reflows to one column on a narrow modal turns a palette
   into a list. */
.av-slots { display: grid; grid-template-columns: repeat(3, 78px); gap: 10px; }
.av-slot {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 5px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 12px;
  padding: 9px 4px 7px; cursor: pointer; color: var(--text-dim); font: inherit;
}
.av-slot:hover { border-color: var(--accent); color: var(--text); }
.av-slot.has { border-color: var(--accent-soft); }
.av-slot-face {
  width: 40px; height: 40px; border-radius: 50%; overflow: hidden;
  display: flex; align-items: center; justify-content: center; font-size: 21px;
  background: var(--bg-2);
  /* Same reason as the emoji grid: --font leads with the UI stack, and where that
     resolves to a monochrome face first, half these tiles render as flat outlines. */
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", var(--font);
}
.av-slot-face img { width: 100%; height: 100%; object-fit: cover; display: block; }
.av-slot-label { font-size: 11px; font-weight: 600; }
.av-slot-x {
  position: absolute; top: -6px; right: -6px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--bg-1); border: 1px solid var(--border);
  font-size: 10px; line-height: 16px; text-align: center; color: var(--text-faint);
}
.av-slot-x:hover { color: var(--danger); border-color: var(--danger); }

/* The privacy line is a warning, not a hint: it is the only notice that a real face
   leaves the building, so it must not read like placeholder small print. */
.av-warn {
  font-size: 12.5px; line-height: 1.55; color: var(--warn);
  background: var(--warn-soft); border-radius: 10px;
  padding: 9px 12px; margin: 4px 0 12px;
}
.av-progress {
  font-size: 12.5px; line-height: 1.6; color: var(--text-dim);
  background: var(--bg-3); border-radius: 10px; padding: 10px 12px; margin-top: 10px;
}
.av-progress b { color: var(--text); }

/* The copy-paste prompt. A `pre` because every line break in it is meaningful — the
   numbered list is what makes ChatGPT do them one at a time — so it must not reflow. */
.diy-box { position: relative; }
.diy-prompt {
  margin: 0; padding: 12px 14px; max-height: 240px; overflow: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  font-family: var(--mono); font-size: 11.5px; line-height: 1.55; color: var(--text-dim);
  /* `pre-wrap`, not `pre`. The line breaks ARE meaningful — the numbered list is what
     makes ChatGPT do one picture at a time — but `pre` also refuses to wrap, so the
     box scrolled sideways and the prompt could not be read without dragging it. Keep
     the breaks, wrap the overflow. */
  white-space: pre-wrap; overflow-wrap: break-word; tab-size: 2;
  /* The copy button sits over the top-right corner, so keep text out from under it. */
  padding-right: 76px;
}
.diy-copy {
  position: absolute; top: 8px; right: 8px; z-index: 1;
  padding: 4px 12px; font-size: 12px; min-width: 62px;
}
.diy-note { margin-top: 8px; line-height: 1.7; }
.diy-steps {
  margin: 0 0 12px; padding-left: 20px;
  font-size: 12.5px; line-height: 1.75; color: var(--text-dim);
}
.diy-steps b { color: var(--text); }
/* The single most useful sentence on the page: you can stop after one picture. Given
   its own weight rather than buried in the small print under the box. */
.diy-lede {
  font-size: 12.5px; line-height: 1.65; color: var(--text-dim);
  background: var(--bg-3); border-radius: 10px; padding: 9px 12px; margin: 0 0 12px;
}
.diy-lede b { color: var(--text); }
/* Wider button now that it carries a full label, and the prompt must clear it. */
.diy-copy { min-width: 148px; }
.diy-prompt { padding-right: 162px; }

/* ---- message reactions ---- */
/* The strip sits in the message BODY, before the hover toolbar — the body is the
   bubble's own column and already aligns its children to the right edge for your
   messages and the left for the AI's, so this needs no side-specific rules. */
.msg-reactions { display: flex; gap: 5px; flex-wrap: wrap; margin-top: 5px; }
.rx-chip {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--bg-3); border: 1px solid var(--border); cursor: help;
  font-size: 14px; line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", var(--font);
}
.rx-chip.mine { border-color: var(--accent); }
.rx-face-wrap { width: 100%; height: 100%; border-radius: 50%; overflow: hidden;
  display: flex; align-items: center; justify-content: center; }
.rx-chip .rx-face { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The emoji badge rides on the corner of a real face. Without it two colleagues'
   expressions are only distinguishable by squinting at a 26px picture — the badge
   says WHICH feeling, the face says WHOSE. */
.rx-badge {
  position: absolute; right: -3px; bottom: -3px; font-size: 10px; line-height: 1;
  background: var(--bg-2); border-radius: 50%; padding: 1px;
}

/* The picker: one in the document, positioned against the viewport. */
.rx-picker {
  position: fixed; z-index: 120; display: flex; gap: 2px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 999px;
  padding: 5px 7px; box-shadow: var(--shadow-2);
}
.rx-pick {
  width: 32px; height: 32px; border: none; background: none; border-radius: 50%;
  cursor: pointer; font-size: 19px; line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", var(--font);
  transition: transform .12s ease;
}
.rx-pick:hover { transform: scale(1.25); background: var(--bg-hover); }
.rx-pick.on { background: var(--accent-soft); }

/* The same chips inside a Messages bubble. They sit AFTER the foot, so the bubble
   reads message -> time -> reactions, the order every chat app uses and the order
   people scan. Slightly smaller than in the AI chat: a room bubble is denser, and the
   badge is doing more of the work here because a colleague's face is always their
   neutral one (`peer_avatars` mirrors one picture per node). */
/* The hover strip, mirroring `.msg-acts` in the AI chat — same size, same icons, same
   appear-on-hover — because a person reacts on both surfaces and should not have to
   learn two gestures. In FLOW at the bottom of the bubble, never absolute: a button
   with a real rect is what the picker anchors to, and the ▾ button (absolute, in the
   corner) is what put the picker in the top-left of the window. */
.gc-msg-acts {
  display: flex; gap: 2px; height: 24px; margin: 2px 0 -4px;
  opacity: 0; visibility: hidden; transition: opacity .12s;
}
.cn-smsg:hover .gc-msg-acts,
.cn-smsg:focus-within .gc-msg-acts { opacity: 1; visibility: visible; }
/* Your own bubbles sit on the right, so their controls do too. */
.cn-smsg.mine .gc-msg-acts { justify-content: flex-end; }
.gc-msg-act {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; border-radius: 7px; cursor: pointer;
  background: transparent; border: 1px solid transparent; color: var(--text-faint);
}
.gc-msg-act:hover { background: var(--bg-3); border-color: var(--border); color: var(--text); }
.gc-msg-act svg { width: 13px; height: 13px; }

.gc-reactions { margin-top: 4px; gap: 4px; }
.gc-reactions .rx-chip { width: 22px; height: 22px; font-size: 12px; }
.gc-reactions .rx-badge { font-size: 9px; right: -2px; bottom: -2px; }
/* Your own reaction is ringed, the same signal as in the AI chat. */
.cn-smsg.mine .gc-reactions { justify-content: flex-end; }
.cn-ppl-info { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cn-ppl-name { font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cn-ppl-job { color: var(--text-dim); font-size: 12.5px; }
.cn-ppl-meta { color: var(--text-faint); font-size: 12px; }
.cn-ppl-card .cn-role-chips { margin-top: 4px; display: flex; flex-wrap: wrap; gap: 4px; }

.cn-ppl-back {
  background: none; border: none; color: var(--text-dim); font-size: 13px; font-weight: 600;
  cursor: pointer; padding: 4px 6px; border-radius: 8px; margin-bottom: 10px;
}
.cn-ppl-back:hover { background: var(--bg-hover); color: var(--text); }
.cn-ppl-detail { border: 1px solid var(--border); border-radius: 14px; background: var(--bg); overflow: hidden; }
.cn-ppl-dhead { display: flex; align-items: center; gap: 14px; padding: 18px 20px; border-bottom: 1px solid var(--border); background: var(--bg-2); }
.cn-ppl-dhead-text { flex: 1; min-width: 0; }
.cn-ppl-dname { font-size: 18px; font-weight: 700; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cn-ppl-djob { color: var(--text-dim); font-size: 13px; margin-top: 2px; }
.cn-ppl-dbody { display: grid; grid-template-columns: 1fr 328px; gap: 0; }
.cn-ppl-dcol { padding: 18px 20px; min-width: 0; }
.cn-ppl-dcol + .cn-ppl-dcol { border-left: 1px solid var(--border); }
.cn-ppl-org { background: var(--bg-2); }
.cn-ppl-field { margin-bottom: 18px; }
.cn-ppl-field .cn-role-opts { margin-top: 6px; }
/* Multi-role picker: a person may hold several job roles, one marked primary. */
.cn-jobrole-list { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; }
.cn-jobrole-row { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 6px; cursor: pointer; }
.cn-jobrole-row:hover { background: var(--bg-3); }
.cn-jobrole-row.pending { opacity: .75; font-style: italic; }
.cn-jobrole-name { flex: 1; min-width: 0; }   /* icon + gap: see .cn-role-optname below */
.cn-jobrole-primary { display: flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-faint); cursor: pointer; }

/* Company-authored job roles (Admin → Job roles). */
.cn-corole-row { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.cn-corole-row:last-child { border-bottom: 0; }
.cn-corole-main { flex: 1; min-width: 0; }
.cn-corole-name { display: flex; align-items: center; gap: 7px; font-weight: 600; }
.cn-corole-name .ico { width: 15px; height: 15px; flex-shrink: 0; color: var(--text-faint); }
.cn-corole-sub { font-size: 12px; color: var(--text-faint); }
.cn-corole-grid { display: grid; grid-template-columns: 2fr 70px 1.4fr; gap: 10px; }
.cn-role-form { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.cn-role-form .cn-label { display: block; margin-top: 12px; }
.cn-role-persona { min-height: 96px; font-family: inherit; resize: vertical; }
.cn-role-opt small { display: block; color: var(--text-faint); font-size: 11px; }
@media (max-width: 640px) { .cn-corole-grid { grid-template-columns: 1fr; } }

/* "What their AI can use" — the resolved effect of the selected role + grants. */
.cn-ppl-effective { padding-bottom: 16px; border-bottom: 1px solid var(--border); }
.cn-eff-row { display: flex; gap: 8px; align-items: baseline; margin: 6px 0; }
.cn-eff-row > div { display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
.cn-eff-label { flex: 0 0 74px; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint); }
.cn-org-chart-title { font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 12px; }
.cn-org-chain { display: flex; flex-direction: column; gap: 6px; }
.cn-org-line {
  display: flex; align-items: center; gap: 9px; padding: 7px 9px; border-radius: 10px;
  margin-left: calc(var(--d, 0) * 20px); border: 1px solid transparent;
}
.cn-org-line.clickable { cursor: pointer; }
.cn-org-line.clickable:hover { background: var(--bg-hover); }
.cn-org-line.self { background: var(--accent-soft); border-color: var(--accent); }
.cn-org-line .cn-ppl-avatar { width: 30px; height: 30px; font-size: 13px; }
.cn-org-line-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.cn-org-line-name { font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cn-org-line-job { color: var(--text-faint); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cn-org-count {
  flex: 0 0 auto; background: var(--bg-3); color: var(--text-dim); border-radius: 999px;
  min-width: 22px; height: 22px; padding: 0 6px; font-size: 11px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
@media (max-width: 820px) {
  .cn-ppl-dbody { grid-template-columns: 1fr; }
  .cn-ppl-dcol + .cn-ppl-dcol { border-left: none; border-top: 1px solid var(--border); }
}
/* Members & Teams reuse the People card look */
#cn-members-grid { display: block; }   /* inner .cn-ppl-grid drives the layout */
.cn-ppl-card.active { border-color: var(--accent); background: var(--accent-soft); }
.cn-member-tile:not([data-node]) { cursor: default; }
.cn-member-tile:not([data-node]):hover { border-color: var(--border); background: var(--bg); }
.cn-ppl-name .cn-presence { margin-right: 3px; }
.cn-relay-tag { font-size: 10px; opacity: .6; }
.cn-member-tile .cn-recruit-remove { margin-left: 8px; }
/* Team cards */
.cn-team-tile { align-items: center; }
.cn-team-ava {
  --ico-sz: 40px;
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 9px; display: inline-flex;
  align-items: center; justify-content: center; font-size: 20px;
}
.cn-team-ava:not(.ico-tile) { background: var(--bg-3); }
.cn-team-arrow { margin-left: auto; align-self: center; color: var(--text-faint); font-size: 20px; }
.cn-team-ava-stack { display: flex; margin-top: 5px; }
.cn-team-ava-stack .cn-ppl-avatar { width: 24px; height: 24px; font-size: 11px; margin-left: -6px; border: 2px solid var(--bg-2); }
.cn-team-ava-stack .cn-ppl-avatar:first-child { margin-left: 0; }
/* Team detail: members as user cards (no role/scope) */
.team-members-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 10px; margin-bottom: 14px; }
.team-member-card { align-items: center; }
/* The lead is the one member with a consequence attached, so the card says so. */
.team-member-card.is-lead { border-color: var(--accent); }
.team-lead-chip {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 1px 6px; border-radius: 999px; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.team-role-sel {
  font-size: 12px; padding: 2px 6px; border-radius: 8px; max-width: 140px;
  background: var(--bg-3); border: 1px solid var(--border); color: var(--text-dim);
}
.team-role-sel:hover { color: var(--text); border-color: var(--accent); }
.team-member-card .chip-x {
  margin-left: auto; flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%;
  border: none; background: none; color: var(--text-faint); font-size: 15px; line-height: 1;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
}
.team-member-card .chip-x:hover { background: rgba(239,68,68,.14); color: var(--danger); }
/* Dashed "add member" tile that matches the member cards */
.team-add-tile {
  position: relative; display: flex; align-items: center; gap: 10px; cursor: pointer;
  border: 1px dashed var(--border); border-radius: 12px; padding: 12px 14px;
  color: var(--text-dim); font-size: 13.5px; font-weight: 600; background: transparent;
}
.team-add-tile:hover { border-color: var(--accent); color: var(--text); background: var(--bg-hover); }
.team-add-plus {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 9px; display: inline-flex;
  align-items: center; justify-content: center; font-size: 20px; border: 1px dashed var(--border);
}
.team-add-tile .team-add-select { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }

/* The page header shows the active section name (Overview/Members/Teams);
   renderCN toggles .hidden for admin panes, which carry their own title. */
.cn-main-head h1 { font-size: 18px; }
/* People detail: role input + unified save row + field hint */
.cn-ppl-saverow { display: flex; align-items: center; gap: 12px; margin-top: 2px; }
.cn-field-hint { color: var(--text-faint); font-size: 12px; margin: 2px 0 8px; }
/* Monochrome badges. The desaturating filter that used to sit on .cn-team-ava,
   .cn-org-tier-h, .cn-org-legend, .cn-type-chip and .cn-you-badge is GONE: it existed to
   stop full-colour emoji shouting on a grey page, and there are no emoji left in them —
   drained of colour, the outline glyphs that replaced them lost the one thing carrying
   their meaning (rose person / violet AI). Anything still holding an emoji keeps the
   filter through the rule below, so nothing got louder. */
#view-community-network .cn-role-badge { background: var(--bg-3); color: var(--text-dim); border: 1px solid var(--border); }
/* The one emoji left on these pages is the one an ADMIN typed for their own role. It
   keeps the desaturation, because the reason for it — a full-colour picture shouting on
   a grey page — is exactly what a hand-picked 🎯 or 💰 would do. */
#view-community-network .cn-emoji { filter: grayscale(1); }
/* Sidebar icons: outline SVG (no fill) */
.cn-nav-ico svg, .cn-nav-team-ico svg { width: 17px; height: 17px; display: block; }
.cn-nav-section-title svg { width: 14px; height: 14px; }
.cn-logo svg { width: 22px; height: 22px; }
.cn-icon-btn svg, .cn-back-arrow svg { width: 15px; height: 15px; display: block; }
.cn-btn-block svg { width: 15px; height: 15px; vertical-align: -3px; margin-right: 4px; }
.cn-nav-section-title { display: flex; align-items: center; gap: 7px; }

/* ---- Modern toggle switch ---- */
.aios-switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; }
.aios-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.aios-switch .track {
  position: relative; flex: 0 0 auto; width: 38px; height: 22px; box-sizing: border-box;
  background: #3c3c3c; border-radius: 999px;
  transition: background .22s ease;
}
.aios-switch .track::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px;
  background: #f4f4f5; border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .5);
  transition: transform .24s cubic-bezier(.34, 1.35, .5, 1);
}
.aios-switch input:checked + .track { background: #22c55e; }
.aios-switch input:checked + .track::after { transform: translateX(16px); }
.aios-switch input:focus-visible + .track { outline: 2px solid var(--accent-2); outline-offset: 2px; }
.aios-switch input:disabled + .track { opacity: .55; cursor: default; }
.aios-switch .sw-label { font-size: 13px; font-weight: 600; color: var(--text); letter-spacing: .1px; }

/* ---- Settings pane ---- */
.cn-settings-pane { display: flex; flex-direction: column; gap: 16px; }

/* ---- Company usage dashboard (admin, company-wide) ---- */
.cn-usage-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 2px 0 14px; }
.cn-usage-seg { display: inline-flex; background: var(--bg-3); border: 1px solid var(--border); border-radius: 10px; padding: 2px; gap: 2px; }
.cn-usage-seg button { appearance: none; background: none; border: none; color: var(--text-dim); font: inherit; font-size: 12.5px; font-weight: 600; padding: 5px 11px; border-radius: 8px; cursor: pointer; }
.cn-usage-seg button:hover { color: var(--text); }
.cn-usage-seg button.on { background: var(--bg); color: var(--text); box-shadow: 0 1px 2px rgba(0,0,0,.12); }
.cn-usage-export { margin-left: auto; font-size: 12.5px; font-weight: 600; color: var(--text-dim); text-decoration: none; border: 1px solid var(--border); border-radius: 10px; padding: 6px 12px; }
.cn-usage-export:hover { color: var(--text); border-color: var(--accent); }
.cn-usage-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin: 22px 0 10px; border-bottom: 1px solid var(--border); }
.cn-usage-tab { appearance: none; background: none; border: none; border-bottom: 2px solid transparent; color: var(--text-dim); font: inherit; font-size: 13px; font-weight: 600; padding: 7px 10px; margin-bottom: -1px; cursor: pointer; }
.cn-usage-tab:hover { color: var(--text); }
.cn-usage-tab.on { color: var(--accent-2); border-bottom-color: var(--accent); }
.cn-usage-list { display: flex; flex-direction: column; }
.cn-usage-lrow { display: flex; align-items: center; gap: 12px; padding: 10px 4px; border-top: 1px solid var(--border); }
.cn-usage-lrow:first-child { border-top: none; }
.cn-usage-lmain { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.cn-usage-lname { font-size: 13.5px; font-weight: 600; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; word-break: break-word; }
.cn-usage-lmeta { font-size: 11.5px; opacity: .55; font-variant-numeric: tabular-nums; }
.cn-usage-lnum { text-align: right; font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; display: flex; flex-direction: column; gap: 1px; min-width: 104px; }
.cn-usage-lnum b { font-weight: 700; }
.cn-usage-lnum span { font-size: 11.5px; opacity: .55; }
.cn-usage-chip { font-size: 10.5px; font-weight: 600; letter-spacing: .02em; color: var(--text-faint); background: var(--bg-3); border-radius: 6px; padding: 1px 6px; }
.cn-usage-chip.cheap { color: var(--accent-2); background: var(--accent-soft); }
.cn-usage-chip.bad { color: var(--danger); background: var(--danger-soft); }
.cn-usage-stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; margin: 4px 0 2px; }
.cn-usage-stat { background: var(--bg); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; }
.cn-usage-stat-n { font-size: 1.7em; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.cn-usage-stat-l { opacity: .6; font-size: .78em; margin-top: 2px; }
.cn-usage-subhead { font-size: .76em; text-transform: uppercase; letter-spacing: .06em; opacity: .55; font-weight: 700; margin: 22px 0 8px; display: flex; align-items: center; gap: 8px; }
.cn-usage-count { background: var(--bg-3); color: var(--text-dim); border-radius: 9px; padding: 1px 7px; font-size: 11px; letter-spacing: 0; }
.cn-usage-members { display: flex; flex-direction: column; gap: 0; }
.cn-usage-mrow { display: flex; align-items: center; gap: 12px; padding: 9px 4px; border-top: 1px solid var(--border); }
.cn-usage-mrow:first-child { border-top: none; }
.cn-usage-mrow .cn-ppl-avatar { width: 32px; height: 32px; font-size: 13px; flex: 0 0 auto; }
.cn-usage-mmain { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.cn-usage-mname { font-size: 13.5px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.cn-usage-you { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; background: var(--accent-soft); color: var(--accent-2); border-radius: 6px; padding: 1px 6px; }
.cn-usage-pending { font-size: 10.5px; font-weight: 600; color: var(--text-faint); background: var(--bg-3); border-radius: 6px; padding: 1px 7px; }
.cn-usage-bar { height: 6px; border-radius: 4px; background: var(--bg-3); overflow: hidden; }
.cn-usage-bar > span { display: block; height: 100%; border-radius: 4px; background: var(--accent); min-width: 2px; }
.cn-usage-mnum { text-align: right; font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; display: flex; flex-direction: column; gap: 1px; min-width: 96px; }
.cn-usage-mnum b { font-weight: 700; }
.cn-usage-mturns { font-size: 11px; opacity: .55; }
.cn-usage-note { font-size: 12px; color: var(--text-faint); margin: 10px 0 0; }
.cn-usage-days { display: flex; flex-direction: column; gap: 0; }
.cn-usage-drow { display: flex; justify-content: space-between; gap: 12px; padding: 7px 4px; border-top: 1px solid var(--border); font-size: 13px; font-variant-numeric: tabular-nums; }
.cn-usage-drow:first-child { border-top: none; }
.cn-usage-drow > span:first-child { opacity: .8; }
.cn-usage-dcache { opacity: .6; min-width: 90px; text-align: right; }
.cn-usage-empty { opacity: .7; font-size: 13.5px; }
.cn-settings-card { background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
.cn-settings-card.danger { border-color: rgba(239,68,68,.3); }
/* Company → Skills — cards grouped by AUDIENCE.
   The groups are the model this page sets (who each skill is for), so they are the
   structure rather than a column: a card sits under Company / Team / Role /
   Individual and MOVES the moment its scope saves. Failure stays loud inside the
   group and gets its own filter, because five skills failing every morning with
   nobody told is why this page exists at all. */
/* The same two controls the library pages use — a search pill and a compact select.
   These are shared by Company → Skills and by Heartbeat's find bar, so they were the
   last place in the app still wearing the old square-input look. */
.skl-toolbar { display: flex; align-items: center; gap: 10px; margin: 2px 0 10px; flex-wrap: wrap; }
.skl-filter {
  flex: 1 1 220px; min-width: 0; max-width: 300px; height: 36px; padding: 0 14px;
  font: inherit; font-size: 13px; color: var(--text);
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 999px; outline: none;
}
.skl-filter::placeholder { color: var(--text-faint); }
.skl-filter:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.skl-alert { font-size: 12px; font-weight: 600; color: var(--danger); cursor: pointer;
  background: transparent; border: 1px solid transparent; border-radius: 999px; padding: 3px 10px; }
.skl-alert:hover { border-color: var(--danger); }
.skl-alert.on { background: color-mix(in srgb, var(--danger) 16%, transparent); border-color: var(--danger); }
.skl-allok { font-size: 12px; color: var(--text-faint); }
.skl-warn { font-size: 12px; color: var(--warn, #d98324); }

/* "Group by" — the same control on the three pages that list skills (Skills,
   Heartbeat, Company → Skills), so the way you re-cut a list is one thing to learn.
   Sized off .market-cat, which is the picker it sits beside on the Skills page. */
.skl-groupby { background: var(--bg-2); border: 1px solid var(--border); color: var(--text-dim);
  border-radius: 999px; height: 36px; padding: 0 12px; font: inherit; font-size: 13px; }
.skl-groupby:hover { color: var(--text); }
.skl-groupby:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* Heartbeat's find controls ride the right-hand end of the unread bar, so the count
   stays the first thing read on the page. */
.skl-find { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; }
.skl-find .skl-filter { flex: 0 1 240px; }

.skl-group { margin-top: 18px; }
.skl-group-h { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 0 2px 8px; border-bottom: 1px solid var(--border); margin-bottom: 12px; }
.skl-group-t { font-size: 13px; font-weight: 700; letter-spacing: .01em; }
.skl-group-n { font-size: 11px; color: var(--text-faint);
  background: var(--bg-3); border-radius: 999px; padding: 1px 7px; }
.skl-group-bad { font-size: 11px; font-weight: 600; color: var(--danger); }
.skl-group-hint { font-size: 11.5px; color: var(--text-faint); }

/* auto-fill, so the cards reflow instead of stretching to one per row on a wide screen */
.skl-cards { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); }
.skl-card {
  display: flex; flex-direction: column; gap: 8px; min-width: 0;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 13px 14px; border-left: 3px solid transparent;
}
.skl-card:hover { border-color: var(--text-faint); }
.skl-card.failing { border-left-color: var(--danger); background: rgba(239,68,68,.06); }
.skl-card.scheduled { border-left-color: var(--ok); }
.skl-card.off { opacity: .72; }
.skl-card.off:hover { opacity: 1; }
.skl-card-top { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; min-width: 0; }
/* The app's icon tile, at list size — the same glyph and hue this skill wears in the
   catalog, on Heartbeat and on its own page. */
.skl-card-icon { --ico-sz: 26px; font-size: 15px; line-height: 1; }
.skl-card-state { display: flex; flex-direction: column; gap: 2px; font-size: 12px; }
.skl-needs { color: var(--text-faint); font-size: 11px; }
.skl-card-controls { display: flex; gap: 8px; flex-wrap: wrap; margin-top: auto; padding-top: 4px; }
/* Wrap rather than shrink: three squeezed pickers turned a role name into
   "Product Data M". Two per line, the third drops under them. */
.skl-field { display: flex; flex-direction: column; gap: 3px; flex: 1 1 132px; min-width: 124px; }
.skl-field > span { font-size: 10px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-faint); }

/* State reads at a glance: a dot for the verdict, the detail under it, quietly. */
.skl-state { display: inline-flex; align-items: center; gap: 6px; color: var(--text-dim); }
.skl-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-faint); flex: none; }
.skl-state.ok { color: var(--ok); }
.skl-state.ok .skl-dot { background: var(--ok); }
.skl-state.bad { color: var(--danger); font-weight: 600; }
.skl-state.bad .skl-dot { background: var(--danger); }
.skl-state.off { color: var(--text-faint); }
.skl-state.off .skl-dot { background: transparent; border: 1px solid var(--text-faint); }
.skl-why { color: var(--text-faint); font-size: 11px; padding-left: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.skl-chip {
  font-size: 10px; text-transform: uppercase; letter-spacing: .04em; padding: 1px 6px;
  border-radius: 999px; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
/* Findings waiting on somebody reads as work, not as a feature label. */
.skl-chip.rows { color: var(--warn, #d98324);
  background: color-mix(in srgb, var(--warn, #d98324) 16%, transparent); }
/* What the skill is ABOUT is context, not status — it must not compete with the two
   chips that mean something is waiting. Shown only when the category is not already
   the heading above the card. */
.skl-chip.cat { color: var(--text-faint); background: var(--bg-3);
  text-transform: none; letter-spacing: 0; font-size: 10.5px; }
/* The name is a link into the skill; it must not look like a button. */
.skl-open { background: none; border: 0; padding: 0; font: inherit; font-weight: 600;
  font-size: 13.5px; color: inherit; cursor: pointer; text-align: left; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.skl-open:hover { text-decoration: underline; }
.skl-scope, .skl-ref, .skl-owner {
  width: 100%; font-size: 12px; padding: 4px 8px; border-radius: 8px;
  background: var(--bg-3); border: 1px solid var(--border); color: var(--text);
}
@media (max-width: 560px) {
  .skl-cards { grid-template-columns: 1fr; }
  .skl-card-controls { flex-direction: column; }
}
.cn-settings-h { font-weight: 700; font-size: 14px; margin-bottom: 6px; }
.cn-settings-card.danger .cn-settings-h { color: var(--danger); }
.cn-settings-desc { font-size: 13px; color: var(--text-dim); line-height: 1.5; margin: 0 0 12px; }
/* AI capabilities & limits card — label ↔ control rows */
.cn-ops-grid { display: flex; flex-direction: column; gap: 13px; margin: 6px 0 12px; }
.cn-ops-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.cn-ops-row > span { display: flex; flex-direction: column; font-size: 13.5px; line-height: 1.35; }
.cn-ops-row > span small { color: var(--text-dim); font-size: 11.5px; margin-top: 2px; }
.cn-ops-row .cn-input { flex: none; max-width: 240px; }
.cn-ops-row .cn-ops-num { max-width: 130px; }
@media (max-width: 560px) {
  .cn-ops-row { flex-direction: column; align-items: stretch; gap: 6px; }
  .cn-ops-row .cn-input, .cn-ops-row .cn-ops-num { max-width: none; }
}
/* "Which AI does which job" — one card per function, grouped by the part of the
   product a person recognises rather than by tier or by the module that implements it.
   Deliberately the same shape as Company → Skills (.skl-group-h / .skl-cards): both
   pages ask "here are N things, which ones do you want to change", and inventing a
   second card language for the same question is how a settings page stops feeling
   like one product.

   Each card carries what that function actually COST, because "should this run on the
   cheaper model" is unanswerable without knowing whether it runs twice a week or four
   hundred times. */
.cn-policy-group { margin-top: 18px; }
.cn-policy-group-head { display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; flex-wrap: wrap; padding: 0 2px 8px; border-bottom: 1px solid var(--border); }
.cn-policy-group-head b { font-size: 13px; font-weight: 700; letter-spacing: .01em; }
.cn-policy-group-count { font-size: 11px; color: var(--text-faint);
  background: var(--bg-3); border-radius: 999px; padding: 1px 8px; white-space: nowrap; }
/* Save — and the set-the-whole-group controls — sit in the group header, next to the
   group's own summary. The single button at the foot of the card is several screens from
   the switch you just flipped, and "put every background job on the cheaper model" is one
   decision rather than nine identical clicks. */
.cn-policy-group-actions { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cn-policy-group-save { padding: 3px 12px; font-size: 12px; line-height: 1.6; }
/* Deliberately NOT styled like the per-card segmented control: these are actions that
   move nine switches, not a display of what the group currently is. That is what the
   tally beside them is for. */
.cn-policy-bulk { display: inline-flex; border: 1px solid var(--border); border-radius: 8px;
  overflow: hidden; }
.cn-policy-bulk button { background: none; border: 0; color: var(--text-dim);
  font-size: 11.5px; padding: 4px 10px; cursor: pointer; font-family: inherit;
  white-space: nowrap; transition: background .12s, color .12s; }
.cn-policy-bulk button + button { border-left: 1px solid var(--border); }
.cn-policy-bulk button:hover { background: var(--bg-3); color: var(--text); }
.cn-policy-bulk-effort { width: auto; margin-bottom: 0; padding: 4px 8px; font-size: 11.5px; }
.cn-policy-group > small { display: block; color: var(--text-faint); font-size: 11.5px;
  margin: 6px 2px 0; }

/* auto-fill so the cards reflow instead of stretching to one per row on a wide screen */
.cn-policy-grid { display: grid; gap: 12px; margin: 12px 0 4px;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.cn-policy-card {
  display: flex; flex-direction: column; gap: 6px; min-width: 0;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 13px 14px; border-left: 3px solid transparent;
}
.cn-policy-card:hover { border-color: var(--text-faint); }
/* A card that is NOT on its default. Among 31 of them a deliberate choice is invisible
   otherwise — you would have to know every default by heart to spot one. The left edge
   carries state here exactly as it does on a skill card. */
.cn-policy-card.moved { border-left-color: var(--accent); }
.cn-policy-card-head { display: flex; align-items: center; gap: 2px; }
.cn-policy-card-head b { font-size: 13px; line-height: 1.3; }
/* Always in the DOM, shown only while the card is off its default — the dot has to be
   able to appear and disappear as switches move, without re-rendering the whole card
   (which would throw away every other unsaved change on the page). */
.cn-policy-moved { color: var(--accent); font-style: normal; margin-left: 6px;
  font-size: 15px; line-height: 0; display: none; }
.cn-policy-card.moved .cn-policy-moved { display: inline; }
.cn-policy-card > small { color: var(--text-faint); font-size: 11.5px; line-height: 1.4; }
.cn-policy-usage { display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; font-size: 11.5px; color: var(--text-dim); margin-top: 2px; }
.cn-policy-usage b { color: var(--text); font-size: 12px; }
.cn-policy-usage.quiet { color: var(--text-faint); font-style: italic; }
.cn-policy-card .cn-usage-bar { margin: 0; }
/* `margin-top: auto` bottom-aligns the controls: a card with no spend has no bar, so
   without it the switches sit at a different height from the card beside them and the
   row reads as ragged rather than as a set of comparable choices. */
.cn-policy-controls { display: flex; align-items: center; gap: 8px; margin-top: auto; padding-top: 8px; }
.cn-policy-controls .cn-input { flex: 1; min-width: 0; font-size: 12px; padding: 5px 8px; }

/* Segmented tier control. Radios, not a <select>: the whole point of a card is that its
   state is readable without opening anything first. */
.cn-policy-seg { display: inline-flex; flex: none; border: 1px solid var(--border);
  border-radius: 8px; overflow: hidden; }
.cn-policy-seg input { position: absolute; opacity: 0; pointer-events: none; }
.cn-policy-seg label { padding: 5px 10px; font-size: 12px; cursor: pointer; user-select: none;
  color: var(--text-dim); transition: background .12s, color .12s; }
.cn-policy-seg label + input + label { border-left: 1px solid var(--border); }
.cn-policy-seg input:checked + label { background: var(--accent); color: var(--on-accent); }
/* Keyboard users have to be able to see which segment holds focus. */
.cn-policy-seg input:focus-visible + label { outline: 2px solid var(--accent); outline-offset: -2px; }
@media (max-width: 560px) {
  .cn-policy-grid { grid-template-columns: 1fr; }
  .cn-policy-group-head { flex-direction: column; align-items: flex-start; gap: 6px; }
}
.mining-optout-row { display: flex; align-items: center; gap: 8px; }
.mining-optout-row input { width: auto; margin: 0; }
details.cn-settings-card > summary.cn-limits-summary { cursor: pointer; list-style: revert; user-select: none; }
details.cn-settings-card[open] > summary.cn-limits-summary { margin-bottom: 4px; }
.cn-invite-panel .cn-copyrow { margin-bottom: 12px; }
.cn-invite-panel code { font-size: 12.5px; color: var(--accent-2); word-break: break-all; }
.cn-invite-actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* ---- Add a teammate (admin-only, managed nodes) ---- */
.cn-team-seats { font-size: 12px; color: var(--text-faint); margin: -6px 0 12px; }
.cn-team-form { display: flex; flex-direction: column; }
.cn-team-form .cn-label:first-of-type { margin-top: 0; }
.cn-req { color: var(--danger); font-size: 11px; font-weight: 600; }
.cn-opt { color: var(--text-faint); font-size: 11px; font-weight: 400; }
.cn-team-submit { align-self: flex-start; margin-top: 14px; }
.cn-team-submit:disabled { opacity: .6; cursor: default; }
.cn-team-error { color: var(--danger); font-size: 12.5px; margin-top: 10px; }
.cn-team-result { margin-top: 14px; border: 1px solid var(--accent); border-radius: 10px; padding: 14px; background: var(--accent-soft); }
.cn-team-result-h { font-weight: 700; font-size: 13.5px; color: var(--accent-2); margin-bottom: 6px; }
.cn-team-result .cn-copyrow { margin-top: 8px; }
.cn-team-cred-label { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; min-width: 92px; }
.cn-team-ws { font-size: 12.5px; color: var(--text-dim); margin: 10px 0 0; }

/* ---- Teammates roster (admin-only convert person <-> AI) ---- */
.cn-members-list { display: flex; flex-direction: column; gap: 8px; }
.cn-member-row {
  display: flex; align-items: center; gap: 12px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px;
}
.cn-member-info { min-width: 0; flex: 1; }
.cn-member-line1 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cn-member-nm { font-weight: 600; font-size: 13.5px; color: var(--text); }
.cn-member-line2 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 3px; }
.cn-member-sub { font-size: 12px; color: var(--text-dim); word-break: break-all; }
.cn-member-job { font-size: 11px; color: var(--text-faint); }
.cn-member-job::before { content: "· "; }
.cn-member-act { flex-shrink: 0; }
.cn-member-act .cn-member-convert { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.cn-member-act .cn-member-convert:disabled { opacity: .7; cursor: default; }
.cn-type-chip {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10.5px; font-weight: 700; padding: 2px 7px; border-radius: 6px;
  text-transform: uppercase; letter-spacing: .03em; white-space: nowrap;
}
/* The glyph inside a chip is BARE — the chip is already the container, and a tile
   inside a tile is what made these read as buttons. */
.cn-type-chip .ico { width: 12px; height: 12px; flex-shrink: 0; }
.cn-type-chip.person, .cn-type-chip.virtual { background: var(--bg-3); color: var(--text-dim); }
.cn-type-chip.person .ico { color: #f2708a; }
.cn-type-chip.virtual .ico { color: #8b7ff8; }
.cn-member-status {
  font-size: 10.5px; font-weight: 600; padding: 1px 6px; border-radius: 6px;
  background: rgba(255,255,255,.06); color: var(--text-faint);
}
.cn-member-status.active { color: var(--ok); }

/* Per-member role assignment (Teammates card + Company Members grid) */
.cn-member-roles-line, .member-roles-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.cn-roles-label { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--text-faint); }
.cn-role-chips { display: inline-flex; gap: 5px; flex-wrap: wrap; }
/* A role chip carries the role's glyph, coloured by what the role is about (ic-* sets
   --ic) while the pill itself stays the accent wash. The text is the label — the glyph
   is what makes a column of eleven of them scannable, which is the whole reason a
   member card lists them at all. */
.cn-role-chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; background: var(--accent-soft); color: var(--accent-2);
  border-radius: 20px; padding: 3px 9px; white-space: nowrap;
}
.cn-role-chip .ico { width: 12px; height: 12px; flex-shrink: 0; color: var(--ic, currentColor); }
.cn-role-chip.empty { background: var(--bg-3); color: var(--text-faint); }
.cn-role-editor { flex-basis: 100%; margin-top: 8px; padding: 10px; background: var(--bg); border: 1px solid var(--border); border-radius: 10px; }
.cn-role-opts { display: flex; flex-direction: column; gap: 4px; max-height: 220px; overflow-y: auto; }
.cn-role-opt { display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 3px 4px; border-radius: 6px; cursor: pointer; }
/* The role's glyph in a picker row and in the job-role list — same rule for both, so a
   role looks the same whether you are choosing it or reading it back. */
.cn-role-optname, .cn-jobrole-name { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.cn-role-optname .ico, .cn-jobrole-name .ico {
  width: 14px; height: 14px; flex-shrink: 0; color: var(--ic, var(--text-faint));
}
.cn-role-opt:hover { background: var(--bg-2); }
.cn-role-bundle { font-size: 10.5px; color: var(--text-faint); margin-left: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%; }
.cn-role-editor-act { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.cn-role-msg { font-size: 11.5px; color: var(--danger, #e5534b); }

/* ---- project teams ---- */
.cn-team-new { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.cn-team-new input { flex: 1; min-width: 140px; }
.cn-teams-list { display: flex; flex-direction: column; gap: 12px; }
.cn-team-block {
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
}
.cn-team-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cn-team-head .cn-team-del { margin-left: auto; }
.cn-team-members { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }
.cn-team-mrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cn-team-mnm { font-size: 13px; color: var(--text); flex: 1; min-width: 100px; }
.cn-team-mrow select, .cn-team-add select, .cn-team-new select {
  font-size: 12px; padding: 3px 6px; border-radius: 7px;
  background: var(--surface); color: var(--text); border: 1px solid var(--border);
}
.cn-team-add { margin-top: 4px; }

/* ---- center column ---- */
.cn-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.cn-main-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding: 20px 26px 14px; border-bottom: 1px solid var(--border);
}
.cn-main-head h1 { margin: 0; font-size: 21px; }
.cn-main-head p { margin: 4px 0 0; color: var(--text-dim); font-size: 13px; max-width: 560px; }
.cn-invite-box {
  display: flex; align-items: center; gap: 10px; background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 8px 12px; flex-shrink: 0;
}
.cn-invite-label { display: block; font-size: 10px; text-transform: uppercase; color: var(--text-faint); letter-spacing: .5px; }
.cn-invite-box code { font-size: 12px; color: var(--accent-2); word-break: break-all; max-width: 220px; display: inline-block; }
.cn-tabs { display: flex; gap: 4px; padding: 10px 22px 0; border-bottom: 1px solid var(--border); }
.cn-tab {
  background: none; border: none; border-bottom: 2px solid transparent; color: var(--text-dim);
  padding: 9px 12px; font-size: 13.5px; font-weight: 600; cursor: pointer; margin-bottom: -1px;
}
.cn-tab:hover { color: var(--text); }
.cn-tab.active { color: var(--text); border-bottom-color: var(--accent); }
.cn-tab-count {
  font-size: 11px; background: var(--bg-3); color: var(--text-dim); border-radius: 9px; padding: 1px 7px; margin-left: 3px;
}
.cn-tab-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.cn-pane { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 22px; }
.cn-pane.hidden { display: none; }
.cn-pane[data-pane="chat"] { padding: 0; overflow: hidden; }

/* ---- members grid ---- */
.cn-members-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
.cn-member-card {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 14px; cursor: pointer;
  transition: border-color .15s, transform .1s;
}
.cn-member-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.cn-member-card.active { border-color: var(--accent); background: var(--accent-soft); }
.cn-member-card.you { cursor: default; border-style: dashed; }
.cn-member-card.you:hover { transform: none; }
.cn-member-top { display: flex; align-items: center; gap: 8px; }
.cn-member-name { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cn-you-badge { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; background: var(--accent-soft); color: var(--accent-2); padding: 2px 6px; border-radius: 6px; font-weight: 700; }
.cn-you-badge .ico { width: 11px; height: 11px; flex-shrink: 0; }
.cn-role-badge {
  font-size: 10px; font-weight: 700; padding: 1px 7px; border-radius: 6px; margin-left: auto;
  text-transform: uppercase; letter-spacing: .3px; background: var(--bg-3); color: var(--text-dim); flex-shrink: 0;
}
.cn-role-badge.role-owner { background: rgba(255,255,255,.16); color: #fff; }
.cn-role-badge.role-admin { background: rgba(255,255,255,.11); color: var(--text); }
.cn-role-badge.role-manager { background: rgba(255,255,255,.08); color: var(--text-dim); }
.cn-role-badge.role-hr { background: rgba(255,255,255,.08); color: var(--text-dim); }
.cn-role-badge.role-finance { background: rgba(255,255,255,.08); color: var(--text-dim); }
.cn-detail-name .cn-role-badge { margin-left: 6px; }
.cn-detail-knowledge {
  font-size: 13px; color: var(--text-dim); line-height: 1.5; background: var(--bg-3);
  border-radius: 9px; padding: 10px 12px; margin: 14px 0 4px;
}
.cn-role-assign { display: flex; gap: 8px; align-items: center; }
.cn-role-assign .cn-input { margin: 0; }
.cn-makeowner-btn { margin-top: 10px; font-size: 12.5px; }
/* THE DOT VOCABULARY. Two independent facts share one dot, so each gets its own
   channel — mixing them is what made the old palette misleading:

     FILL   = is a PERSON there   (solid = yes, hollow = no)
     COLOUR = do we KNOW          (green = we have an answer, grey = we don't)

   The bug that forced this: `away` (we know they've stepped away) was grey while
   an unreadable person fell back to `online` and drew a GREEN ring — so the state
   we were certain about looked deader than the state we knew nothing about. And
   grey meant both "away" and "node down", which are opposite facts about whether
   the workspace can be reached at all.

   Under the rule above, `away` and `online` are the SAME statement — "reachable,
   nobody at the desk" — which is exactly right: an AI seat never has anyone at
   the desk, and an away colleague's AI is still picking things up. */
.cn-presence { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; box-sizing: border-box; }
/* Reachable, nobody at the desk — an AI seat, or a person who has stepped away.
   NOT a filled dot: filled is how a container running 24/7 came to look like a
   person being present. Only `.active` earns the solid green. */
.cn-presence.online { background: transparent; border: 1.5px solid var(--ok); }
.cn-presence.away   { background: transparent; border: 1.5px solid var(--ok); }
/* We cannot say. Dashed, because a solid grey ring would read as a fact about the
   person rather than an admission that we have none. */
.cn-presence.unknown { background: transparent; border: 1.5px dashed var(--text-faint); opacity: .8; }
/* Nothing is answering — the one grey that is a statement, not an absence of one. */
.cn-presence.offline { background: var(--text-faint); }
/* An AI recruit runs inside this workspace — never pinged, always up. Distinct
   from "online" (a peer node that answered a heartbeat) on purpose. */
.cn-presence.local { background: var(--accent); box-shadow: 0 0 7px var(--accent); }
/* A PERSON at their desk. `active` is the only solid green anywhere in the UI. */
.cn-presence.active { background: var(--ok); box-shadow: 0 0 7px var(--ok); }
.cn-presence.idle   { background: var(--warn); box-shadow: 0 0 6px var(--warn); }

/* Presence sharing: a one-line control right under the headline. */
.cn-presence-privacy {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: -6px 0 14px; font-size: 12px; color: var(--text-faint);
}

/* Members headline: reachable-now, split person vs AI. */
.cn-presence-summary {
  display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
  margin: 0 0 14px; font-size: 12.5px; color: var(--text-dim);
}
.cn-presence-summary .cn-sum-strong {
  display: inline-flex; align-items: center; gap: 6px;
  font-weight: 650; color: var(--text);
}
/* Each figure carries its own glyph, so the row reads as a set of counts rather than a
   sentence — and they are the app's glyphs, at the size a line of 12.5px text can take. */
.cn-presence-summary .cn-sum-part {
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
}
.cn-presence-summary .ico { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-faint); }
.cn-presence-summary .cn-sum-unknown { color: var(--text-faint); cursor: help; }
.cn-member-skills { font-size: 12.5px; color: var(--text-dim); margin: 10px 0; min-height: 17px; }
.cn-member-foot { display: flex; align-items: center; justify-content: space-between; font-size: 11.5px; }
.cn-trust { color: #d0d0d0; letter-spacing: 1px; }
.cn-trust-n { color: var(--text-faint); letter-spacing: 0; margin-left: 2px; }
.cn-member-skillcount { color: var(--text-faint); }

/* ---- detail panel ---- */
.cn-detail { border-left: 1px solid var(--border); background: var(--bg-2); overflow-y: auto; padding: 18px; }
.cn-detail.hidden { display: none; }
.cn-detail-head { display: flex; align-items: flex-start; justify-content: space-between; }
.cn-detail-name { font-weight: 700; font-size: 16px; display: flex; align-items: center; gap: 8px; }
.cn-detail-sub { font-size: 12px; color: var(--text-faint); margin-top: 4px; word-break: break-all; }
.cn-detail-stats { display: flex; gap: 10px; margin: 16px 0; }
.cn-detail-stats > div { flex: 1; background: var(--bg-3); border-radius: 10px; padding: 10px; text-align: center; }
.cn-stat-n { display: block; font-weight: 700; color: #d0d0d0; }
.cn-detail-stats > div:last-child .cn-stat-n { color: var(--accent-2); }
.cn-stat-l { font-size: 10.5px; color: var(--text-faint); }
.cn-detail-actions { display: flex; gap: 8px; }
.cn-detail-actions .btn-primary { flex: 1; }
.cn-detail-section { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--text-faint); font-weight: 700; margin: 18px 0 8px; }
.cn-detail-skills { display: flex; flex-direction: column; gap: 6px; }
.cn-skill-chip {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: var(--bg-3); border-radius: 8px; padding: 7px 10px; font-size: 13px;
}
.cn-skill-ask { background: none; border: none; color: var(--accent-2); cursor: pointer; font-size: 12px; font-weight: 600; }
.cn-skill-ask:hover { text-decoration: underline; }
.cn-detail-id { display: block; background: var(--bg); border-radius: 8px; padding: 8px 10px; font-size: 11px; color: var(--text-dim); word-break: break-all; }

/* ---- chat ---- */
.cn-chat { flex: 1; min-height: 0; display: grid; grid-template-columns: 170px minmax(0, 1fr); }
.cn-chat-channels { border-right: 1px solid var(--border); padding: 14px 10px; display: flex; flex-direction: column; gap: 3px; overflow-y: auto; }
.cn-channel {
  background: none; border: none; color: var(--text-dim); text-align: left; padding: 7px 10px;
  border-radius: 8px; cursor: pointer; font-size: 13.5px; font-weight: 500;
}
.cn-channel:hover { background: var(--bg-hover); color: var(--text); }
.cn-channel.active { background: var(--accent-soft); color: var(--accent-2); font-weight: 600; }
.cn-channel.add { color: var(--text-faint); margin-top: 6px; }
.cn-chat-main { display: flex; flex-direction: column; min-height: 0; }
.cn-chat-messages { flex: 1; overflow-y: auto; padding: 18px 20px; display: flex; flex-direction: column; gap: 12px; }
.cn-msg { max-width: 78%; align-self: flex-start; }
.cn-msg.mine { align-self: flex-end; text-align: right; }
.cn-msg-meta { font-size: 11px; color: var(--text-faint); margin-bottom: 3px; }
.cn-msg-meta strong { color: var(--text-dim); }
.cn-msg-body {
  display: inline-block; background: var(--bg-3); padding: 8px 12px; border-radius: 12px;
  font-size: 14px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; text-align: left;
}
.cn-msg.mine .cn-msg-body { background: var(--user-bubble); }
.cn-chat-composer { display: flex; gap: 10px; padding: 12px 18px; border-top: 1px solid var(--border); }
.cn-chat-composer .cn-input { margin: 0; }

/* ---- skill market ---- */
.cn-market-bar { margin-bottom: 14px; }
.cn-market-list { display: flex; flex-direction: column; gap: 8px; }
.cn-market-row {
  display: grid; grid-template-columns: 2fr 2fr auto auto; gap: 14px; align-items: center;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; padding: 11px 14px;
}
.cn-market-skill { display: flex; flex-direction: column; gap: 2px; }
.cn-market-skillname { font-weight: 600; font-size: 14px; }
.cn-market-type { font-size: 11px; color: var(--text-faint); text-transform: uppercase; }
.cn-market-peer { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--text-dim); }
.cn-market-comm { color: var(--text-faint); }
.cn-market-trust { color: #d0d0d0; letter-spacing: 1px; font-size: 12px; }

/* ---- activity ---- */
.cn-requests-list { display: flex; flex-direction: column; gap: 8px; }
.cn-request {
  display: grid; grid-template-columns: 70px 1fr 1.2fr 90px; gap: 12px; align-items: center;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px; padding: 10px 14px; font-size: 13px;
}
.cn-request-direction { font-size: 11px; font-weight: 700; }
.cn-request.incoming .cn-request-direction { color: var(--ok); }
.cn-request.outgoing .cn-request-direction { color: var(--accent-2); }
.cn-request-status { text-align: right; text-transform: capitalize; color: var(--text-dim); }
.cn-request-status.status-completed { color: var(--ok); }
.cn-request-status.status-failed { color: var(--danger); }
.cn-community-meta { font-size: 12px; color: var(--text-faint); }

/* ---- modals ---- */
.cn-modal-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.55); backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center; z-index: 1000;
}
.cn-modal-overlay.hidden { display: none; }
.cn-modal { background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px; width: 460px; max-width: 92vw; max-height: 88vh; overflow-y: auto; box-shadow: 0 24px 60px rgba(0,0,0,.5); }
.cn-modal-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px; border-bottom: 1px solid var(--border); }
.cn-modal-head h3 { margin: 0; font-size: 16px; }
.cn-modal-body { padding: 16px 18px 20px; }
.cn-modal-hint { font-size: 12.5px; color: var(--text-dim); margin: 0 0 6px; line-height: 1.5; }
.cn-modal-submit { width: 100%; margin-top: 14px; }
.cn-copyrow { display: flex; align-items: center; gap: 8px; background: var(--bg); border-radius: 8px; padding: 8px 10px; }
.cn-copyrow code { font-size: 12px; color: var(--accent-2); word-break: break-all; flex: 1; }
.cn-stars { display: flex; gap: 4px; font-size: 26px; }
.cn-star { cursor: pointer; color: var(--text-faint); transition: color .1s; }
.cn-star.on { color: #d0d0d0; }
.cn-ask-result { margin-top: 14px; }
.cn-ask-result.hidden { display: none; }
.cn-ask-ok { color: var(--ok); font-weight: 600; margin-bottom: 6px; }
.cn-ask-err { color: var(--danger); font-weight: 600; }

/* ---- generated files (downloads) ---- */
/* Width comes from the shared card rule in the Messages section — a card that sets
   its own max-width is a card that stops short of the answer above it. */
.files-card {
  margin: 8px 0; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--bg-2); overflow: hidden;
}
.file-row { display: flex; align-items: center; gap: 10px; padding: 9px 12px; }
.file-row + .file-row { border-top: 1px solid var(--border); }
.file-icon { font-size: 18px; flex-shrink: 0; }
.file-info { flex: 1; min-width: 0; }
.file-name { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-meta { font-size: 11px; color: var(--text-dim); }
a.file-dl { text-decoration: none; white-space: nowrap; flex-shrink: 0; }
/* The two actions move as ONE. They were siblings of the name, so the row was four
   things competing for one line and the NAME was the only one that could give — it
   gave all the way to nothing (measured: 49px of a 320px screen, "ESP3…"), and once it
   reached zero the buttons had nowhere left to go and the card, which clips, cut the
   Download in half. The filename is the whole point of the row; it must not be the
   part that is sacrificed. */
.file-acts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

/* ---- file viewer (look at it instead of downloading it) ----------------------
   One viewer, opened from the chat card and from the Files page. The body is a
   plain box and whatever goes in it (image, PDF frame, text) owns its own
   scrolling — a preview that scrolls the modal AND itself is two scrollbars
   arguing over one gesture. */
/* ==== The file preview is a DRAWER that SQUEEZES, not an overlay ====================
   A preview is something you consult beside your work. As a centred dialog it covered
   the list you were reading and the chat you were reading it for; as an overlay drawer
   it still dimmed them and still swallowed every click.

   So it takes ROOM instead of taking over: `#app` gives up its right edge and the whole
   shell — top bar, sidebar, content — reflows into what is left. Nothing is dimmed and
   nothing is blocked, so the row you clicked stays readable AND clickable while the
   preview is open beside it.

   ⚠️ Scoped by ID on purpose. `.overlay` and `.modal` are shared by every dialog in the
   app and all of them should stay centred and modal — an id + a class outranks a class
   WHEREVER IT SITS, so this cannot be undone by anything appended later. (Three bugs
   this session came from rules that read correctly and lost the cascade on position.)

   ONE entry point feeds it — `openFilePreview` — so Files, the chat's download cards and
   anywhere else that shows a file all behave the same, with nothing to keep in step. */
#preview-overlay {
  /* Not `inset: 0`: an overlay that covers the page is exactly what this stops being.
     It occupies its own strip at the right and nothing else, which is what lets clicks
     through to the app rather than needing pointer-events tricks to fake it. */
  left: auto; right: 0; top: 0; bottom: 0; width: var(--preview-w);
  background: none; backdrop-filter: none; padding: 0;
  justify-content: flex-end; align-items: stretch;
}
#preview-overlay .modal {
  width: 100%; max-width: none; height: auto; max-height: none;
  border-radius: 0; border-top: 0; border-right: 0; border-bottom: 0;
  animation: previewIn .22s cubic-bezier(.4, 0, .2, 1);
}
/* The squeeze. `#app` is `height: 100vh; overflow: hidden`, so padding on it shrinks the
   content box and everything inside reflows — one property, no layout of its own. */
:root { --preview-w: min(880px, 46vw); }
/* ⚠️ `#app-body`, not `#app` — the top bar is fixed chrome and nothing pushes it. */
#app-body { transition: padding-right .22s cubic-bezier(.4, 0, .2, 1); }
body.preview-open #app-body { padding-right: var(--preview-w); }
/* Under ~1100px there is nothing left to squeeze INTO, so it goes back to covering the
   page — a 46vw drawer beside a 54vw app is two unusable columns. */
@media (max-width: 1100px) {
  :root { --preview-w: 100%; }
  body.preview-open #app-body { padding-right: 0; }
  #preview-overlay { background: rgba(0, 0, 0, .55); }
}
/* The panel's own curve, the one the rails already slide on. */
@keyframes previewIn { from { transform: translateX(100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) {
  #preview-overlay .modal { animation: none; }
  #app-body { transition: none; }
}
/* A drawer is edge-to-edge, so its body gets the room the dialog's inset used to take. */
#preview-overlay .preview-body { padding: 14px 18px 22px; }
.preview-head-main { min-width: 0; }
.preview-head-main h2 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.preview-acts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.preview-body { flex: 1; min-height: 0; padding: 14px 16px; gap: 10px; }
.pv-wrap { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
.pv-img {
  max-width: 100%; max-height: 100%; object-fit: contain; display: block;
  border-radius: var(--radius-sm); background: var(--bg-3);
}
.pv-frame {
  flex: 1; width: 100%; min-height: 0; background: #fff;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.pv-text {
  flex: 1; min-height: 0; overflow: auto; margin: 0; padding: 12px 14px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: var(--mono); font-size: 12.5px; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
}
.pv-note { flex-shrink: 0; font-size: 11.5px; color: var(--text-faint); }

/* ---- Office previews: a spreadsheet, a document and a deck ---------------------
   All three render on PAPER — white, dark ink — in both themes, exactly as the PDF
   frame above does. These files carry their own colours (a header fill chosen against
   Excel's white, a slide's theme background) and re-hosting them on a dark surface
   does not make them dark-mode, it makes them wrong. The modal chrome around them
   stays themed, so the page still belongs to the app. */
.pv-sheet, .pv-doc, .pv-deck {
  --paper: #ffffff; --ink: #17171d; --rule: #d8d8e0; --band: #f3f3f7;
  flex: 1; min-height: 0; overflow: auto;
}
.pv-sheet { display: flex; flex-direction: column; overflow: hidden; }

/* Sheet tabs sit on top rather than at the bottom: the modal's bottom edge is where
   the eye leaves, and with several sheets the tab strip is the first thing you need. */
.pv-tabs {
  display: flex; gap: 4px; flex-shrink: 0; overflow-x: auto; padding-bottom: 8px;
  scrollbar-width: thin;
}
.pv-tab {
  padding: 5px 12px; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
  white-space: nowrap; color: var(--text-dim); background: var(--bg-3);
  border: 1px solid var(--border); border-radius: 999px;
}
.pv-tab:hover { color: var(--text); border-color: var(--accent); }
.pv-tab.on { color: #fff; background: var(--accent); border-color: var(--accent); }

.pv-grid-wrap { flex: 1; min-height: 0; overflow: auto; background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius-sm); }
.pv-grid-box { display: flex; flex-direction: column; min-width: min-content; }
.pv-grid { border-collapse: separate; border-spacing: 0; color: var(--ink); font-size: 12.5px; }
.pv-grid th, .pv-grid td {
  border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  padding: 4px 8px; max-width: 460px; text-align: left; font-weight: 400;
  white-space: pre-wrap; word-break: break-word; vertical-align: top;
}
/* The letter/number gutters stay put while the grid scrolls — without them a wide
   sheet loses which column you are reading three screens in. */
.pv-grid thead th {
  position: sticky; top: 0; z-index: 2; background: var(--band); color: #55555f;
  font-size: 11px; font-weight: 600; text-align: center; min-width: 64px;
}
.pv-grid .pv-rowhead {
  position: sticky; left: 0; z-index: 1; background: var(--band); color: #55555f;
  font-size: 11px; font-weight: 600; text-align: right; min-width: 34px;
}
.pv-grid .pv-corner { position: sticky; left: 0; z-index: 3; min-width: 34px; }
.pv-grid td.num { text-align: right; font-variant-numeric: tabular-nums; }
.pv-grid td.b { font-weight: 700; }
.pv-grid td.hd { background: var(--band); font-weight: 600; }

/* Document flow. Sized like a page, not like the app's UI text. */
.pv-doc {
  background: var(--paper); color: var(--ink); border: 1px solid var(--rule);
  border-radius: var(--radius-sm); padding: 30px 38px; font-size: 14px; line-height: 1.65;
}
.pv-doc h1, .pv-doc h2, .pv-doc h3, .pv-doc h4, .pv-doc h5, .pv-doc h6 {
  margin: 20px 0 8px; line-height: 1.3; color: var(--ink);
}
.pv-doc h1 { font-size: 25px; }
.pv-doc h2 { font-size: 20px; }
.pv-doc h3 { font-size: 17px; }
.pv-doc h4, .pv-doc h5, .pv-doc h6 { font-size: 15px; }
.pv-doc > :first-child { margin-top: 0; }
.pv-doc p { margin: 0 0 10px; }
.pv-doc .pv-list { margin: 0 0 10px; padding-left: 26px; }
.pv-doc .pv-list li { margin: 2px 0; }
.pv-doc blockquote {
  margin: 10px 0; padding: 4px 14px; border-left: 3px solid var(--rule); color: #4a4a55;
}
.pv-doc .pv-figure { margin: 14px 0; }
.pv-doc .pv-figure img { max-width: 100%; border-radius: 4px; display: block; }
.pv-table-box { margin: 12px 0; overflow-x: auto; }
.pv-doctable { border-collapse: collapse; font-size: 13px; min-width: min-content; }
.pv-doctable th, .pv-doctable td {
  border: 1px solid var(--rule); padding: 6px 10px; text-align: left; vertical-align: top;
  white-space: pre-wrap;
}
.pv-doctable th, .pv-doctable td.b { background: var(--band); font-weight: 700; }

/* Slides: real geometry, scaled to fit. `.pv-stage` owns the post-scale height, since
   a transform does not change layout and the deck would otherwise overlap itself. */
.pv-deck { display: flex; flex-direction: column; gap: 18px; }
.pv-slide-wrap { display: flex; flex-direction: column; gap: 5px; }
.pv-slide-n { font-size: 11.5px; color: var(--text-faint); }
.pv-stage { overflow: hidden; }
.pv-slide {
  position: relative; transform-origin: top left; background: var(--paper);
  color: var(--ink); border: 1px solid var(--rule); border-radius: 4px;
  box-shadow: var(--shadow-1); overflow: hidden;
}
.pv-shape { position: absolute; overflow: hidden; }
.pv-shape.title { display: flex; flex-direction: column; justify-content: center; }
.pv-shape img { width: 100%; height: 100%; object-fit: contain; display: block; }
.pv-line { line-height: 1.25; margin-bottom: 2px; word-break: break-word; }
.pv-line.bullet { position: relative; padding-left: 1.1em; }
.pv-line.bullet::before { content: "•"; position: absolute; left: 0.2em; opacity: .55; }
.pv-slide-table { border-collapse: collapse; width: 100%; font-size: 13px; }
.pv-slide-table td { border: 1px solid var(--rule); padding: 3px 6px; }

/* ---- photometric files (.ies / .ldt) --------------------------------------
   The two plotted planes are the same quantity in the same unit, so they get two
   CATEGORICAL colours and a legend — not a light-to-dark ramp, which would read as one
   of them being "more". Both hues are readable on either theme's background, which is
   why they are declared here per theme rather than borrowed from --warn (amber means
   something else everywhere else in the app). */
.pv-phot { --ph-a: var(--accent); --ph-b: #12a5a5; display: flex; flex-direction: column; min-height: 0; }
:root[data-theme="light"] .pv-phot { --ph-b: #0b8785; }
.pv-phot-pane { overflow: auto; min-height: 0; }
.pv-phot-body { display: flex; flex-direction: column; gap: 14px; padding: 2px 2px 18px; }
.pv-phot-head { display: flex; flex-direction: column; gap: 2px; }
.pv-phot-title { margin: 0; font-size: 17px; }
.pv-phot-sub { font-size: 12.5px; color: var(--text-faint); }
.pv-phot-export { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px; }
.pv-phot-export-k { font-size: 11.5px; color: var(--text-faint); margin-right: 2px; }
.pv-phot-h { margin: 6px 0 0; font-size: 13px; letter-spacing: .02em; text-transform: uppercase; color: var(--text-faint); }
.pv-phot-class { font-size: 13.5px; color: var(--text-dim); }

.pv-phot-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.pv-phot-tile { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; }
.pv-phot-tile-k { font-size: 11.5px; color: var(--text-faint); }
.pv-phot-tile-v { font-size: 21px; font-weight: 650; line-height: 1.25; }
.pv-phot-unit { font-size: 13px; font-weight: 500; color: var(--text-dim); }
.pv-phot-tile-n { font-size: 11px; color: var(--text-faint); }

.pv-phot-cols { display: grid; grid-template-columns: minmax(260px, 1fr) minmax(220px, 1fr); gap: 18px; align-items: start; }
@media (max-width: 720px) { .pv-phot-cols { grid-template-columns: 1fr; } }
.pv-phot-col { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.pv-phot-chart { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 6px; }
.pv-phot-legend { display: flex; flex-wrap: wrap; gap: 12px; padding: 4px 6px 2px; font-size: 12px; color: var(--text-dim); }
.pv-phot-key { display: inline-flex; align-items: center; gap: 6px; }
.pv-phot-swatch { width: 14px; height: 3px; border-radius: 2px; background: var(--ph-a); }
.pv-phot-swatch.s1 { background: var(--ph-b); }
.pv-phot-key-note { margin-left: auto; color: var(--text-faint); }

.ph-ring, .ph-spoke, .ph-axis { fill: none; stroke: var(--border); }
.ph-spoke { stroke-dasharray: 2 4; }
.ph-axis { stroke: var(--text-faint); }
.ph-tick { fill: var(--text-faint); font-size: 10px; }
.ph-tick.mid { text-anchor: middle; }
.ph-tick.end { text-anchor: end; }
.ph-line { fill: none; stroke: var(--ph-a); stroke-width: 2; stroke-linejoin: round; }
.ph-line.s1 { stroke: var(--ph-b); stroke-dasharray: 5 3; }

.pv-phot-pairs { border-collapse: collapse; font-size: 13px; width: 100%; }
.pv-phot-pairs th { text-align: left; font-weight: 500; color: var(--text-faint); padding: 3px 10px 3px 0; vertical-align: top; white-space: nowrap; }
.pv-phot-pairs td { padding: 3px 0; word-break: break-word; }
.pv-phot-tablebox { overflow: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.pv-phot-table { border-collapse: collapse; font-size: 13px; width: 100%; }
.pv-phot-table th, .pv-phot-table td { border-bottom: 1px solid var(--border); padding: 5px 10px; text-align: left; }
.pv-phot-table th { background: var(--bg-3); font-weight: 600; white-space: nowrap; }
.pv-phot-table th.sub { font-weight: 500; color: var(--text-faint); text-align: right; }
.pv-phot-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.pv-phot-grid { font-size: 12px; }

/* One ordered quantity (glare), so one hue in three steps — and the step that matters
   is UGR<19, the line every office specification is written to. */
.pv-phot-ugrtable td.ugr.good { color: var(--ok); font-weight: 600; }
.pv-phot-ugrtable td.ugr.fair { color: var(--text-dim); }
.pv-phot-ugrtable td.ugr.poor { color: var(--warn); }
.pv-phot-ugr-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.pv-phot-ugr-value { font-size: 34px; font-weight: 650; line-height: 1; }
.pv-phot-ugr-rating { font-size: 13px; color: var(--text-dim); }
.pv-phot-room { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px; }
.pv-phot-field { display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; color: var(--text-faint); }
.pv-phot-field input { width: 92px; padding: 5px 7px; background: var(--bg-3); color: var(--text); border: 1px solid var(--border); border-radius: 6px; font: inherit; font-size: 13px; }
.pv-phot-actions { display: flex; align-items: center; gap: 10px; }
.pv-phot-status { font-size: 12px; color: var(--text-faint); }
.pv-phot-warn { background: var(--warn-soft); border: 1px solid var(--warn); border-radius: var(--radius-sm); padding: 10px 12px; font-size: 13px; }
.pv-phot-warn-h { font-weight: 650; margin-bottom: 4px; }
.pv-phot-warn ul { margin: 0; padding-left: 18px; }
.pv-phot-warn li { margin: 2px 0; }

/* The Photometrics page reuses every renderer above; only the drop zone and the
   comparison wrapper are its own. */
.phot-drop {
  border: 1.5px dashed var(--border); border-radius: var(--radius); background: var(--bg-1);
  padding: 26px 18px; text-align: center; cursor: pointer; transition: border-color .12s, background .12s;
}
.phot-drop:hover, .phot-drop.on { border-color: var(--accent); background: var(--accent-soft); }
.phot-drop-in { display: flex; flex-direction: column; gap: 6px; align-items: center; pointer-events: none; }
.phot-drop-in button { pointer-events: auto; }
.phot-drop-ico { font-size: 26px; }
.phot-drop-note { font-size: 12.5px; color: var(--text-faint); }
.phot-status { margin: 12px 0 0; font-size: 13px; color: var(--text-dim); }
.phot-status.bad { color: var(--danger); }
.phot-compare { display: flex; flex-direction: column; gap: 10px; margin-top: 18px; }
.phot-report { margin-top: 18px; }
.phot-strip { margin-bottom: 8px; }
.phot-pane { min-height: 0; }
#view-photometrics .pv-phot-pane { overflow: visible; }

/* The file NAME is a click target on every surface that lists one, so it has to
   read as text and behave as a button. */
button.file-name, button.fv-name {
  display: block; max-width: 100%; font: inherit; font-size: 13px; font-weight: 600;
  color: var(--text); background: none; border: 0; padding: 0; text-align: left;
  cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
button.file-name:hover, button.fv-name:hover { color: var(--accent-2); text-decoration: underline; }
.file-item .mem-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- inline send approval (chat gate) ----------------------------------------
   The decision belongs where the request was made. Accent-framed while it waits on
   you; it settles in place to sent/not-sent so the transcript stays truthful. */
.send-approve {
  margin: 10px 0; padding: 12px 14px; border-radius: var(--radius);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border));
  background: color-mix(in srgb, var(--accent) 7%, var(--bg-2));
}
.sa-head { display: flex; align-items: center; gap: 8px; }
.sa-ico { font-size: 15px; flex: none; }
.sa-title { font-size: 13.5px; font-weight: 700; color: var(--text); flex: 1; min-width: 0; overflow-wrap: anywhere; }
.sa-tag {
  flex: none; padding: 2px 8px; border-radius: 999px; font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
  background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent);
}
.sa-msg {
  margin-top: 9px; padding: 9px 11px; border-radius: 9px; background: var(--bg);
  border: 1px solid var(--border); font-size: 13px; line-height: 1.5; color: var(--text);
  white-space: pre-wrap; overflow-wrap: anywhere; max-height: 260px; overflow-y: auto;
}
.sa-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 11px; }
.sa-actions:empty { display: none; }
.sa-send {
  border: none; border-radius: 9px; padding: 7px 15px; font-size: 13px; font-weight: 600;
  background: var(--accent); color: var(--on-accent); cursor: pointer;
}
.sa-send:hover { filter: brightness(1.08); }
.sa-cancel {
  border: 1px solid var(--border); border-radius: 9px; padding: 7px 13px; font-size: 13px;
  background: var(--bg); color: var(--text-dim); cursor: pointer;
}
.sa-cancel:hover { color: var(--text); border-color: var(--accent); }
.send-approve button:disabled { opacity: .55; cursor: default; }
.sa-hint { margin-top: 8px; font-size: 11.5px; color: var(--text-faint); }
.sa-note { margin-top: 9px; font-size: 12px; color: var(--text-dim); overflow-wrap: anywhere; }
/* The one line a resumed turn leaves in the chat: you clicked Approve, the action ran,
   and the AI carried on. A real `user` row underneath (the click is the input the model
   reads) but nobody typed it, so it is a centred system line rather than a bubble —
   see frontend/app.js:renderApprovalOutcome. */
.msg-sysnote {
  margin: 10px auto; max-width: 90%; text-align: center;
  font-size: 12px; color: var(--text-faint); overflow-wrap: anywhere;
}
/* The record an approved action created — a draft Odoo order. Rendered as real
   anchors OUTSIDE the note's character clip, because opening it is the reader's
   next move and a URL inside a 300-char textContent clip is neither clickable nor,
   on a long result, even present. */
.sa-links { margin-top: 9px; display: flex; flex-wrap: wrap; gap: 8px; }
.sa-links a {
  font-size: 12px; font-weight: 600; text-decoration: none;
  padding: 6px 11px; border-radius: 8px;
  color: var(--ok);
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--ok) 38%, transparent);
}
.sa-links a:hover { background: color-mix(in srgb, var(--ok) 20%, transparent); }
.send-approve.sent { border-color: color-mix(in srgb, var(--ok) 45%, var(--border)); background: var(--bg-2); }
.send-approve.sent .sa-tag { background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok); }
.send-approve.cancelled { border-color: var(--border); background: var(--bg-2); }
.send-approve.cancelled .sa-tag { background: color-mix(in srgb, var(--text-faint) 16%, transparent); color: var(--text-faint); }
.send-approve.cancelled .sa-msg { color: var(--text-faint); }
.cn-ask-result pre {
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 10px;
  font-size: 12.5px; white-space: pre-wrap; word-break: break-word; max-height: 240px; overflow-y: auto;
}

/* ---- consultation rooms ---- */
.btn-ask {
  background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: var(--on-accent); border: none;
  border-radius: 9px; padding: 0 14px; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap;
}
.btn-ask:hover { filter: brightness(1.08); }
.btn-ask:disabled { opacity: .6; cursor: default; }
.cn-chan-group {
  display: flex; align-items: center; gap: 6px;
  font-size: 10px; text-transform: uppercase; letter-spacing: .5px; color: var(--text-faint);
  font-weight: 700; padding: 12px 10px 4px;
}
.cn-chan-group .ico { width: 13px; height: 13px; flex-shrink: 0; }
.cn-channel.room { display: flex; align-items: center; gap: 6px; }
.cn-room-n {
  margin-left: auto; font-size: 10px; background: var(--bg-3); color: var(--text-dim);
  border-radius: 8px; padding: 0 6px; line-height: 16px;
}
.cn-skill-chip.readonly { cursor: default; }
.cn-skill-type { font-size: 10px; color: var(--text-faint); text-transform: uppercase; }
.cn-detail-hint {
  font-size: 12px; color: var(--text-dim); background: var(--accent-soft); border-radius: 9px;
  padding: 10px 12px; margin-top: 14px; line-height: 1.5;
}
.cn-ai-badge {
  font-size: 9px; font-weight: 700; background: var(--accent); color: var(--on-accent);
  border-radius: 5px; padding: 1px 5px; margin-left: 5px; vertical-align: middle;
}
.cn-msg-system {
  align-self: center; text-align: center; font-size: 12px; color: var(--text-faint);
  background: var(--bg-2); border-radius: 10px; padding: 6px 12px; max-width: 90%;
}
.cn-msg-question {
  align-self: stretch; background: var(--bg-2); border-left: 3px solid var(--accent);
  border-radius: 8px; padding: 10px 14px; font-size: 14.5px; font-weight: 500; line-height: 1.5; white-space: pre-wrap;
}
.cn-msg-summary {
  align-self: stretch; background: linear-gradient(180deg, var(--accent-soft), transparent);
  border: 1px solid var(--accent); border-radius: 10px; padding: 12px 14px; font-size: 14px; line-height: 1.55; white-space: pre-wrap;
}
.cn-msg-tag {
  display: block; font-size: 10.5px; text-transform: uppercase; letter-spacing: .5px;
  color: var(--accent-2); font-weight: 700; margin-bottom: 6px;
}
.cn-howto {
  background: var(--accent-soft); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 14px; margin-bottom: 16px; font-size: 13px; color: var(--text-dim); line-height: 1.55;
}
.cn-howto strong { color: var(--text); }
.cn-discussion {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 11px 14px; margin-bottom: 8px;
}
.cn-disc-topic { font-weight: 600; font-size: 14px; margin-bottom: 3px; }

/* ---- Sessions (live P2P consultation viewer) ---- */
.cn-sessions { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 14px; height: 100%; min-height: 0; }
.cn-sessions-list { display: flex; flex-direction: column; gap: 7px; overflow-y: auto; padding-right: 2px; }
.cn-session-item {
  text-align: left; width: 100%; background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 12px; cursor: pointer; color: var(--text);
}
.cn-session-item:hover { border-color: var(--accent); }
.cn-session-item.active { border-color: var(--accent); background: var(--accent-soft); }
.cn-session-top { display: flex; align-items: center; gap: 8px; }
.cn-session-topic { font-weight: 600; font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cn-session-live { width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); flex-shrink: 0; }
.cn-session-live.on { background: var(--ok); box-shadow: 0 0 7px var(--ok); animation: cn-pulse 1.2s ease-in-out infinite; }
.cn-session-meta { font-size: 11.5px; color: var(--text-faint); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cn-session-view {
  min-width: 0; min-height: 0; display: flex; flex-direction: column;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
}
.cn-session-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.cn-session-title { font-weight: 600; font-size: 15px; display: flex; align-items: center; gap: 8px; }
.cn-session-sub { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-faint); margin-top: 3px; }
.cn-session-sub .ico { width: 13px; height: 13px; flex-shrink: 0; }
.cn-session-badge {
  font-size: 10px; font-weight: 700; letter-spacing: .5px; padding: 2px 8px; border-radius: 6px;
  background: var(--bg-3); color: var(--text-faint);
}
.cn-session-head .cn-session-badge { color: var(--ok); }
.cn-session-thread { flex: 1; min-height: 0; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.cn-smsg { max-width: 78%; align-self: flex-start; }
.cn-smsg.mine { align-self: flex-end; }
.cn-smsg-body {
  position: relative; display: inline-flex; flex-direction: column; align-items: flex-start;
  background: var(--bg-3); padding: 6px 11px 4px; border-radius: 14px; max-width: 100%;
  box-shadow: 0 1px 1.5px rgba(0,0,0,.07);
}
.cn-smsg.mine .cn-smsg-body { background: var(--user-bubble); }
.cn-smsg-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12px; font-weight: 700; color: var(--accent-2); margin-bottom: 1px; }
.cn-smsg-text { font-size: 13.5px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; text-align: left; }
/* An AI-written bubble holds RENDERED Markdown. Borrow .msg-content's typography
   (lists, headings, code, tables) but strip its bubble chrome — this is already
   inside a bubble — and drop pre-wrap, which would double the gap between every
   block element. A wide table scrolls inside the bubble instead of stretching it. */
.cn-smsg-text.msg-content {
  background: none; border: none; border-radius: 0; padding: 0;
  white-space: normal; overflow-x: auto;
}
.cn-smsg-text.msg-content > :first-child { margin-top: 0; }
.cn-smsg-text.msg-content > :last-child { margin-bottom: 0; }
.cn-smsg-text.msg-content .md-table { font-size: 12px; }
.cn-smsg-text.msg-content .md-table th, .cn-smsg-text.msg-content .md-table td { padding: 5px 8px; }
/* tables and code need more room than a chat line does */
.cn-smsg.has-md { max-width: 92%; }
.cn-smsg-foot { align-self: flex-end; display: inline-flex; align-items: center; gap: 4px; margin-top: 1px;
  font-size: 10.5px; color: var(--text-faint); white-space: nowrap; line-height: 1.4; }
.cn-smsg .gc-edited { font-style: italic; opacity: .8; }
/* The stamp in a bubble's foot carries the DATE as well as the clock — a room thread
   is read back days later and a bare "05:11 PM" is a time on a day the reader has to
   infer from the bubbles above it. Same wording as the AI chat's `.msg-time`, from the
   same `fmtMsgStamp`; `cursor: help` because the full instant, to the second, is on
   the hover title. The foot is already `nowrap`, so a date can never break the line. */
.gc-time { cursor: help; }
.cn-smsg-foot .cn-ai-badge, .cn-smsg-foot .cn-assist-badge { transform: scale(.9); }
/* Author face beside every bubble, first of a run only (`gcRenderMessages`). Still a
   MODIFIER rather than a change to `.cn-smsg` — the discussion-session thread reuses
   that class and has no avatars to put in the gutter. The rest of a run gets an empty
   `.gc-msg-ava` of the same width, so the column does not shift under a burst.
   `row-reverse` puts YOUR face on the outside of your own right-aligned bubble, which
   is the side chat already draws it on. */
.cn-smsg.with-ava { display: flex; align-items: flex-start; gap: 8px; }
.cn-smsg.mine.with-ava { flex-direction: row-reverse; }
.cn-smsg.with-ava .cn-smsg-body { min-width: 0; }
.gc-msg-ava { flex: 0 0 auto; width: 28px; }
.gc-msg-ava .cn-ppl-avatar { width: 28px; height: 28px; font-size: 12px; }
.cn-session-composer { display: flex; gap: 8px; padding: 10px 14px; border-top: 1px solid var(--border); }
.cn-session-composer .cn-input { margin: 0; }

/* ===== Company redesign: chat layout + conclusion panel ==================== */
#view-chat { min-height: 0; }
.chat-layout { flex: 1; display: grid; grid-template-columns: minmax(0, 1fr) 360px; min-height: 0; }
.chat-layout:has(#conclusion-panel.hidden) { grid-template-columns: minmax(0, 1fr) 0; }
.chat-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
/* Kimi-style new chat: float the composer directly under the logo, vertically
   centred, with the header pinned at the top. Reverts to the normal docked layout
   the moment the first message arrives (the .chat-empty class is dropped). */
.chat-main.chat-empty { position: relative; justify-content: center; }
.chat-main.chat-empty #chat-header { position: absolute; top: 0; left: 0; right: 0; z-index: 5; }
.chat-main.chat-empty #messages { flex: 0 0 auto; overflow: visible; padding: 0; }
.chat-main.chat-empty #empty-state { margin: 0 auto 40px; }
.chat-main.chat-empty .empty-logo { font-size: 60px; }
.chat-main.chat-empty #composer { border-top: none; padding-top: 0; }
.conclusion-panel {
  border-left: 1px solid var(--border); background: var(--bg-2);
  overflow-y: auto; padding: 16px 16px 20px; display: flex; flex-direction: column; gap: 14px;
}
.conclusion-panel .cp-head { display: flex; align-items: center; justify-content: space-between; }
.cp-title { font-weight: 700; font-size: 14px; }
.cp-close { background: none; border: none; color: var(--text-faint); font-size: 15px; }
.cp-close:hover { color: var(--text); }
.cp-section { display: flex; flex-direction: column; gap: 6px; }
.cp-section.cp-grow { flex: 1; min-height: 0; }
.cp-label { font-size: 11.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint); font-weight: 700; }
.cp-editable-hint { text-transform: none; letter-spacing: 0; color: var(--text-faint); font-weight: 500; }
.cp-minutes { font-size: 13.5px; background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; }
.cp-minutes.hint { color: var(--text-faint); }
.cp-conclusion { flex: 1; min-height: 120px; resize: vertical; width: 100%;
  background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
  color: var(--text); font-family: inherit; font-size: 13.5px; }
.cp-actions { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
@media (max-width: 900px) {
  .chat-layout, .chat-layout:has(#conclusion-panel.hidden) { grid-template-columns: minmax(0, 1fr); }
  .conclusion-panel { position: fixed; top: 0; right: 0; bottom: 0; width: min(360px, 90vw); z-index: 40; }
}

/* Group Chat: the discussion conclusion rendered inline (Meeting-notes panel retired) */
.conclusion-inline { border-left: 3px solid var(--accent); background: var(--bg-1); }
.ci-title { font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--accent-2); margin-bottom: 6px; }

/* ===== Workbench: collapsed process box + slide-out panel ==================
   Width is the shared card rule's (see .msg-body > .wb-box in Messages). */
.wb-box {
  border: 1px solid var(--border); border-radius: 12px; background: var(--bg-1);
  overflow: hidden; margin: 4px 0 8px; }
.wb-box.live { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.wb-boxhead { display: flex; align-items: center; gap: 10px; padding: 9px 12px; cursor: pointer; user-select: none; }
.wb-boxhead:hover { background: var(--bg-2); }
.wb-ico { width: 22px; height: 22px; border-radius: 6px; background: var(--accent-soft);
  display: flex; align-items: center; justify-content: center; font-size: 12px; flex-shrink: 0; }
.wb-htitles { min-width: 0; }
.wb-title { font-weight: 600; font-size: 13px; }
.wb-sub { color: var(--text-faint); font-size: 12px; }
.wb-hsp { flex: 1; }
.wb-chev { color: var(--text-faint); font-size: 11px; transition: transform .2s; }
.wb-box:not(.collapsed) .wb-chev { transform: rotate(90deg); }
.wb-steps { display: flex; flex-direction: column; border-top: 1px solid var(--border);
  max-height: 340px; overflow-y: auto; }
.wb-box.collapsed .wb-steps { display: none; }
.wb-live { display: inline-flex; align-items: center; gap: 7px; color: var(--accent-2); font-weight: 600; }
.wb-pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  display: inline-block; flex-shrink: 0; animation: wb-pulse 1s infinite ease-in-out; }
@keyframes wb-pulse { 0%, 100% { opacity: .35; transform: scale(.85); } 50% { opacity: 1; transform: scale(1.15); } }
@media (prefers-reduced-motion: reduce) { .wb-pulse { animation: none; } }
.wb-step { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-left: 2px solid transparent; }
.wb-step.tool { cursor: pointer; }
.wb-step.tool:hover { background: var(--bg-2); }
.wb-step.active { background: var(--accent-soft); border-left-color: var(--accent); }
.wb-step.narration { display: block; padding: 6px 12px 6px 14px; }
/* A step belonging to a skill running inside the turn (`run_skill`): indented and
   dimmed so the assistant's own steps still read as the main thread of work. */
.wb-step.sub { padding-left: 30px; border-left: 2px solid var(--border); }
.wb-step.sub .wb-dot { width: 11px; height: 11px; }
.wb-step.sub .wb-slabel { font-size: 12.5px; color: var(--text-dim); }
.wb-narr { font-size: 13px; color: var(--text-dim); }
.wb-narr p { margin: 0 0 6px; }
.wb-narr p:last-child { margin-bottom: 0; }
.wb-step.think { align-items: flex-start; cursor: default; }
.wb-dot.think { background: var(--accent-soft); }
.wb-step.think.live .wb-dot.think { animation: wb-pulse 1s infinite ease-in-out; }
.wb-thinktext { font-size: 11.5px; color: var(--text-faint); white-space: pre-wrap; word-break: break-word;
  line-height: 1.5; margin-top: 3px; max-height: 120px; overflow-y: auto; }
.wb-step.pending { cursor: default; }
.wb-step.pending .wb-slabel { color: var(--accent-2); font-weight: 600; }
.wb-dot { width: 15px; height: 15px; border-radius: 50%; flex-shrink: 0; background: var(--bg-3); position: relative; }
.wb-step.running .wb-dot { background: var(--accent-soft); animation: wb-pulse 1s infinite ease-in-out; }
.wb-step.done .wb-dot { background: rgba(52, 195, 126, .2); }
.wb-step.done .wb-dot::after { content: "✓"; position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center; font-size: 9px; color: var(--ok); }
.wb-smain { flex: 1; min-width: 0; }
.wb-slabel { font-size: 13px; }
.wb-stool { font-family: var(--mono); font-size: 11px; color: var(--text-faint); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-sopen { font-size: 11px; color: var(--text-faint); opacity: 0; flex-shrink: 0; }
.wb-step.tool:hover .wb-sopen { opacity: 1; }

/* Full height of the window. It used to start below the top bar, because it slides in
   on the RIGHT — exactly where the online switch sat — and covering the switch read as
   the control vanishing. There is no bar and no switch up there now. */
.wb-panel { position: fixed; top: 0; right: 0; height: 100vh; width: min(440px, 92vw);
  background: var(--bg-1); border-left: 1px solid var(--border); z-index: 46;
  display: flex; flex-direction: column; box-shadow: -16px 0 44px rgba(0, 0, 0, .42);
  transition: transform .3s cubic-bezier(.4, 0, .2, 1); }
.wb-panel.hidden { transform: translateX(100%); }
.wb-p-head { display: flex; align-items: center; gap: 10px; padding: 12px 15px; border-bottom: 1px solid var(--border); }
.wb-p-ico { width: 24px; height: 24px; border-radius: 6px; background: var(--accent-soft);
  display: flex; align-items: center; justify-content: center; font-size: 13px; flex-shrink: 0; }
.wb-p-titles { flex: 1; min-width: 0; }
.wb-p-title { font-weight: 600; font-size: 14px; }
.wb-p-sub { color: var(--text-faint); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wb-p-x { background: none; border: none; color: var(--text-faint); font-size: 15px; cursor: pointer;
  padding: 4px 7px; border-radius: 6px; }
.wb-p-x:hover { background: var(--bg-hover); color: var(--text); }
.wb-p-nav { display: flex; align-items: center; gap: 8px; padding: 8px 15px; border-bottom: 1px solid var(--border);
  font-size: 12px; color: var(--text-faint); }
.wb-p-navbtn { background: var(--bg-2); border: 1px solid var(--border); color: var(--text-dim);
  width: 26px; height: 26px; border-radius: 7px; cursor: pointer; font-size: 13px; }
.wb-p-navbtn:hover { border-color: var(--accent); color: var(--text); }
.wb-p-body { flex: 1; overflow-y: auto; padding: 15px; }
.wb-p-tool { display: inline-flex; align-items: center; gap: 7px; background: var(--bg-2);
  border: 1px solid var(--border); border-radius: 999px; padding: 4px 11px; font-family: var(--mono);
  font-size: 12px; color: var(--accent-2); margin-bottom: 12px; }
.wb-p-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint);
  margin: 13px 0 6px; font-weight: 600; }
.wb-p-io { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px;
  font-family: var(--mono); font-size: 12px; line-height: 1.55; white-space: pre-wrap; word-break: break-word;
  color: var(--text-dim); overflow-x: auto; }
.wb-p-io.out { color: var(--text); }
.wb-p-io.running { display: flex; align-items: center; gap: 8px; color: var(--accent-2); }
.wb-backdrop { position: fixed; inset: 0; z-index: 45; display: none; }
@media (max-width: 900px) { .wb-backdrop:not(.hidden) { display: block; background: rgba(0, 0, 0, .4); } }

/* participant picker menu */
.drop-menu.wide { min-width: 260px; max-height: 340px; overflow-y: auto; }
.drop-section { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint);
  font-weight: 700; padding: 8px 12px 4px; }
.drop-empty { padding: 10px 12px; font-size: 12.5px; color: var(--text-faint); }
.drop-item.pick { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.drop-item.pick small { color: var(--text-faint); }
.drop-item.pick.on { background: var(--accent-soft); }

/* typing dots (a member is composing) */
.typing-dots { display: inline-flex; gap: 4px; align-items: center; }
.typing-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--text-faint);
  animation: td-bounce 1.2s infinite ease-in-out; }
.typing-dots span:nth-child(2) { animation-delay: .15s; }
.typing-dots span:nth-child(3) { animation-delay: .3s; }
@keyframes td-bounce { 0%, 80%, 100% { opacity: .3; transform: translateY(0); } 40% { opacity: 1; transform: translateY(-3px); } }

/* Company: members + teams management */
.members-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.company-members { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
.member-card { background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; }
.member-top { display: flex; align-items: center; gap: 10px; }
.member-ico { --ico-sz: 32px; font-size: 20px; }
.member-name { font-weight: 600; }
.member-role { font-size: 12px; color: var(--text-faint); }
.member-top .member-fire { margin-left: auto; }
.member-skills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.skill-tag { font-size: 11px; background: var(--bg-3); border: 1px solid var(--border); border-radius: 20px; padding: 2px 9px; color: var(--text-dim); }
.company-teams { display: flex; flex-direction: column; gap: 14px; }
.team-card { background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }
.team-head { display: flex; align-items: center; gap: 10px; }
.team-ico { font-size: 18px; }
.team-name-input { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 10px; color: var(--text); font-weight: 600; font-family: inherit; }
.team-purpose-input { width: 100%; margin-top: 8px; background: var(--bg); border: 1px solid var(--border);
  border-radius: 8px; padding: 6px 10px; color: var(--text); font-size: 13px; font-family: inherit; }
.team-members { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; }
.team-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 20px; padding: 3px 6px 3px 11px; }
.team-chip .chip-x { background: none; border: none; color: var(--text-faint); font-size: 12px; line-height: 1; padding: 0 2px; }
.team-chip .chip-x:hover { color: var(--danger); }
.team-add-select { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 6px 10px; color: var(--text); font-family: inherit; }
.recruit-btn { cursor: pointer; }
/* ---- software (dev) teams — unified into the Teams tab ---- */
.team-sw-toggle { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--text-dim); cursor: pointer; white-space: nowrap; }
.team-sw-toggle input { cursor: pointer; }
.team-software { border-color: var(--accent); }
.team-soft { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0 4px; padding: 8px 10px; background: var(--accent-soft); border-radius: 8px; }
.team-mode { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim); }
.team-mode-sel, .team-role-sel { background: var(--bg); border: 1px solid var(--border); border-radius: 7px; padding: 3px 6px; color: var(--text); font-size: 12px; font-family: inherit; }
.team-repo { font-size: 12px; color: var(--ok); }
.team-run-sprint { margin-left: auto; }
.team-mrow { display: flex; align-items: center; gap: 8px; width: 100%; }
.team-software .team-members { flex-direction: column; align-items: stretch; }
.btn-mini.primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
/* ---- per-team knowledge & facts (the team's own shared context) ---- */
.team-kn { margin-top: 10px; border-top: 1px solid var(--border); padding-top: 8px; }
.team-kn > summary { cursor: pointer; font-size: 12.5px; color: var(--text-dim); user-select: none; list-style: none; }
.team-kn > summary::-webkit-details-marker { display: none; }
.team-kn > summary::before { content: "▸ "; color: var(--text-faint); }
.team-kn[open] > summary::before { content: "▾ "; }
.team-know { width: 100%; margin: 8px 0; background: var(--bg); border: 1px solid var(--border);
  border-radius: 8px; padding: 6px 10px; color: var(--text); font-size: 13px; font-family: inherit; resize: vertical; }
.team-facts { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.team-fact { display: flex; align-items: center; gap: 6px; }
.team-fact-k, .team-fact-v { background: var(--bg); border: 1px solid var(--border); border-radius: 7px;
  padding: 4px 8px; color: var(--text); font-size: 12.5px; font-family: inherit; }
.team-fact-k { flex: 0 0 34%; font-weight: 600; }
.team-fact-v { flex: 1; }
.team-fact .team-fact-x { background: none; border: none; color: var(--text-faint); cursor: pointer; padding: 0 4px; }
.team-fact .team-fact-x:hover { color: var(--danger); }
.team-flash { animation: teamFlash 1.4s ease; }
@keyframes teamFlash { 0%,100% { box-shadow: none; } 25% { box-shadow: 0 0 0 2px var(--accent); } }
/* ---- sidebar rail: teams nested under their company ---- */
.cn-rail-teams { display: flex; flex-direction: column; gap: 1px; margin: 1px 0 6px 14px;
  padding-left: 8px; border-left: 1px solid var(--border); }
.cn-rail-team { display: flex; align-items: center; gap: 7px; width: 100%; text-align: left;
  background: none; border: none; color: var(--text-dim); font-size: 12.5px; font-family: inherit;
  padding: 5px 8px; border-radius: 7px; cursor: pointer; }
.cn-rail-team:hover { background: var(--bg-3); color: var(--text); }
.cn-rt-ico { display: inline-flex; font-size: 12px; }
.cn-rt-ico .ico { width: 14px; height: 14px; color: var(--text-faint); }
.cn-rt-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Teams tab: clean list of teams (click a row to open its page) ---- */
.team-list { display: flex; flex-direction: column; gap: 8px; }
.team-list-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px;
  color: var(--text); font-family: inherit; cursor: pointer; transition: border-color .12s, background .12s; }
.team-list-row:hover { border-color: var(--accent); background: var(--bg-3); }
.tlr-ico { font-size: 20px; }
.tlr-main { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.tlr-name { font-weight: 600; }
.tlr-meta { font-size: 12px; color: var(--text-dim); }
.tlr-arrow { font-size: 20px; color: var(--text-faint); }
/* ---- Team detail page: breadcrumb + Members/Settings sub-tabs ---- */
.team-detail-head { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.team-back { background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px; padding: 5px 10px;
  color: var(--text-dim); font-family: inherit; font-size: 13px; cursor: pointer; }
.team-back:hover { color: var(--text); border-color: var(--accent); }
.team-crumb { font-size: 14px; color: var(--text-dim); }
.team-crumb b { color: var(--text); }
.team-type-badge { margin-left: auto; font-size: 12px; color: var(--text-dim);
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 20px; padding: 4px 12px; }
.team-subtabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 16px; }
.team-subtab { display: inline-flex; align-items: center; gap: 7px;
  background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px;
  padding: 8px 14px; color: var(--text-dim); font-family: inherit; font-size: 13.5px; cursor: pointer; }
.team-subtab .ico { width: 15px; height: 15px; flex-shrink: 0; }
.team-subtab:hover { color: var(--text); }
.team-subtab.active { color: var(--text); border-bottom-color: var(--accent); }
.team-subbody { display: flex; flex-direction: column; gap: 12px; }
.team-subbody .team-members { flex-direction: column; align-items: stretch; margin: 0; }
.team-set-row { display: flex; flex-direction: column; gap: 6px; max-width: 640px; }
.team-set-row > label:first-child { font-size: 12px; color: var(--text-dim); font-weight: 600; }
.team-set-row .team-name-input, .team-set-row .team-purpose-input { margin: 0; width: 100%; }
.team-set-kn .team-kn-body { display: flex; flex-direction: column; gap: 8px; }
.team-set-kn .team-know { margin: 0; }
.team-set-danger { margin-top: 8px; padding-top: 12px; border-top: 1px solid var(--border); }

/* ===== Company dashboard: onboarding + settings overview ================== */
.cn-onboarding { padding: 40px 24px; display: flex; align-items: center; justify-content: center; }
.cn-onb-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 320px)); gap: 20px; }
.cn-onb-card { background: var(--bg-2); border: 1px solid var(--border); border-radius: 16px;
  padding: 28px 24px; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 8px; }
.cn-onb-ico { --ico-sz: 56px; font-size: 40px; }
.cn-onb-card h2 { margin: 4px 0 0; font-size: 18px; }
.cn-onb-card p { color: var(--text-dim); font-size: 13.5px; margin: 0 0 12px; }
.cn-onb-card .btn-primary { min-width: 180px; }
.btn-primary.ghost { background: transparent; color: var(--text); border: 1px solid var(--border); }
.btn-primary.ghost:hover { background: var(--bg-hover); }
.cn-set-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.cn-set-stat { background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px; text-align: center; display: flex; flex-direction: column; gap: 2px; }
.cn-set-stat-n { font-size: 22px; font-weight: 700; }
.cn-set-stat-l { font-size: 11.5px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .03em; }

/* ===== Group Chat participant picker: summary line + Setup popover ======== */
.participants-bar { position: relative; flex-shrink: 0; width: 100%; max-width: 780px;
  margin: 0 auto; padding: 8px 22px 0; }
.pbar-summary { display: flex; align-items: center; gap: 8px; width: 100%; cursor: pointer;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 7px 12px; color: var(--text-dim); font: inherit; font-size: 12.5px; text-align: left; }
.pbar-summary:hover { border-color: var(--accent); }
.pbar-sum-ico { font-size: 14px; flex-shrink: 0; }
.pbar-sum-lead { color: var(--text-faint); flex-shrink: 0; }
.pbar-sum-text { color: var(--text); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pbar-tag { flex-shrink: 0; font-size: 11px; padding: 1px 7px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-2); }
.pbar-sum-edit { margin-left: auto; flex-shrink: 0; color: var(--text-faint); font-weight: 600; }

.pbar-popover { position: absolute; left: 22px; right: 22px; bottom: calc(100% - 2px);
  background: var(--bg-1); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow-2); padding: 12px; z-index: 50; max-height: 60vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: 14px; }
.pbar-popover.hidden { display: none; }
.pbar-pop-sec { display: flex; flex-direction: column; gap: 4px; }
.pbar-pop-title { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-faint); font-weight: 700; margin-bottom: 2px; }
.pbar-radio, .pbar-check { display: flex; align-items: flex-start; gap: 9px; padding: 6px 8px;
  border-radius: 8px; cursor: pointer; font-size: 13px; color: var(--text); }
.pbar-radio:hover, .pbar-check:hover { background: var(--bg-2); }
.pbar-radio input, .pbar-check input { margin-top: 2px; flex-shrink: 0; accent-color: var(--accent); }
.pbar-radio small, .pbar-check small { display: block; color: var(--text-faint); font-size: 11.5px; margin-top: 1px; }
.pbar-choose { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 4px 26px;
  padding-left: 10px; border-left: 1px solid var(--border); }
.pbar-choose.hidden { display: none; }
.pbar-item { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 7px;
  cursor: pointer; font-size: 13px; color: var(--text); }
.pbar-item:hover { background: var(--bg-2); }
.pbar-item.lead { cursor: default; }
.pbar-item input { accent-color: var(--accent); flex-shrink: 0; }
.pbar-item .pbar-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pbar-item small { color: var(--text-faint); font-size: 11px; flex-shrink: 0; }
.pbar-av { width: 20px; height: 20px; border-radius: 50%; background: var(--bg-3); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; }
.pbar-order { width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0; font-size: 10.5px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; background: var(--accent); color: var(--on-accent); }
.pbar-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint); flex-shrink: 0; margin-left: auto; box-sizing: border-box; }
/* Same vocabulary again — fill = a person is there, colour = whether we know. */
.pbar-dot.active { background: var(--ok); }
.pbar-dot.idle { background: var(--warn); }
.pbar-dot.away { background: transparent; border: 1.5px solid var(--ok); }
.pbar-dot.online { background: transparent; border: 1.5px solid var(--ok); }
.pbar-dot.unknown { background: transparent; border: 1.5px dashed var(--text-faint); opacity: .8; }
.pbar-dot.offline { background: var(--text-faint); }
.pbar-dot.local { background: var(--accent); }
.pbar-moves { display: inline-flex; align-items: center; gap: 1px; margin-left: auto; }
.pbar-move { border: none; background: none; color: var(--text-faint); cursor: pointer; font-size: 10px;
  line-height: 1; padding: 3px; border-radius: 5px; }
.pbar-move:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); }
.pbar-move:disabled { opacity: .3; cursor: default; }
.pbar-empty { font-size: 12.5px; color: var(--text-faint); padding: 4px; }

/* ---- Inbox / Action Center ---- */
.inbox-list { display: flex; flex-direction: column; gap: 12px; max-width: 780px; }
.inbox-group-title {
  font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-dim); margin: 8px 2px 0; display: flex; align-items: center; gap: 8px;
}
.inbox-group-title.inbox-group-dim { color: var(--text-faint); }
.inbox-count {
  background: var(--accent); color: var(--on-accent); border-radius: 999px;
  min-width: 18px; height: 18px; padding: 0 5px; font-size: 11px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
.inbox-count-dim { background: var(--bg-3); color: var(--text-dim); }
/* Collapsible "Recent" history header — same uppercase label language as a group title */
.inbox-recent-toggle {
  font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-faint); margin: 10px 2px 0; padding: 4px 4px; width: 100%;
  display: flex; align-items: center; gap: 8px; background: none; border: none;
  cursor: pointer; text-align: left; border-radius: 8px;
}
.inbox-recent-toggle:hover { background: var(--bg-hover); color: var(--text-dim); }
.inbox-recent-caret { display: inline-block; transition: transform .15s ease; font-size: 10px; }
.inbox-recent-toggle.open .inbox-recent-caret { transform: rotate(90deg); }
/* --- One task's cards, folded into a single expandable line -------------------
   A task that fanned out into eleven approvals is ONE thing to decide about, so it
   reads as one row until you open it. Near-duplicate tasks merge into the same
   group, which is what the "N similar tasks" sub-label is telling you. */
.inbox-grp { margin: 2px 0; }
.inbox-grp-head {
  width: 100%; display: flex; align-items: center; gap: 9px; padding: 9px 10px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px;
  cursor: pointer; text-align: left; color: var(--text);
}
.inbox-grp-head:hover { border-color: var(--accent); background: var(--accent-soft); }
.inbox-grp-caret { flex-shrink: 0; width: 10px; display: inline-block; transition: transform .15s ease;
  font-size: 13px; line-height: 1; color: var(--text-dim); }
.inbox-grp-head:hover .inbox-grp-caret { color: var(--accent-2); }
.inbox-grp.open .inbox-grp-caret { transform: rotate(90deg); }
.inbox-grp-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.inbox-grp-title { font-size: 13px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inbox-grp-sub { font-size: 11.5px; color: var(--text-faint); }
/* An approval nearing its expiry: the age is the actionable fact, so it stops being
   grey furniture and starts reading as a deadline. */
.inbox-row-when.stale { color: var(--warn, #e0a800); font-weight: 700; }
.inbox-expired { color: var(--text-faint); border: 1px solid var(--border); border-radius: 5px;
  padding: 0 4px; font-size: 10px; text-transform: uppercase; letter-spacing: .04em; }
/* Decide the whole group in one go — sits above the individual rows, because for a
   task that fanned out into 25 sends the bulk action is the normal case. */
.inbox-grp-bulk { display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin: 6px 0 2px 21px; }
.inbox-grp-bulk .btn-primary, .inbox-grp-bulk .btn-mini { padding: 6px 12px; font-size: 12.5px; }
.inbox-grp-bulkhint { font-size: 11.5px; color: var(--text-faint); }
/* Indented + rail so an open group's rows read as belonging to it. */
.inbox-grp.open .inbox-grp-items {
  margin: 3px 0 8px 12px; padding-left: 9px; border-left: 2px solid var(--border);
  display: flex; flex-direction: column; gap: 2px;
}
/* --- Memory review: a night's compaction batch as a per-item checklist ---------
   Replaces the old body-as-wall-of-text for ref_type="compaction". Groups collapse
   (big batches start closed) and every row is independently tickable. */
.cmp-review { display: flex; flex-direction: column; gap: 10px; margin: 14px 0 4px; }
.cmp-lead { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.cmp-lead-count { font-size: 13.5px; font-weight: 600; color: var(--text); }
.cmp-lead-count b { color: var(--accent); }
.cmp-lead-hint { font-size: 12.5px; color: var(--text-faint); }
/* The "what am I even approving?" banner. It sits ABOVE the groups and states what a
   tick and an untick each do — with 100+ rows the old faint one-line hint scrolled out
   of sight long before you reached a checkbox. */
.cmp-intro {
  display: flex; flex-direction: column; gap: 8px; padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
  border-radius: var(--radius); background: color-mix(in srgb, var(--accent) 7%, var(--bg-2));
}
.cmp-intro-lead { font-size: 13px; color: var(--text); line-height: 1.5; }
.cmp-legend { display: flex; flex-wrap: wrap; gap: 6px 20px; }
.cmp-leg { display: flex; align-items: baseline; gap: 7px; font-size: 12.5px; color: var(--text-dim); }
.cmp-leg b { color: var(--text); font-weight: 600; }
.cmp-box {
  flex: none; width: 14px; height: 14px; border-radius: 4px; border: 1.5px solid var(--border);
  background: var(--bg); font-size: 10px; line-height: 12px; text-align: center; align-self: center;
}
.cmp-box.on { background: var(--accent); border-color: var(--accent); color: #fff; }
/* What applying this whole group does — visible even while the group is collapsed. */
.cmp-gnote {
  padding: 0 12px 10px 33px; margin-top: -4px;
  font-size: 12px; line-height: 1.5; color: var(--text-faint);
}
.cmp-group.danger .cmp-gnote { color: var(--danger); }
/* Per-row verb, so a row read on its own still says what happens to it. */
.cmp-act {
  display: inline-block; margin-right: 7px; padding: 1px 7px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  vertical-align: 1px; background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}
.cmp-act.danger { background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger); }
.cmp-row.off .cmp-act { background: color-mix(in srgb, var(--text-faint) 14%, transparent); color: var(--text-faint); }
.cmp-group { border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-2); overflow: hidden; }
.cmp-group.danger { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }
.cmp-ghead { display: flex; align-items: center; gap: 6px; padding-right: 8px; }
.cmp-gtoggle {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 9px;
  padding: 11px 12px; border: none; background: none; color: var(--text);
  font-size: 13.5px; font-weight: 600; text-align: left; cursor: pointer;
}
.cmp-gtoggle:hover { background: var(--bg-hover); }
.cmp-caret { color: var(--text-faint); font-size: 10px; transition: transform .15s ease; }
.cmp-group.open .cmp-caret { transform: rotate(90deg); }
/* Labels now name the ACTION ("Retire the old wording — …"), so they wrap rather than
   ellipsise — a truncated label is exactly what made the decision unreadable. */
.cmp-glabel { flex: 1; min-width: 0; line-height: 1.4; overflow-wrap: anywhere; }
.cmp-group.danger .cmp-glabel { color: var(--danger); }
.cmp-gcount { color: var(--text-faint); font-size: 12px; font-weight: 500; white-space: nowrap; }
.cmp-gcount b { color: var(--accent); font-weight: 700; }
.cmp-gall {
  border: 1px solid var(--border); background: var(--bg); color: var(--text-dim);
  border-radius: 7px; padding: 4px 9px; font-size: 11.5px; white-space: nowrap; cursor: pointer;
}
.cmp-gall:hover { color: var(--text); border-color: var(--accent); }
.cmp-rows { display: none; flex-direction: column; border-top: 1px solid var(--border); max-height: 420px; overflow-y: auto; }
.cmp-group.open .cmp-rows { display: flex; }
.cmp-row {
  display: flex; align-items: flex-start; gap: 10px; padding: 9px 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); cursor: pointer;
}
.cmp-row:last-child { border-bottom: none; }
.cmp-row:hover { background: var(--bg-hover); }
.cmp-row input { margin-top: 2px; accent-color: var(--accent); flex: none; cursor: pointer; }
.cmp-row-main { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cmp-row-text { font-size: 13px; color: var(--text); line-height: 1.45; overflow-wrap: anywhere; }
.cmp-row-sub { font-size: 12px; color: var(--text-faint); overflow-wrap: anywhere; }
/* Unticked = "keep this as-is" — dim it so what you're APPLYING stays scannable. */
.cmp-row.off .cmp-row-text { color: var(--text-faint); text-decoration: line-through; text-decoration-color: var(--text-faint); }
.cmp-row.off .cmp-row-sub { opacity: .6; }
.cmp-group.danger .cmp-row-sub { color: var(--danger); }
/* Provenance chip on a mined suggestion — click to open the chat it came from. */
.cmp-prov {
  border: 1px solid var(--border); background: var(--bg-3); color: var(--text-dim);
  border-radius: 999px; padding: 1px 9px; font-size: 11px; cursor: pointer;
}
.cmp-prov:hover { color: var(--accent); border-color: var(--accent); }
/* "You already remember something similar" — the near neighbours of a mined proposal.
   Deliberately quiet: it is context for the tick, not a warning, and it must not
   out-shout the proposal itself. The rule sits left, so a row with neighbours is
   scannable as one at a glance. */
.cmp-row-known {
  margin-top: 3px; padding-left: 8px; border-left: 2px solid var(--border);
  display: flex; flex-direction: column; gap: 2px;
  font-size: 11.5px; color: var(--text-faint); line-height: 1.4;
}
.cmp-row-known b { font-weight: 600; color: var(--text-dim); }
.cmp-known-item { overflow-wrap: anywhere; }
.cmp-row.off .cmp-row-known { opacity: .55; }

/* --- Actionable digest: "what I'd do next", tickable, run in parallel ----------
   Reuses the .cmp-row anatomy so a review row and a proposal row read the same. */
.dact { margin: 16px 0 4px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-2); overflow: hidden; }
.dact-head { display: flex; flex-direction: column; gap: 3px; padding: 11px 13px 9px; border-bottom: 1px solid var(--border); }
.dact-title { font-size: 13.5px; font-weight: 700; color: var(--text); }
.dact-rows { display: flex; flex-direction: column; max-height: 380px; overflow-y: auto; }
.dact .cmp-row-sub { color: var(--accent); }
.dact .cmp-row.off .cmp-row-sub { color: var(--text-faint); }
.dact-tag {
  margin-left: 8px; padding: 1px 7px; border-radius: 999px; font-size: 10.5px;
  font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  background: color-mix(in srgb, var(--danger) 16%, transparent); color: var(--danger);
}
.dact .inbox-actions { padding: 11px 13px; border-top: 1px solid var(--border); margin: 0; }

.inbox-allclear { color: var(--text-faint); font-size: 13px; padding: 14px 4px 4px; }
.inbox-reading-sub { color: var(--text-faint); font-size: 12.5px; }
.inbox-card {
  background: var(--bg-2); border: 1px solid var(--border); border-left: 3px solid var(--border);
  border-radius: var(--radius); padding: 14px 16px; display: flex; flex-direction: column; gap: 10px;
}
.inbox-card.inbox-approval { border-left-color: var(--ok); }
.inbox-card.inbox-question { border-left-color: #e0a640; }
.inbox-card.inbox-status { border-left-color: #5a9bd8; }
.inbox-card.inbox-digest { border-left-color: #9b8cff; }
.inbox-card.inbox-dealt { opacity: .62; }
.inbox-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.inbox-kind {
  font-size: 11px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  color: var(--text-dim); white-space: nowrap;
}
.inbox-title { font-weight: 600; color: var(--text); flex: 1; min-width: 0; }
.inbox-when { font-size: 11.5px; color: var(--text-faint); white-space: nowrap; margin-left: auto; }
.inbox-src-row { font-size: 12px; color: var(--text-faint); margin: -2px 0 2px; }
.inbox-src-row a { color: var(--accent-2); text-decoration: none; }
.inbox-src-row a:hover { text-decoration: underline; }
.inbox-src-when { white-space: nowrap; }
/* The card's own stamp is a date now, not just a time — give it room not to wrap. */
.idet-when { white-space: nowrap; }
.inbox-body { font-size: 13.5px; color: var(--text-dim); line-height: 1.6; }
.inbox-body.md p { margin: 0 0 6px; }
.inbox-body.md p:last-child { margin-bottom: 0; }
.inbox-body pre, .inbox-body code { font-family: var(--mono); font-size: 12.5px; }
.inbox-action-hint { font-size: 12px; color: var(--text-faint); }
.inbox-action-hint code { font-family: var(--mono); background: var(--bg-3); padding: 1px 5px; border-radius: 5px; }
.inbox-actions { display: flex; gap: 8px; flex-wrap: wrap; }
/* "✓ Task done" closes real work, so it reads as a settle rather than as another grey
   secondary — but it stays a mini button, because the primary on these cards is still
   answering the question, not abandoning it. */
.btn-taskdone { color: var(--ok); border-color: transparent; background: var(--ok-soft); }
.btn-taskdone:hover { background: var(--ok-soft); filter: brightness(1.12); }

/* The literal payload an Approve will send. Open by default — the whole point is
   that it is read before the button is pressed, not found by someone who went looking. */
.inbox-payload {
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 9px; padding: 9px 11px; margin-bottom: 9px; font-size: 12.5px;
}
.inbox-payload > summary {
  cursor: pointer; font-weight: 600; color: var(--text-dim);
  font-size: 12px; list-style: none; display: flex; align-items: center; gap: 8px;
}
.inbox-payload > summary::-webkit-details-marker { display: none; }
.inbox-payload[open] > summary { margin-bottom: 8px; }
.pl-tool {
  margin-left: auto; font-family: var(--mono); font-size: 11px;
  color: var(--text-faint); background: var(--bg-3); padding: 1px 6px; border-radius: 5px;
}
.pl-body-wrap { display: flex; flex-direction: column; gap: 6px; }
.pl-row { display: flex; gap: 10px; align-items: baseline; }
.pl-key {
  color: var(--text-faint); font-size: 11.5px; font-weight: 600;
  min-width: 74px; flex-shrink: 0;
}
.pl-val { color: var(--text); word-break: break-word; }
/* An id with no resolved name: shown shortened (full value in the tooltip), and
   quietly, so it reads as plumbing rather than as the answer to "send it to whom?". */
.pl-idraw { color: var(--text-faint); font-family: var(--mono, ui-monospace, monospace); font-size: 12px; }
/* A resolved Teams name that links to the real chat/team/channel. */
.pl-row-wrap { flex-wrap: wrap; }
.pl-link { color: var(--accent); text-decoration: none; }
.pl-link:hover { text-decoration: underline; }

.pl-redacted { color: var(--text-faint); font-style: italic; }
.pl-block { display: flex; flex-direction: column; gap: 3px; }
.pl-body {
  margin: 0; max-height: 260px; overflow: auto; white-space: pre-wrap; word-break: break-word;
  background: var(--bg); border: 1px solid var(--border); border-radius: 7px;
  padding: 8px 10px; font-family: inherit; font-size: 12.5px; line-height: 1.5; color: var(--text);
}
/* A drafted message is Markdown. Shown as raw source, its comparison table was a wall
   of pipes at the exact moment the reader has to decide whether to send it — so the
   block renders the formatting and keeps the literal string behind "exact text".
   `pre-wrap` has to go in the rendered view: the block elements carry their own
   spacing, and pre-wrap would add the source's newlines on top of them. */
.pl-body.pl-rendered { white-space: normal; }
.pl-body.pl-rendered > :first-child { margin-top: 0; }
.pl-body.pl-rendered > :last-child { margin-bottom: 0; }
.pl-body.pl-rendered .md-table { font-size: 12px; margin: 6px 0; }
.pl-body.pl-rendered th, .pl-body.pl-rendered td {
  border: 1px solid var(--border); padding: 4px 8px; text-align: left;
}
.pl-body.pl-rendered th { background: var(--bg-2); font-weight: 600; }
.pl-hasmd .pl-key { display: flex; align-items: baseline; gap: 8px; }
.pl-raw-toggle {
  background: none; border: none; padding: 0; cursor: pointer; font: inherit;
  font-size: 11px; color: var(--accent); text-decoration: underline;
}
.pl-raw-toggle:hover { color: var(--accent-2); }
.inbox-answer { display: flex; flex-direction: column; gap: 8px; }
/* Where the answer goes — above the box, so it is read before it is needed. */
.inbox-dest { font-size: 12px; color: var(--text-faint); line-height: 1.5; }
.inbox-dest .inbox-taskref { font-weight: 600; }
.inbox-taskref {
  color: var(--accent-2); text-decoration: none; font-weight: 600;
  white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  display: inline-block; vertical-align: bottom;
}
.inbox-taskref:hover { text-decoration: underline; }
.inbox-answer-input {
  width: 100%; background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  color: var(--text); padding: 8px 10px; font-family: var(--font); font-size: 13px; resize: vertical;
}
.inbox-answer-input:focus { outline: none; border-color: var(--text-dim); }
.inbox-resolved { font-size: 12px; font-weight: 700; color: var(--text-faint); }
.inbox-card { cursor: pointer; }
.inbox-open-hint { font-size: 11px; color: var(--text-faint); }
.inbox-card:hover .inbox-open-hint { color: var(--text-dim); }
.inbox-card.inbox-dealt .inbox-open-hint { display: none; }

/* Inbox item detail modal — full problem + the AI's suggested answer */
.idet-modal { max-width: 600px; padding: 0 22px 22px; overflow-y: auto; }
.idet-loading { padding: 44px; text-align: center; color: var(--text-faint); }
.idet-head { display: flex; align-items: center; gap: 10px; position: sticky; top: 0;
  background: var(--bg-2); padding: 18px 0 12px; z-index: 1; }
.idet-when { font-size: 11.5px; color: var(--text-faint); margin-left: auto; }
.idet-close { border: none; background: none; color: var(--text-dim); font-size: 15px; cursor: pointer;
  width: 28px; height: 28px; border-radius: 8px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.idet-close:hover { background: var(--bg-hover); color: var(--text); }
.idet-title { font-size: 18px; margin: 0 0 12px; color: var(--text); line-height: 1.35; }
.idet-suggest { margin: 14px 0; padding: 12px 14px; border-radius: 12px; background: var(--accent-soft); }
.idet-suggest-head { font-size: 12px; font-weight: 700; color: var(--accent-2); margin-bottom: 6px; }
.idet-suggestion { font-size: 13.5px; color: var(--text); line-height: 1.6; }
.idet-aic { font-size: 11.5px; font-weight: 600; color: var(--accent-2); margin-bottom: 8px;
  padding: 5px 9px; border-radius: 8px; background: var(--bg-2); display: inline-block; }
/* "Tick what's suggested" — turns the suggestion's prose into checkbox state on a
   tickable card. Sits under the suggestion because that is what it acts on, and stays a
   mini button: it only moves ticks, and the button that actually applies them is the
   primary one further down the card. */
.idet-apply-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.idet-apply { background: var(--bg-2); border: 1px solid var(--accent); color: var(--accent-2);
  font-weight: 600; cursor: pointer; }
.idet-apply:hover { background: var(--bg-hover); }
.idet-apply-hint { font-size: 11.5px; color: var(--text-faint); flex: 1; min-width: 160px; }
/* "Working out an Inbox item" bar shown atop a scoped chat/discussion (one-click settle) */
.inbox-settle-bar { display: flex; align-items: center; gap: 12px; margin: 8px 16px 0;
  padding: 8px 14px; border-radius: 10px; background: var(--accent-soft);
  border: 1px solid var(--accent); }
.inbox-settle-bar .isb-text { font-size: 12.5px; color: var(--text); font-weight: 500; }
.inbox-settle-bar .isb-title { color: var(--accent-2); font-weight: 600; margin-left: 6px; }
.inbox-settle-bar .isb-settle { margin-left: auto; border: 1px solid var(--accent);
  background: var(--accent); color: #fff; border-radius: 8px; padding: 6px 12px;
  font-size: 12.5px; font-weight: 600; cursor: pointer; white-space: nowrap; }
.inbox-settle-bar .isb-settle:hover { filter: brightness(1.08); }
.inbox-settle-bar .isb-settle:disabled { opacity: .6; cursor: default; }
.idet-suggestion.loading { color: var(--text-faint); font-style: italic; }
.idet-suggestion p { margin: 0 0 6px; } .idet-suggestion p:last-child { margin-bottom: 0; }
.idet-discuss-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 12px 0 4px; }
.idet-discuss { background: var(--bg-2); border: 1px solid var(--accent); color: var(--accent-2);
  border-radius: 999px; padding: 7px 16px; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap; }
.idet-discuss:hover { background: var(--accent-soft); color: var(--text); }
.idet-team { border-color: var(--border); color: var(--text-dim); }   /* secondary: bring the team in */
.idet-team:hover { border-color: var(--accent); color: var(--text); background: var(--bg-hover); }
.idet-discuss-hint { font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }
.idet-actions { margin-top: 12px; }

/* ask_user — clickable guided choices in chat (the "offer options like Opus" tool) */
.user-choices { align-self: stretch; margin: 8px 0 4px; }
.uc-q { font-size: 13.5px; font-weight: 600; color: var(--text); margin-bottom: 8px; }
.uc-opts { display: flex; flex-direction: column; gap: 8px; }
.uc-opt { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; text-align: left;
  width: 100%; background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 10px 14px; cursor: pointer; color: var(--text); transition: background .12s, border-color .12s; }
.uc-opt:hover:not(:disabled) { background: var(--bg-hover); border-color: var(--accent); }
.uc-opt.on, .uc-opt.chosen { background: var(--accent-soft); border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.uc-opt:disabled { cursor: default; }
.uc-label { font-weight: 600; font-size: 13.5px; }
.uc-desc { font-size: 12px; color: var(--text-faint); line-height: 1.45; }
.uc-send { margin-top: 8px; background: var(--accent); color: var(--on-accent); border: none;
  border-radius: 999px; padding: 6px 16px; font-size: 13px; font-weight: 600; cursor: pointer; }
.uc-send:disabled { opacity: .5; cursor: default; }
.user-choices.answered { opacity: .75; }
/* Multi-question (tabbed) variant — ask several decisions at once. */
.mq-intro { font-size: 12.5px; color: var(--text-dim); margin-bottom: 8px; }
.mq-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px;
  border-bottom: 1px solid var(--border); padding-bottom: 8px; }
.mq-tab { display: inline-flex; align-items: center; gap: 5px; background: var(--bg-2);
  border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px; font-size: 12.5px;
  font-weight: 600; color: var(--text-dim); cursor: pointer; transition: background .12s, color .12s, border-color .12s; }
.mq-tab:hover:not(:disabled) { border-color: var(--accent); }
.mq-tab.active { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }
.mq-tab .mq-check { color: var(--accent); font-weight: 700; font-size: 11px; }
.mq-tab .mq-check.hidden { display: none; }
.mq-hint { font-size: 11.5px; color: var(--text-faint); margin-top: 6px; }
.mq-submit { margin-top: 10px; }
.uc-other .uc-label { color: var(--text-dim); }
.uc-otherbox { display: flex; gap: 8px; margin-top: 8px; }
.uc-otherbox.hidden { display: none; }
.uc-otherinput { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  color: var(--text); padding: 8px 12px; font-family: var(--font); font-size: 13.5px; }
.uc-otherinput:focus { outline: none; border-color: var(--accent); }
.uc-othersend { background: var(--accent); color: var(--on-accent); border: none; border-radius: 999px;
  padding: 6px 16px; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap; }
.uc-othersend:disabled { opacity: .5; cursor: default; }
.inbox-result {
  font-size: 12.5px; color: var(--text-dim); line-height: 1.55;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px;
}

/* ============================================================
   REDESIGN LAYER (2026-07) — appended last so it wins the cascade.
   Upgrades hierarchy, spacing and readability across the app and
   makes every hardcoded dark-only colour work in the light theme.
   Component markup and class names are unchanged.
   ============================================================ */

/* ---- global polish ---- */
body { font-size: 14.5px; }
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
h1, h2, h3 { letter-spacing: -0.01em; }

/* ---- sidebar ---- */
#sidebar { width: var(--rail-w); padding: 12px; gap: 10px; }
.brand { padding: 6px 8px 2px; }
.side-item {
  color: var(--text-dim); font-size: 13.5px; padding: 7px 10px; border-radius: 8px;
}
.side-item:hover { color: var(--text); }
.side-item.active, .skill-nav .side-item.active {
  background: var(--accent-soft); color: var(--text); font-weight: 600;
}
.side-section-title {
  font-size: 10px; letter-spacing: .09em; color: var(--text-faint);
  padding: 8px 10px 3px; font-weight: 700;
}
.nav-badge { background: var(--accent); color: var(--on-accent); }
#inbox-badge { background: var(--danger); color: #fff; }
.avatar-mini { background: linear-gradient(135deg, var(--accent), #9c6bf7); color: #fff; }
.logout-btn { padding: 7px 10px; display: inline-flex; align-items: center; justify-content: center; }
#theme-toggle:hover { color: var(--accent-2); border-color: var(--accent); background: var(--accent-soft); }

/* ---- chat: assistant text reads like a document, user keeps a bubble ---- */
.msg-row { max-width: 780px; }  /* match the composer width so chat + input align */
.msg-row.assistant .msg-content {
  background: transparent; border: none; border-radius: 0; padding: 2px 0;
  align-self: stretch;  /* fill the column so text lines up with the Workbench card */
}
.msg-row.user .msg-content { background: var(--user-bubble); border-radius: 16px 16px 4px 16px; max-width: 75%; }
.msg-row.peer .msg-content { border-radius: 16px 16px 16px 4px; }
.msg-content pre { color: #dfe3ea; }
.msg-content pre code { color: inherit; }
.tool-detail { color: #b9c0ca; }
.composer-inner { box-shadow: var(--shadow-1); }
.composer-inner:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* ---- page scaffolding ---- */
.page-head { padding: 24px 32px 0; }
.page-head h1 { font-size: 21px; }
.page-body { padding: 22px 32px 44px; }
.page-head-icon { border-radius: 12px; font-size: 26px; width: 50px; height: 50px; }

/* ---- cards get depth; borders soften ---- */
.market-card, .task-card, .inbox-card, .gc-item, .skill-result, .skill-schedule {
  box-shadow: var(--shadow-1);
}
.market-card:hover { border-color: var(--accent); box-shadow: var(--shadow-2); }
.drop-menu { box-shadow: var(--shadow-2); }
.modal { box-shadow: var(--shadow-2); }

/* ---- tasks board ---- */
.task-col { background: var(--bg-2); }
:root[data-theme="light"] .task-col { background: #f1f1f5; border-color: transparent; }
:root[data-theme="light"] .task-card { background: var(--bg-2); }
.task-card { border-radius: 10px; }
.task-card:hover { border-color: var(--accent); }

/* ---- Home dashboard ---- */
/* Home's scroller and its reading column are the SAME element — `class="page-body
   home-body"` — so capping the width here capped the SCROLLING BOX, and its scrollbar
   sat ~190px in from the window edge while every other view's sat at the edge.
   The column is still 1180px and still centred; the PADDING centres it now, which
   leaves `.page-body` full width and puts its scrollbar back where the rest of the
   app keeps it. `max()` holds the normal 32px gutter on narrower screens. */
.home-body { padding-inline: max(32px, calc((100% - 1180px) / 2)); }
.home-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; padding: 14px 0 20px; flex-wrap: wrap; }
.home-head h1 { margin: 0; font-size: 26px; font-weight: 680; letter-spacing: -.02em; }
/* The old scale jumped 26px -> 13px with nothing between, so nothing read as a
   section. 16px/650 card headings (above) are the missing middle step. */
/* STRETCH, so two cards sharing a grid row end on the same line. `start` let every card
   be its own height, which is fine when they happen to match and ragged the moment they
   do not — "Your automations ran" with two rows sat beside "From your AI" with five, and
   the gap under the short one read as a rendering fault.
   ⚠️ The original reason for `start` still holds and is preserved below: a FOLDED card is
   its header, and stretching that to the height of an open neighbour would leave a tall
   empty box with a title in it. So the exemption is per-card, not per-grid. */
.home-grid { align-items: stretch; }
.home-grid > .home-card.folded { align-self: start; }
.home-date { margin: 4px 0 0; color: var(--text-faint); font-size: 13px; }
.home-quick { display: flex; gap: 8px; align-items: center; }
.home-quick .btn-mini { padding: 9px 14px; }
.home-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 12px; margin-bottom: 18px; }
.home-stat-tile {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px;
  padding: 15px 18px; display: flex; flex-direction: column; gap: 3px; align-items: flex-start;
  cursor: pointer; box-shadow: var(--shadow-1); text-align: left;
  transition: border-color .15s ease, transform .15s ease;
}
.home-stat-tile:hover { border-color: var(--accent); transform: translateY(-1px); }
.home-stat-n { font-size: 30px; font-weight: 700; line-height: 1.05; font-variant-numeric: tabular-nums; color: var(--text); }
.home-stat-n.ready { color: var(--accent-2); }
.home-stat-n.blocked { color: var(--warn); }
.home-stat-n.accent { color: var(--accent); }
.home-stat-label { font-size: 11.5px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .05em; font-weight: 600; }

/* Inbox — full-bleed 4-column hub: folders | list | reading | Your AI.
   Same shell as Group Chat (.gc-hub), one column wider. */
.ibx-hub {
  flex: 1; min-height: 0; display: grid; background: var(--bg); position: relative;
  /* The rail REPLACES the app sidebar, so it is the app sidebar's width — `--rail-w`,
     which every rail in the product now shares. It was squeezed to 200 to buy room for
     the fourth column,
     which made the Inbox the one view where the left edge of the app moved when you
     navigated to it. The reading pane gives the room up instead; it is the column that
     can flex. The AI panel is the widest of the three fixed ones because it is no longer
     a sidebar: it holds the draft, the verbs and the ask, and 340px clipped all three. */
  grid-template-columns: var(--rail-w) 280px minmax(0, 1fr) 500px;
}
.ibx-rail { border-right: 1px solid var(--border); background: var(--bg-2); display: flex; flex-direction: column; min-height: 0; }
.ibx-rail-head { display: flex; align-items: center; gap: 8px; padding: 14px 12px 10px; border-bottom: 1px solid var(--border); }
.ibx-rail-title { font-weight: 700; font-size: 15px; flex: 1; }
/* 12px inset + the row's own 10px = the app sidebar's 22px text edge, so the folders sit
   exactly where the nav items they replaced did. */
.ibx-rail-list { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; }
/* A section head (Needs you / Updates / Done) is a destination in its own right — it
   holds everything in the folders under it — so it is a button too, just a heavier one.
   Touring three folders to clear your work is exactly what this avoids. */
.ibx-fold {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  border: none; background: none; color: var(--text-dim); cursor: pointer;
  border-radius: 9px; padding: 7px 10px 7px 22px; font: inherit; font-size: 13px;
}
.ibx-fold:hover { background: var(--bg-hover); color: var(--text); }
.ibx-fold.on { background: var(--accent-soft); color: var(--text); font-weight: 600; }
.ibx-fold-head { padding-left: 10px; margin-top: 10px; font-weight: 700; font-size: 13.5px; color: var(--text); }
.ibx-fold-head:first-child { margin-top: 0; }
/* Stroke icons, sized and dimmed exactly as the app sidebar's are, so the rail reads as
   the same piece of furniture. Colour comes from the row — one accent when selected,
   muted otherwise — which is the whole reason for dropping emoji. */
.ibx-ico { width: 17px; height: 17px; display: block; }
.ibx-fold-ico { flex-shrink: 0; display: flex; opacity: .72; }
.ibx-fold:hover .ibx-fold-ico, .ibx-fold.on .ibx-fold-ico { opacity: 1; }
.ibx-lh-title { display: inline-flex; align-items: center; gap: 8px; }
.ibx-ico.lh { width: 16px; height: 16px; }
/* The source line's markers: inline with the text they label, at its weight. */
.ibx-ico.xs { width: 13px; height: 13px; display: inline-block; vertical-align: -2px; opacity: .8; }
.ibx-fold-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Two badge weights on purpose. A decision waiting on you is loud; unread news is
   quiet. Same number, different urgency — see `counts()` in backend/inbox_view.py. */
.ibx-fold-n {
  flex-shrink: 0; font-size: 11px; font-weight: 700; min-width: 20px; text-align: center;
  padding: 1px 6px; border-radius: 999px; background: var(--bg-3); color: var(--text-dim);
}
.ibx-fold-n.loud { background: var(--accent); color: var(--on-accent, #fff); }
.ibx-listcol { display: flex; flex-direction: column; min-width: 0; min-height: 0; border-right: 1px solid var(--border); background: var(--bg); }
.ibx-listhead { flex-shrink: 0; padding: 13px 14px 9px; border-bottom: 1px solid var(--border); background: var(--bg-2); }
.ibx-lh-top { display: flex; align-items: center; gap: 8px; }
.ibx-lh-title { font-size: 14.5px; font-weight: 700; color: var(--text); }
.ibx-lh-n { font-size: 11.5px; color: var(--text-faint); }
.ibx-lh-read { margin-left: auto; font: inherit; font-size: 11.5px; padding: 2px 9px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg); color: var(--text-dim); cursor: pointer; }
.ibx-lh-read:hover { border-color: var(--accent); color: var(--text); }
.ibx-lh-blurb { font-size: 11.5px; color: var(--text-faint); line-height: 1.5; margin-top: 3px; }
/* Select all: the same 15px box the rows carry, so it reads as the column's own tick
   rather than a button that happens to sit above them. */
.ibx-lh-sel { margin-top: 8px; }
.ibx-lh-selall {
  display: inline-flex; align-items: center; gap: 7px; font: inherit; font-size: 11.5px;
  font-weight: 600; padding: 3px 10px 3px 6px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg); color: var(--text-dim); cursor: pointer;
}
.ibx-lh-selall:hover { border-color: var(--accent); color: var(--text); }
.ibx-lh-selall.on { border-color: var(--accent); color: var(--text); }
.ibx-lh-selbox {
  width: 15px; height: 15px; border-radius: 4px; flex-shrink: 0;
  border: 1.5px solid var(--border-strong, var(--border)); background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; line-height: 1; color: var(--on-accent, #fff);
}
.ibx-lh-selall.on .ibx-lh-selbox { background: var(--accent); border-color: var(--accent); }
.ibx-read { min-width: 0; min-height: 0; overflow-y: auto; padding: 22px 26px 40px; background: var(--bg); }
/* Reading several at once. Each card keeps the reading pane's own layout — it IS that
   renderer — so the stack is separated by rules and spacing rather than by restyling the
   cards into something that looks like a different feature. */
.ibx-stack-head {
  display: flex; flex-direction: column; gap: 2px; margin-bottom: 14px; padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.ibx-stack-n { font-size: 13px; font-weight: 700; color: var(--text); }
.ibx-stack-sub { font-size: 11.5px; color: var(--text-faint); line-height: 1.5; }
.ibx-stack-hrow { display: flex; align-items: center; gap: 10px; }
.ibx-stack-all { flex-shrink: 0; }
/* A card folds to its title row. Fourteen fully-drawn cards is not a stack, it is one
   endless scroll — the fold is what lets you see the whole selection, open the one you
   are working on, and put it down again. */
.ibx-stack-card {
  display: block; margin: 0 0 10px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--bg-2); overflow: hidden;
}
.ibx-stack-card.open { margin-bottom: 18px; }
.ibx-stack-hd {
  width: 100%; display: flex; align-items: center; gap: 9px; text-align: left;
  padding: 11px 14px; font: inherit; font-size: 13.5px; font-weight: 600; color: var(--text);
  background: none; border: 0; cursor: pointer;
}
.ibx-stack-hd:hover { background: var(--bg-3); }
.ibx-stack-caret { flex-shrink: 0; color: var(--text-faint); transition: transform .14s ease; }
.ibx-stack-card.open .ibx-stack-caret { transform: rotate(90deg); }
.ibx-stack-ico { flex-shrink: 0; display: inline-flex; opacity: .85; }
.ibx-stack-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ibx-stack-done {
  flex-shrink: 0; font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-faint);
}
.ibx-stack-when { flex-shrink: 0; font-size: 11px; font-weight: 500; color: var(--text-faint); }
.ibx-stack-body:not(:empty) { padding: 2px 16px 4px; border-top: 1px solid var(--border); }
.ibx-stack-card .idet-title { font-size: 16.5px; margin: 8px 0 12px; }
/* Where a row click lands you. Two seconds of accent edge, then back — long enough to
   find, short enough not to look like a selected state. */
.ibx-stack-card.flash { border-color: var(--accent); box-shadow: var(--shadow-1); }
.ibx-stack-more {
  font-size: 12px; color: var(--text-faint); line-height: 1.55; padding: 12px 14px;
  border: 1px dashed var(--border); border-radius: 12px;
}
.ibx-read .idet-head { display: flex; align-items: center; gap: 10px; }
.ibx-read .idet-title { margin: 10px 0 14px; font-size: 20px; }
/* The four slots. Each one is labelled, so a draft never has to be told apart from a
   report by its tone — the heading says which it is. */
.ibx-slot { margin: 0 0 16px; }
.ibx-slot-h {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .09em;
  color: var(--text-faint); margin-bottom: 6px;
}
.ibx-slot-ask { border-left: 3px solid var(--accent); padding: 2px 0 2px 12px; }
.ibx-ask { font-size: 14px; color: var(--text); line-height: 1.55; }
.ibx-ask-det { font-size: 12.5px; color: var(--accent-2); margin-top: 4px; overflow-wrap: anywhere; }
.ibx-slot-none { font-size: 13px; color: var(--text-faint); font-style: italic; }
.ibx-slot-decide { border-top: 1px solid var(--border); padding-top: 14px; margin-top: 20px; }
/* The transcript sits last: it is reference material for the decision above it, so it
   must never push the buttons down the page on a long thread. */
.ibx-slot-thread { border-top: 1px solid var(--border); padding-top: 16px; margin-top: 22px; }
.ibx-settled {
  font-size: 12.5px; color: var(--text-dim); background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 9px 12px; margin-bottom: 16px;
}
/* "9 of 11" — this card's place in a fan-out, lifted out of the title so the title can
   be the subject. It is the only thing telling eleven near-identical rows apart. */
.ibx-seq {
  display: inline-block; font-size: 10.5px; font-weight: 700; color: var(--text-dim);
  background: var(--bg-3); border-radius: 5px; padding: 0 5px; margin-right: 6px;
}
.ibx-seq-big { font-size: 11px; padding: 1px 7px; }
/* Your AI, on the card you're reading. Mirrors .gc-ai so the two feel like one product. */
.ibx-ai {
  border-left: 1px solid var(--border); background: linear-gradient(180deg, var(--bg-2), var(--bg));
  display: flex; flex-direction: column; min-height: 0; padding: 16px 14px 14px;
}
.ibx-ai-head { display: flex; align-items: center; gap: 10px; flex-shrink: 0; padding-bottom: 12px; border-bottom: 1px solid var(--border); }
/* The same animated mark Group Chat uses (.gc-ai-logo drives the orbit + glow), sized
   for this panel. Reused rather than re-drawn so the two never drift apart. */
.ibx-ai-mark { position: relative; width: 42px; height: 42px; display: grid; place-items: center; flex: 0 0 auto; }
.ibx-ai-mark svg { position: relative; width: 34px; height: 34px; color: var(--accent); display: block; }
.ibx-ai-mark.sm { width: 38px; height: 38px; }
.ibx-ai-mark.sm svg { width: 30px; height: 30px; }
.ibx-ai-empty .ibx-ai-mark { width: 132px; height: 132px; }
.ibx-ai-empty .ibx-ai-mark svg { width: 96px; height: 96px; }
.ibx-ai-logo svg { width: 38px; height: 38px; color: var(--accent); display: block; }
.ibx-ai-logo.sm svg { width: 30px; height: 30px; }
/* The panel is a conversation: each step answers as it finishes, and nothing appears
   before the turn that produced it. */
.ibx-ai-body { display: flex; flex-direction: column; gap: 12px; }
.ibx-turn { animation: ibx-turn-in .2s ease-out; }
.ibx-turn.ai { background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 4px 14px 14px 14px; padding: 12px 14px; }
.ibx-turn.ai.opts { background: none; border: none; padding: 0; }
/* Your pick reads as something you said, not as a button that silently changed the
   panel — so it sits on the other side, the way a reply does. */
.ibx-turn.me { align-self: flex-end; max-width: 85%; background: var(--accent);
  color: var(--on-accent, #fff); border-radius: 14px 14px 4px 14px; padding: 8px 13px;
  font-size: 12.5px; line-height: 1.45; }
.ibx-turn.draft .inbox-payload { margin: 8px 0 0; }
.ibx-ai-draft-h.plain { font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .09em; color: var(--text-faint); }
.ibx-opts-lead { font-size: 12.5px; color: var(--text-dim); margin-bottom: 8px; }
/* "Mark as done" sits apart from the choices: it is not one of them, it is the way out. */
.ibx-opts-quiet { margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--border); }
.ibx-opt.quiet { background: none; border-color: transparent; color: var(--text-faint); font-size: 12.5px; }
.ibx-opt.quiet:hover { background: var(--bg-hover); border-color: var(--border); color: var(--text-dim); }
/* The live workbench inside an answer: every tool it calls, as it calls it. "Looking
   into it…" for eight seconds says nothing about whether it is reading Odoo, waiting on
   Teams, or stuck. */
.ibx-work { display: flex; flex-direction: column; gap: 1px; }
.ibx-work .ibx-step { padding: 3px 0; }
.ibx-work .wb-slabel { font-size: 12px; }
/* Once the answer is there the work is history: kept, folded small. */
.ibx-work-done { opacity: .5; margin-bottom: 6px; }
.ibx-work-done .wb-slabel { font-size: 11px; }
.ibx-answer { font-size: 13.5px; color: var(--text); line-height: 1.6; overflow-wrap: anywhere; }
.ibx-answer:empty { display: none; }
.ibx-answer p { margin: 0 0 7px; } .ibx-answer p:last-child { margin-bottom: 0; }
.ibx-answer ul, .ibx-answer ol { margin: 5px 0 8px; padding-left: 20px; }
.ibx-answer a { color: var(--accent-2); }
.ibx-answer table { font-size: 12px; display: block; overflow-x: auto; max-width: 100%; }
/* The gate between choosing and sending. It sits under the message it would send, so
   the words and the button that releases them are in one place. */
.ibx-gate { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--border); }
.ibx-gate-to { font-size: 11.5px; color: var(--accent-2); margin-bottom: 8px; overflow-wrap: anywhere; }
.ibx-gate-row { display: flex; flex-direction: column; gap: 6px; }
.ibx-gate-note { font-size: 11px; color: var(--text-faint); margin-top: 7px; line-height: 1.5; }
@keyframes ibx-turn-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.ibx-ai-name { font-size: 13.5px; font-weight: 700; color: var(--text); }
.ibx-ai-sub { font-size: 11.5px; color: var(--text-faint); }
.ibx-ai-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 14px 2px 6px; }
.ibx-ai-quiet { color: var(--text-faint); font-size: 12.5px; }
.ibx-ai-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; height: 100%; text-align: center; }
.ibx-ai-emptytx { font-size: 13px; color: var(--text-faint); max-width: 270px; line-height: 1.65; }
.ibx-ai-foot { flex-shrink: 0; border-top: 1px solid var(--border); padding-top: 12px; }
/* The decision block. It sits under the AI's read of the card, because the order you
   want is "here's what I think, here's what you can do about it" — and in a 340px
   column the verbs have to stack, so each one gets a full line and can say what it
   does instead of being clipped to a word. */
.ibx-ai-act { flex-shrink: 0; border-top: 1px solid var(--border); padding-top: 12px; margin-top: 10px;
  max-height: 58%; overflow-y: auto; }
/* While the steps run there is nothing to act on yet. A wall of buttons under a
   "working it out…" list invites the click it is telling you to wait for. */
.ibx-ai-act.running { display: none; }
.ibx-ai-ask { font-size: 12.5px; color: var(--text); line-height: 1.5; }
.ibx-ai-askdet { font-size: 11.5px; color: var(--accent-2); margin-top: 3px; overflow-wrap: anywhere; }
/* What the AI thinks you might want to do, in its own words. Full-width rows rather
   than a wrapped row of chips: these are sentences, not verbs, and a sentence clipped
   to fit is worse than no suggestion. */
.ibx-ai-opts { display: flex; flex-direction: column; gap: 7px; }
.ibx-ai-opts:empty { display: none; }
.ibx-opts-wait { font-size: 12px; color: var(--text-faint); font-style: italic; padding: 4px 0; }
/* Nothing runs until it is asked for. Opening a card fired two model calls whether or
   not anyone wanted an opinion — on a 112-card inbox that made simply LOOKING cost
   money. */
.ibx-ai-idle { display: flex; flex-direction: column; align-items: flex-start; gap: 11px; padding: 6px 0 2px; }
.ibx-ai-idletx { font-size: 12.5px; color: var(--text-faint); }
/* The hero the panel rests in until it is asked to do something. */
.ibx-hero-tell { display: flex; gap: 6px; align-items: center; width: 100%; max-width: 320px; margin-top: 4px; }
.ibx-analyse {
  font: inherit; font-size: 13.5px; font-weight: 600; padding: 10px 18px; border-radius: 10px;
  border: 1px solid var(--accent); background: var(--accent); color: var(--on-accent, #fff);
  cursor: pointer;
}
.ibx-analyse:hover { filter: brightness(1.08); }
/* Reuses the chat workbench's step rows so "what the AI is doing" looks the same
   wherever you meet it. Each step is a call actually in flight — a real description,
   not a spinner cut into three. */
/* One step per line, each sitting directly above the reply it produced. A finished one
   is history — kept, quietly — while the one still running keeps its pulse. */
.ibx-step { padding: 2px 0; border-left: none; }
.ibx-step .wb-slabel { font-size: 12.5px; }
.ibx-step.done { opacity: .5; }
.ibx-step.done .wb-slabel { font-size: 11.5px; }
.ibx-ai-body > .ibx-step + .ibx-turn { margin-top: 2px; }
.ibx-ai-rec { margin-top: 4px; }
.ibx-opt {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  font: inherit; font-size: 13px; padding: 10px 13px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg-2); color: var(--text);
}
.ibx-opt:hover { border-color: var(--accent); background: var(--bg-hover); }
.ibx-opt-ico { flex: 0 0 auto; width: 18px; text-align: center; color: var(--text-faint); font-size: 12px; }
.ibx-opt-label { flex: 1; min-width: 0; line-height: 1.4; }
/* The first one is what it would do; the rest are alternatives. */
.ibx-opt.lead { border-color: var(--accent); background: var(--accent-soft); font-weight: 600; }
.ibx-opt.go .ibx-opt-ico { color: var(--ok, #4caf50); }
.ibx-opt.no .ibx-opt-ico { color: var(--danger); }
.ibx-opt.ai .ibx-opt-ico { color: var(--accent-2); }
.ibx-ai-verbs { margin-top: 10px; }
.ibx-ai-verbs .inbox-actions { flex-direction: column; align-items: stretch; gap: 6px; }
.ibx-ai-verbs .inbox-actions button { width: 100%; text-align: center; }
.ibx-ai-verbs .btn-primary { padding: 9px 14px; font-size: 13px; }
.ibx-ai-verbs .inbox-answer { display: flex; flex-direction: column; gap: 7px; }
.ibx-ai-verbs .inbox-answer-input { width: 100%; min-height: 68px; }
.ibx-ai-verbs .inbox-dest { font-size: 11.5px; }
.ibx-ai-or { font-size: 11px; color: var(--text-faint); text-align: center; margin: 11px 0 7px;
  display: flex; align-items: center; gap: 8px; }
.ibx-ai-or::before, .ibx-ai-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.ibx-ai-tell { display: flex; gap: 6px; align-items: center; margin-top: 11px; }
.ibx-ai-tellin { flex: 1; min-width: 0; background: var(--bg); border: 1px solid var(--border);
  border-radius: 999px; padding: 8px 13px; color: var(--text); font: inherit; font-size: 12.5px; outline: none; }
.ibx-ai-tellin:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.ibx-ai-tellgo { flex: 0 0 auto; width: 32px; height: 32px; border: none; border-radius: 50%;
  background: var(--accent); color: var(--on-accent, #fff); cursor: pointer; font-size: 14px; }
.ibx-ai-tellgo:hover { filter: brightness(1.08); }
.ibx-ai-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
/* The source line has no heading of its own — it reads as a byline under the title. */
.ibx-slot-src { margin-top: -4px; }
.ibx-chip {
  font: inherit; font-size: 12px; padding: 7px 12px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--accent); background: var(--bg-2); color: var(--accent-2);
}
.ibx-chip:hover { background: var(--accent-soft); color: var(--text); }
.ibx-chip-alt { border-color: var(--border); color: var(--text-dim); }
.ibx-chip-alt:hover { border-color: var(--accent); color: var(--text); }
.ibx-ai-hint { font-size: 11px; color: var(--text-faint); line-height: 1.55; margin-top: 9px; }
/* Narrow screens give ground in the order the columns can be spared: the list and the AI
   panel tighten first (a 1440 laptop keeps all four — the AI panel is the point of the
   redesign, so it is the last thing to go), then the AI panel moves, then the rail
   collapses to icons rather than disappearing. The RAIL holds `--rail-w` throughout: it
   is standing in for the app sidebar, and a sidebar that is a different width on one
   view reads as the app having shifted under you. */
@media (max-width: 1560px) { .ibx-hub { grid-template-columns: var(--rail-w) 260px minmax(0, 1fr) 440px; }
  .ibx-read { padding: 18px 20px 36px; } }
@media (max-width: 1320px) { .ibx-hub { grid-template-columns: var(--rail-w) 236px minmax(0, 1fr) 380px; } }
/* Narrow: the panel MOVES, it does not vanish. Every verb lives in it now, so hiding it
   left a window where you could read cards and do nothing about them. It becomes a
   drawer under the reading pane instead, and all four columns stay reachable. */
@media (max-width: 1120px) {
  .ibx-hub {
    grid-template-columns: var(--rail-w) minmax(0, 290px) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto;
    grid-template-areas: "rail list read" "rail list ai";
  }
  .ibx-rail { grid-area: rail; }
  .ibx-listcol { grid-area: list; }
  .ibx-read { grid-area: read; }
  .ibx-ai { grid-area: ai; display: flex; max-height: 46vh;
    border-left: 1px solid var(--border); border-top: 1px solid var(--border); }
  .ibx-ai-empty { padding: 14px 0; }
  .ibx-ai-empty .ibx-ai-mark { width: 64px; height: 64px; }
  .ibx-ai-empty .ibx-ai-mark svg { width: 46px; height: 46px; }
}
/* 920px is where the APP sidebar itself goes off-canvas, so that is where the rail may
   finally give up its width too — one breakpoint, not two 20px apart. */
@media (max-width: 920px) { .ibx-hub { grid-template-columns: 58px minmax(0, 240px) minmax(0, 1fr); }
  .ibx-rail-title, .ibx-fold-label { display: none; }
  .ibx-rail-head { justify-content: center; padding: 12px 4px 8px; }
  .ibx-fold { justify-content: center; padding: 9px 4px; position: relative; }
  .ibx-fold-n { position: absolute; top: 2px; right: 4px; min-width: 0; padding: 0 4px; font-size: 9.5px; }
  .ibx-fold-head { margin-top: 8px; } }
.inbox-list { display: flex; flex-direction: column; gap: 2px; max-width: none; flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 8px; }
.inbox-row { display: flex; align-items: flex-start; gap: 10px; width: 100%; text-align: left; background: none; border: none; border-left: 3px solid transparent; border-radius: 10px; padding: 10px 12px; cursor: pointer; color: var(--text); }
.inbox-row:hover { background: var(--bg-hover); }
.inbox-row.active { background: var(--accent-soft); border-left-color: var(--accent); }
.inbox-row.read { opacity: .68; }
/* Unread shouts a little; opened stops shouting but stays full strength, because it
   still needs deciding. Only a HANDLED row dims (.read above). */
.inbox-row:not(.seen) .inbox-row-title { font-weight: 700; }
.inbox-row.seen .inbox-row-title { font-weight: 500; }
/* Multi-select: a tick box per row, and a bar that appears only once something is
   ticked. The box is a span inside the row button (a real <input> inside a <button> is
   invalid), so it carries role="checkbox" and is intercepted before the row's own
   open-this-item handler. */
.inbox-pick {
  width: 15px; height: 15px; border-radius: 4px; flex-shrink: 0; margin-top: 2px;
  border: 1.5px solid var(--border-strong, var(--border)); background: var(--bg);
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; line-height: 1; color: var(--on-accent, #fff);
}
.inbox-row:hover .inbox-pick { border-color: var(--accent); }
.inbox-pick.on { background: var(--accent); border-color: var(--accent); }
.inbox-row.picked { background: var(--accent-soft); }
/* Sticky so the verbs stay reachable while you scroll a long list ticking things. */
.inbox-selbar {
  position: sticky; top: 0; z-index: 3; display: flex; align-items: center;
  gap: 8px; flex-wrap: wrap; margin-bottom: 8px; padding: 8px 10px;
  border: 1px solid var(--accent); border-radius: 10px; background: var(--bg-2);
  box-shadow: var(--shadow-1);
}
.isel-count { font-size: 12.5px; font-weight: 600; color: var(--accent-2); margin-right: 2px; }
.isel-btn { display: inline-flex; align-items: center; gap: 6px; }
.isel-btn:disabled { opacity: .45; cursor: default; }
.isel-n {
  font-size: 11px; font-weight: 700; padding: 0 5px; border-radius: 6px;
  background: rgba(127, 127, 127, .22);
}
.isel-clear { margin-left: auto; }
/* A row's icon says which folder it is, so it carries the folder's colour rather than
   an emoji's. Muted until the row matters — a pending decision earns the accent. */
.inbox-row-ico { flex-shrink: 0; margin-top: 2px; display: flex; color: var(--text-faint); }
.inbox-row-ico .ibx-ico { width: 16px; height: 16px; }
.inbox-row:not(.read) .inbox-row-ico.ico-approval { color: var(--ok, #4caf50); }
.inbox-row:not(.read) .inbox-row-ico.ico-question { color: #e0a640; }
.inbox-row:not(.read) .inbox-row-ico.ico-escalation { color: var(--danger); }
.inbox-row:not(.read) .inbox-row-ico.ico-alert { color: var(--warn, #e0a800); }
.inbox-row:not(.read) .inbox-row-ico.ico-report { color: var(--text-dim); }
.inbox-kind .ibx-ico { width: 14px; height: 14px; }
.inbox-kind { display: inline-flex; align-items: center; gap: 6px; }
.inbox-kind.ico-approval { color: var(--ok, #4caf50); }
.inbox-kind.ico-question { color: #e0a640; }
.inbox-kind.ico-escalation { color: var(--danger); }
.inbox-kind.ico-alert { color: var(--warn, #e0a800); }
.inbox-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.inbox-row-top { display: flex; align-items: baseline; gap: 8px; }
.inbox-row-title { font-weight: 600; font-size: 13.5px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inbox-row-when { font-size: 11px; color: var(--text-faint); flex-shrink: 0; }
.inbox-row-sub { font-size: 12px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inbox-row-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex-shrink: 0; margin-top: 5px; }
.inbox-reading-empty { color: var(--text-faint); font-size: 14px; text-align: center; padding: 70px 20px; }
.idet-totask { margin-left: auto; white-space: nowrap; }
/* The orders a card names, SHOWN and linked. Sits under the source line, because an
   order number IS where the card came from. Empty until the lookup answers, so nothing
   is drawn for a reference that resolved to no record. */
.ibx-refs { display: flex; flex-direction: column; gap: 8px; margin: -2px 0 12px; }
.ibx-ord { border: 1px solid var(--border); border-radius: 11px; background: var(--bg-2);
  padding: 10px 12px; }
.ibx-ord-h { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ibx-ord-kind { font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--text-faint); }
.ibx-ord-ref { font-weight: 650; font-size: 13.5px; font-variant-numeric: tabular-nums; }
.ibx-ord-state { font-size: 11px; color: var(--text-dim); border: 1px solid var(--border);
  border-radius: 999px; padding: 1px 8px; }
.ibx-ord-h .ibx-ref { margin-left: auto; }
/* Two columns, label then value: a five-line order reads down the page rather than
   wrapping into a paragraph you have to parse. */
.ibx-ord-grid { display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 3px 14px; margin-top: 8px; font-size: 12.5px; }
.ibx-ord-k { color: var(--text-faint); font-size: 11.5px; white-space: nowrap; }
.ibx-ord-v { color: var(--text); overflow-wrap: anywhere; }
.ibx-ord-linked { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--border); }
.ibx-ord-chip { display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
  font-size: 11.5px; color: var(--text-dim); border: 1px solid var(--border);
  border-radius: 999px; padding: 2px 9px; font-variant-numeric: tabular-nums; }
.ibx-ord-chip:hover { border-color: var(--accent); color: var(--text); }
.ibx-ord-chip-s { color: var(--text-faint); font-size: 10.5px; }
.ibx-refs:empty { display: none; }
.ibx-ref { display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
  white-space: nowrap; font-variant-numeric: tabular-nums; }
.ibx-ref:hover { border-color: var(--accent); color: var(--text); }
.ibx-ref-ico { width: 14px; height: 14px; color: var(--text-faint); }
.ibx-ref:hover .ibx-ref-ico { color: var(--accent); }
.ibx-ref-n { max-width: 240px; overflow: hidden; text-overflow: ellipsis; }
/* Housekeeping on the card: open the task, close it, get rid of the card. Quiet, because
   they are always there and none of them is the decision you came to make. */
.ibx-tools { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }
.ibx-tool { white-space: nowrap; text-decoration: none; }
.ibx-tool-quiet { opacity: .7; }
.ibx-tool-quiet:hover { opacity: 1; }
/* A message the AI wrote because nothing captured one — flagged as such, because a
   drafted message and a captured one are not the same promise. */
.ibx-draft { font-size: 13.5px; color: var(--text); line-height: 1.6; padding: 10px 13px;
  border-left: 3px solid var(--accent); border-radius: 0 10px 10px 0;
  background: var(--bg); margin-top: 8px; overflow-wrap: anywhere; }
.ibx-draft p { margin: 0 0 7px; } .ibx-draft p:last-child { margin-bottom: 0; }
.ibx-draft-note { font-size: 11px; color: var(--text-faint); margin-top: 7px; }

/* Memory review, one question at a time. The design job here is to stop 167 decisions
   looking like a job: answered rows collapse to a single line, so the list visibly
   shrinks as you work down it, and the two answers are the only things you can click. */
.brv { display: flex; flex-direction: column; gap: 10px; }
.brv-head { display: flex; align-items: center; gap: 10px; }
.brv-prog { font-size: 12.5px; font-weight: 600; color: var(--text-dim); white-space: nowrap; }
.brv-bar { flex: 1; height: 4px; border-radius: 999px; background: var(--bg-3); overflow: hidden; }
.brv-bar-fill { display: block; height: 100%; background: var(--accent); transition: width .25s ease; }
.brv-done { display: flex; flex-direction: column; gap: 2px; }
.brv-donerow {
  display: flex; align-items: center; gap: 8px; padding: 5px 9px; border-radius: 8px;
  background: var(--bg-2); font-size: 12px; color: var(--text-faint);
}
.brv-done-ico { flex-shrink: 0; font-weight: 700; }
.brv-donerow.yes .brv-done-ico { color: var(--ok, #4caf50); }
.brv-done-tx { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brv-done-note { flex-shrink: 0; font-size: 11px; }
.brv-undo {
  flex-shrink: 0; border: none; background: none; font: inherit; font-size: 11.5px;
  color: var(--accent-2); cursor: pointer; text-decoration: underline; padding: 0;
}
.brv-rows { display: flex; flex-direction: column; gap: 10px; }
/* The whole row is the target — it is a <label>, so the sentence you are reading is
   what you click, not a 15px box at the end of it. */
.brv-row {
  border: 1px solid var(--border); border-radius: 12px; background: var(--bg-2);
  padding: 13px 15px; display: flex; align-items: flex-start; gap: 14px; cursor: pointer;
}
.brv-row:hover { border-color: var(--border-strong, var(--text-faint)); }
.brv-row.on { border-color: var(--accent); background: var(--accent-soft); }
.brv-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.brv-pick { flex: 0 0 auto; display: flex; align-items: center; padding-top: 1px; }
.brv-pick input { width: 17px; height: 17px; accent-color: var(--accent); cursor: pointer; }
.brv-row.danger { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }
.brv-row.danger.on { background: color-mix(in srgb, var(--danger) 12%, transparent); }
.brv-row.working { opacity: .5; pointer-events: none; }
/* The two verbs, once, under the rows — sticky so they stay reachable while you work
   down a long batch instead of living at the bottom of a scroll. */
.brv-bar {
  position: sticky; bottom: 0; z-index: 2; display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; margin-top: 4px; padding: 10px 13px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--bg-2);
  box-shadow: var(--shadow-1);
}
.brv-all { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px;
  color: var(--text-dim); cursor: pointer; user-select: none; }
.brv-all input { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }
.brv-bar-n { font-size: 12px; color: var(--text-faint); margin-left: auto; }
.brv-bar .brv-yes, .brv-bar .brv-no { padding: 7px 16px; }
.brv-bar button:disabled { opacity: .45; cursor: default; }
.brv-q { font-size: 14px; font-weight: 600; color: var(--text); line-height: 1.45; }
.brv-texts { display: flex; flex-direction: column; gap: 5px; }
.brv-side { display: flex; align-items: baseline; gap: 8px; }
.brv-tag {
  flex-shrink: 0; font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .07em; color: var(--text-faint); background: var(--bg-3);
  border-radius: 5px; padding: 1px 6px;
}
.brv-tag.new { color: var(--accent-2); background: var(--accent-soft); }
.brv-tx { font-size: 13px; color: var(--text-dim); line-height: 1.5; overflow-wrap: anywhere; }
/* On a one-question card the two answers sit side by side, the same size: neither is
   the "safe" one to click through, because both directions are a real choice. */
.brv-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 2px; }
.brv-yes, .brv-no {
  font: inherit; font-size: 13px; font-weight: 600; padding: 7px 15px; border-radius: 9px;
  cursor: pointer; border: 1px solid transparent;
}
.brv-yes { background: var(--accent); color: var(--on-accent, #fff); }
.brv-yes:hover { filter: brightness(1.08); }
.brv-yes.danger { background: var(--danger); }
.brv-no { background: var(--bg); border-color: var(--border); color: var(--text-dim); }
.brv-no:hover { border-color: var(--text-dim); color: var(--text); }
.brv-hint { font-size: 11.5px; color: var(--text-faint); }
.brv-hint.yes { color: var(--accent-2); }
.brv-foot { flex-direction: column; align-items: flex-start; gap: 6px; }
.brv-foot-hint, .brv-allrone { font-size: 11.5px; color: var(--text-faint); }
/* One question, not a review: no progress bar, no done-list, no footer. The thing
   itself is the biggest text on the card and the two answers sit right under it. */
.brv-one { gap: 12px; }
.brv-one-q { font-size: 15px; font-weight: 600; color: var(--text); }
.brv-one-thing {
  font-size: 15px; line-height: 1.55; color: var(--text); padding: 12px 15px;
  border-left: 3px solid var(--accent); border-radius: 0 10px 10px 0;
  background: var(--accent-soft); overflow-wrap: anywhere;
}
.brv-why { font-size: 12.5px; color: var(--text-faint); }
.brv-why > summary { cursor: pointer; padding: 4px 0; list-style: none; }
.brv-why > summary::-webkit-details-marker { display: none; }
.brv-why > summary::before { content: "▸ "; display: inline-block; transition: transform .15s; }
.brv-why[open] > summary::before { content: "▾ "; }
.brv-why > summary:hover { color: var(--text-dim); }
.brv-why .inbox-body { margin-top: 6px; }

/* The conversation a card came from, drawn AS a conversation. You are judging whether
   a drafted reply still makes sense, and who said what last is most of that judgement —
   so it gets avatars, bubbles and reading order rather than a stack of indented quotes. */
.ibx-thread { margin-top: 2px; }
.thr-loading, .thr-err { font-size: 12px; color: var(--text-faint); padding: 6px 0; }
.thr { border: 1px solid var(--border); border-radius: 12px; background: var(--bg-2); overflow: hidden; }
.thr-head {
  padding: 9px 14px; font-size: 12px; font-weight: 600; color: var(--text-dim);
  display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--border);
  background: var(--bg-3);
}
.thr-head-main { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thr-n { font-size: 11px; font-weight: 500; color: var(--text-faint); white-space: nowrap; }
/* The opening message is a POST: full width, no bubble, room to breathe, and its
   pictures at a size you can actually look at. What follows is chat. Treating both the
   same made "next Monday cannot" as prominent as the request itself. */
.thr-post { padding: 14px 16px 16px; }
.thr-post-head { display: flex; align-items: center; gap: 11px; margin-bottom: 10px; }
.thr-post-who { flex: 1; min-width: 0; }
.thr-post-name { font-size: 14.5px; font-weight: 700; color: var(--text); }
.thr-post-when { font-size: 11.5px; color: var(--text-faint); margin-top: 1px; }
.thr-post-tx { font-size: 14px; color: var(--text); line-height: 1.65; overflow-wrap: anywhere; }
.thr-post-tx p { margin: 0 0 9px; } .thr-post-tx p:last-child { margin-bottom: 0; }
.thr-post-tx ul, .thr-post-tx ol { margin: 6px 0 9px; padding-left: 20px; }
.thr-post-tx a { color: var(--accent-2); }
.thr-post-tx table { font-size: 12.5px; display: block; overflow-x: auto; max-width: 100%; }
.thr-replies { border-top: 1px solid var(--border); background: var(--bg); padding: 10px 14px 14px; }
.thr-replies-h { font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; color: var(--text-faint); margin-bottom: 9px; }
.thr-msgs { padding: 12px 14px; display: flex; flex-direction: column; gap: 12px; }
.thr-msg { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 9px; }
.thr-msg:last-child { margin-bottom: 0; }
.thr-ava {
  flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%; color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 700; letter-spacing: .02em; margin-top: 2px;
}
.thr-ava.lg { width: 40px; height: 40px; font-size: 14px; margin-top: 0; }
.thr-bubble {
  flex: 1; min-width: 0; background: var(--bg); border: 1px solid var(--border);
  border-radius: 4px 12px 12px 12px; padding: 9px 13px;
}
.thr-who { font-size: 12.5px; font-weight: 600; color: var(--text); display: flex;
  align-items: baseline; flex-wrap: wrap; gap: 6px; }
.thr-when { font-weight: 400; color: var(--text-faint); font-size: 11px; }
.thr-badge { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--accent-2); background: var(--accent-soft); border-radius: 5px; padding: 1px 6px; }
.thr-tx { font-size: 13.5px; color: var(--text); line-height: 1.6; margin-top: 4px; overflow-wrap: anywhere; }
.thr-tx p { margin: 0 0 7px; } .thr-tx p:last-child { margin-bottom: 0; }
.thr-tx ul, .thr-tx ol { margin: 4px 0 7px; padding-left: 20px; }
.thr-tx a { color: var(--accent-2); }
.thr-tx table { font-size: 12px; display: block; overflow-x: auto; max-width: 100%; }
/* Pictures, actually shown. Capped so a screenshot of a spreadsheet doesn't take over
   the pane; click opens it full size. */
.thr-imgs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.thr-img { display: block; line-height: 0; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--border); max-width: 100%; }
.thr-img img { display: block; max-width: 200px; max-height: 170px; width: auto; height: auto;
  object-fit: cover; background: var(--bg-3); }
/* On a post the pictures are usually the point — a photo of the parts, a screenshot of
   the order — so they get roughly twice the area they do on a reply. */
.thr-imgs.big { margin-top: 12px; gap: 10px; }
.thr-imgs.big img { max-width: 300px; max-height: 260px; }
.thr-img:hover { border-color: var(--accent); }
.thr-files { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 8px; }
.thr-file { font-size: 11.5px; color: var(--accent-2); text-decoration: none; }
.thr-file:hover { text-decoration: underline; }
.thr-edited { font-size: 10.5px; color: var(--text-faint); font-weight: 400; margin-left: 6px; }
.thr-open { font-size: 11px; color: var(--accent-2); text-decoration: none; margin-left: 8px; font-weight: 400; }
.thr-open:hover { text-decoration: underline; }
.thr-clip, .thr-more { font-size: 11.5px; color: var(--warn, #e0a800); margin-top: 4px; }
.thr-retry { margin-left: 8px; font: inherit; font-size: 11.5px; border: none; background: none;
  color: var(--accent-2); text-decoration: underline; cursor: pointer; padding: 0; }
.thr-more { padding: 0 12px 10px; }
/* A long thread scrolls inside its own panel rather than pushing everything below it
   out of reach. It now sits BELOW the draft, so the decision buttons are above it and
   stay reachable either way. */
.thr-msgs { max-height: 52vh; overflow-y: auto; }
/* Two explicit columns rather than auto-fit: the work card carries most of the
   content and the ambient one should sit BESIDE it, not wrap under it leaving a
   third of the page blank. Collapses to one column below 900px (rule further down). */
/* One column. The 2fr/1fr split is what left Home ragged: it decided a card's WIDTH
   by the order it happened to load in, so Daily News rendered at 2fr and Weekly Sales
   at 1fr — the same kind of card, two different widths, with a hole beside each.
   Sections stack full-width; the digest cards get their own equal-width row below. */
.home-grid { display: grid; gap: 16px;
  /* 420, not 330 — two columns, not three. A row cannot end until its tallest card
     does, so every extra column is another chance for a short card to sit beside a
     tall one. Two pairs them off: one seam to reconcile per row instead of two. */
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); }
.home-card {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px;
  padding: 16px 16px 12px; display: flex; flex-direction: column; gap: 10px;
  box-shadow: var(--shadow-1); min-height: 180px;
}
.home-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
/* A card heading was 13px dim uppercase while its own content was 14px near-white —
   the label was quieter than the thing it labelled, so the eye had no anchor. Real
   size, real weight, real colour; the row beneath can now be the quiet one. */
.home-card-head h2 { margin: 0; font-size: 16px; font-weight: 650; letter-spacing: -.01em; color: var(--text);
  display: inline-flex; align-items: center; gap: 9px; min-width: 0; }
/* A skill card's tile. Its header row centres rather than sitting on the baseline
   .home-fold uses everywhere else — a 26px tile on a baseline sits a third of its
   height too low. Scoped to these cards, so no other fold header shifts. */
.home-card-ico { --ico-sz: 26px; }
.home-card-tools { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.home-card-link { background: none; border: none; color: var(--accent-2); font-size: 12.5px; font-weight: 600; padding: 2px 4px; border-radius: 6px; }
.home-card-link:hover { background: var(--accent-soft); }
.home-card-body { display: flex; flex-direction: column; gap: 6px; }

/* ===== Folding =============================================================
   Every card collapses to ONE line carrying its count and a one-line summary, so a
   folded Home still answers "is there anything for me?" in a single screen. Folded is
   the default: the list is what makes the page long, and it is one click away. */
.home-fold { display: flex; align-items: baseline; gap: 8px; min-width: 0; flex: 1;
  background: none; border: none; padding: 0; cursor: pointer; text-align: left; color: inherit; }
.home-fold-caret { flex-shrink: 0; width: 10px; font-size: 12px; line-height: 1; color: var(--text-faint);
  transition: transform .15s ease; }
.home-fold:hover .home-fold-caret, .home-act-lede:hover .home-fold-caret { color: var(--accent-2); }
/* Rotated when OPEN — i.e. when the card is NOT folded. */
.home-card:not(.folded) .home-fold-caret,
.home-act:not(.folded) .home-fold-caret { transform: rotate(90deg); }
.home-fold .home-card-count { flex-shrink: 0; }
.home-fold h2 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.home-card-sum { font-size: 12.5px; color: var(--text-faint); font-weight: 500; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
/* ⚠️ The TITLE is not what shrinks. Both halves were flexible with `min-width: 0`, so a
   long summary ate the heading it was describing — "Running on a schedule" rendered as
   "Running on a sche…" beside "3 active · 1 f…", which is two truncations where one
   would have done. The summary is the disposable half: it repeats what the body already
   says, while the title is the only thing that names the card. */
.home-card-head h2 { flex: 0 0 auto; white-space: nowrap; }
.home-card-sum { flex: 0 1 auto; min-width: 0; }
/* The fold itself. min-height goes too, so a folded card is exactly its header — that
   is what makes the collapsed stack read as an even, tidy list. */
.home-card.folded > .home-card-body,
.home-card.folded > .home-cardlist,
.home-act.folded > .home-act-body { display: none; }
.home-card.folded { min-height: 0; gap: 0; padding-bottom: 14px; }
.home-act.folded { padding-bottom: 16px; }
.home-act.folded .home-act-n { font-size: 30px; }

/* ===== TIER 1 · act now ====================================================
   Raised, accent-edged, and the only surface with buttons that act in place. */
.home-act {
  background: var(--bg-2); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 14px; padding: 16px 18px 12px; margin-bottom: 16px;
  box-shadow: var(--shadow-2); display: flex; flex-direction: column; gap: 10px;
}
.home-act.clear { border-left-color: var(--ok, #1f9d55); box-shadow: var(--shadow-1); }
/* Reports sit OUTSIDE the count, so they must not look like part of the breakdown. */
.home-act-fyi { margin: 0; font-size: 12.5px; color: var(--text-faint); padding-left: 2px; }
.home-act-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.home-act-lede { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1;
  background: none; border: none; padding: 0; cursor: pointer; text-align: left; color: inherit; }
.home-act-n { font-size: 40px; font-weight: 700; line-height: 1; color: var(--accent);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
.home-act.clear .home-act-n { color: var(--ok, #1f9d55); }
.home-act-what { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.home-act-title { font-size: 17px; font-weight: 650; letter-spacing: -.01em; }
.home-act-sub { font-size: 13px; color: var(--text-dim); }
.home-act-body { display: flex; flex-direction: column; gap: 4px; }
/* Two lines, no ellipsis: the end of "Approve 7/11 — sale.order #10859: Deliver 8…"
   is the part that tells you which one it is. */
.home-arow { display: flex; align-items: center; gap: 8px; border-radius: 10px; }
.home-arow:hover { background: var(--bg-hover); }
.home-arow-open { flex: 1; min-width: 0; display: flex; align-items: flex-start; gap: 10px;
  background: none; border: none; text-align: left; color: var(--text); padding: 9px 10px; cursor: pointer; }
.home-arow .home-row-title { white-space: normal; overflow: visible;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.home-arow.stale .home-row-sub { color: var(--warn, #e0a800); font-weight: 600; }
.home-act-btn { flex-shrink: 0; margin-right: 8px; background: var(--accent); color: var(--on-accent);
  border: none; border-radius: 9px; padding: 7px 13px; font-size: 12.5px; font-weight: 600; cursor: pointer; }
.home-act-btn:hover { filter: brightness(1.08); }

/* Header chips replace the old tile row: a "0" tile took a fifth of the width to say
   nothing, so a count of zero now simply isn't drawn. */
.home-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-right: 6px; }
.home-chip { background: var(--bg-2); border: 1px solid var(--border); border-radius: 999px;
  padding: 5px 11px; font-size: 12.5px; color: var(--text-dim); cursor: pointer; }
.home-chip:hover { border-color: var(--accent); color: var(--text); }
.home-chip-n { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }
.home-chip-n.ready { color: var(--accent-2); }
.home-chip-n.blocked { color: var(--warn, #e0a800); }


.home-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: none; border: none; border-radius: 9px; padding: 8px 10px; cursor: pointer; color: var(--text);
}
.home-row:hover { background: var(--bg-hover); }
.home-row-ico { display: inline-flex; align-items: center; justify-content: center;
  font-size: 16px; width: 24px; text-align: center; flex-shrink: 0; }
.home-row-ico .ico { width: 16px; height: 16px; color: var(--ic, var(--text-faint)); }
.home-row-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; margin: 0 8px 0 7px; }
.home-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; line-height: 1.45; }
/* Two lines before truncating. At one line these clipped mid-identifier — "…sale.order
   #108" — which is precisely the part that says which row this is. */
.home-row-title { font-size: 13.5px; font-weight: 600; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }
.home-row-sub { font-size: 12px; color: var(--text-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.home-row-go { color: var(--text-faint); font-size: 13px; }
.home-row:hover .home-row-go { color: var(--accent-2); }
.home-empty, .home-loading, .home-more { color: var(--text-faint); font-size: 13px; padding: 10px 6px; line-height: 1.5; }
/* .home-more became a <button> so it can open the Inbox — without this it inherits the
   default button chrome and renders as a grey slab across the card. */
button.home-more { background: none; border: none; width: 100%; text-align: left;
  cursor: pointer; border-radius: 9px; font-weight: 600; }
button.home-more:hover { background: var(--bg-hover); color: var(--accent-2); }
.home-stat { font-size: 12.5px; color: var(--text-dim); }
.home-stat b { font-size: 15px; margin-right: 3px; }
.home-unread {
  background: var(--accent); color: var(--on-accent); border-radius: 999px;
  font-size: 10.5px; font-weight: 700; padding: 1px 7px; margin-left: 6px; vertical-align: 1px;
}
/* Home dashboard card for a skill that declares home_card — previews its latest run. */
.home-skillcard {
  text-align: left; background: none; border: 0; cursor: pointer; color: inherit;
  padding: 4px 2px; width: 100%;
}
.home-skillcard:hover { color: var(--accent); }
.home-skillcard-preview {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  color: var(--text-dim); font-size: 13px; line-height: 1.5;
}
.home-skillcard .home-row-sub { display: block; margin-top: 4px; }

/* ---- light-theme fixes for colours tuned for dark backgrounds ---- */
:root[data-theme="light"] .cap-test.ok { color: #116e43; background: var(--ok-soft); border-color: rgba(23,145,91,.35); }
:root[data-theme="light"] .cap-test.err,
:root[data-theme="light"] .auth-error,
:root[data-theme="light"] .msg-error { color: #b42318; }
:root[data-theme="light"] #toast.err { color: #b42318; }
:root[data-theme="light"] .cap-oauth-status.ok { color: #116e43; }
:root[data-theme="light"] .run-error-title, :root[data-theme="light"] .cap-help.warn { color: #b45309; }
:root[data-theme="light"] .req-chip.req-ok, :root[data-theme="light"] .req-chip.req-ok b { color: #116e43; }
:root[data-theme="light"] .req-chip.req-missing, :root[data-theme="light"] .req-chip.req-missing b { color: #b45309; }
:root[data-theme="light"] .ext-price.free { color: #116e43; }
:root[data-theme="light"] .st-ready { color: #2563eb; }
:root[data-theme="light"] .st-blocked { color: #b45309; }
:root[data-theme="light"] .task-meta .tm-due { color: #b45309; }
:root[data-theme="light"] .reach-switch.on .sw-label { color: #15803d; }
:root[data-theme="light"] .st-done { color: #116e43; }
:root[data-theme="light"] .st-in_progress { color: var(--accent-2); }

/* ---- responsive: off-canvas sidebar under 920px ---- */
/* It lived in the top bar, which is gone, and it cannot move into the sidebar: below
   920px the sidebar is off-canvas, so a button inside it is unreachable at exactly the
   moment it is wanted. So it floats again — but the reason it was put in a bar (it
   overlapped whatever the view drew in its top-left corner) is answered by RESERVING
   the strip on #app-body instead of by the per-view padding hacks that came before.
   It is a solid chip, not a transparent glyph, because it sits over page content. */
.sidebar-toggle {
  /* Above ordinary page content, BELOW every overlay — the settings modal (50), the
     right-hand drawers (60/61), the sidebar backdrop (65) and the drawer itself (70)
     must all cover it, exactly as they covered the bar this used to live in. */
  display: none; position: fixed; top: 9px; left: 10px; z-index: 45;
  background: var(--bg-2); border: 1px solid var(--border); color: var(--text);
  border-radius: 9px; padding: 5px 10px; font-size: 15px; box-shadow: var(--shadow-1);
}
.sidebar-toggle:hover { background: var(--bg-hover); }
#sidebar-backdrop { display: none; }
@media (max-width: 920px) {
  .sidebar-toggle { display: block; }
  /* The one place the shell still reserves a row: 44px for the chip to sit in, so it
     never covers a heading. Not a bar — nothing is drawn in it. */
  #app-body { padding-top: 44px; }
  #sidebar {
    position: fixed; inset: 0 auto 0 0; z-index: 70; transform: translateX(-102%);
    transition: transform .2s ease; box-shadow: var(--shadow-2); width: 280px;
  }
  body.sidebar-open #sidebar { transform: translateX(0); }
  body.sidebar-open #sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 65; background: rgba(0,0,0,.45);
  }
  .home-grid { grid-template-columns: 1fr; }
}
/* ⚠️ The wordmark used to drop below 560px, because the top bar it sat in was as narrow
   as the window. Both frames that carry the brand now have a fixed width — the sidebar
   and its off-canvas drawer are 264/280px, a full-bleed rail no narrower — so there is no
   cramped case left, and hiding it only left a lone dot above a full-width menu. */

/* ---- task priority badges ---- */
.tm.pr-high { color: var(--danger); font-weight: 700; }
.tm.pr-medium { color: var(--warn); font-weight: 600; }
.tm.pr-low { color: var(--text-faint); }
table.tasks-table .tm.pr-high, table.tasks-table .tm.pr-medium, table.tasks-table .tm.pr-low {
  text-transform: capitalize;
}

/* ---- Home: Today's plan card ---- */
/* No longer spans the row — it is the wide COLUMN of the two now, so a full-width
   span would push everything else below it again. */
/* The name is the contract: a wide card spans BOTH columns. It only ever set
   min-height, so "Router output sits full-width right below Today's plan" was never
   true — the plan and all five router cards flowed into the 2fr/1fr columns like
   anything else, which is what left one column short and a hole beside it. */
.home-card-wide { min-height: 0; grid-column: 1 / -1; }

/* ===== Home: the four state cards ==========================================
   Chats / What your AI can do / Running on a schedule / This month. Each is a few
   rows and a summary, so they are styled to be SCANNED rather than read: the number
   carries the meaning and the caption explains it, never the other way round. */

/* A running chat. The dot is the whole signal — a turn outlives the tab, so this is
   the only place a person can see that something is still being written for them.
   ⚠️ The animation is on a pseudo-element ring, not on the glyph: pulsing the icon
   itself moved the row's text baseline a fraction every second, which reads as the
   list twitching rather than as one row being alive. */
.home-row-ico.home-live { position: relative; }
.home-row-ico.home-live::after {
  content: ""; position: absolute; inset: -3px; border-radius: 50%;
  border: 1.5px solid var(--ok); opacity: .55;
  animation: home-live-pulse 1.8s ease-out infinite;
}
@keyframes home-live-pulse {
  0%   { transform: scale(.85); opacity: .55; }
  70%  { transform: scale(1.25); opacity: 0; }
  100% { transform: scale(1.25); opacity: 0; }
}
/* Motion is a preference, not a decoration. The ring still draws — it just stops. */
@media (prefers-reduced-motion: reduce) {
  .home-row-ico.home-live::after { animation: none; opacity: .5; }
}

/* The spend figure. Big number, quiet caption, in that order — the caption is what
   the number is OF, and it is unreadable if it competes. */
.home-usage-figure { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.home-usage-big {
  font-size: 26px; font-weight: 680; letter-spacing: -.02em; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.home-usage-cap { font-size: 13px; color: var(--text-faint); }

/* The bar. A track and a fill, and the fill is the ONLY thing that carries colour —
   a coloured track competes with its own fill and makes a low percentage look high. */
.home-bar {
  height: 8px; border-radius: 999px; background: var(--bg-3, var(--bg-hover));
  overflow: hidden; margin: 2px 0 4px;
}
.home-bar-fill {
  display: block; height: 100%; border-radius: 999px;
  background: var(--accent); transition: width .35s ease;
  min-width: 3px;   /* a real but tiny spend must still be visible as a sliver */
}
/* Three states, and they mean three different things: fine, going to be a problem,
   already a problem. `warn` fires on the PROJECTION as well as the level, because
   "on track to go over" is the one a person can still act on. */
.home-bar-fill.ok { background: var(--accent); }
.home-bar-fill.warn { background: var(--warn); }
.home-bar-fill.over { background: var(--danger); }
.home-usage-foot { font-size: 12.5px; color: var(--text-faint); }
.home-usage-foot.warn { color: var(--warn); }
.home-usage-foot.over { color: var(--danger); font-weight: 600; }

/* The headline chip for decisions. It is the only chip that means "somebody is
   waiting", so it is the only one that carries the accent. */
.home-chip-n.act { color: var(--accent-2); }

/* ===== Home: the spend strip ===============================================
   Full width, above the grid. The same content boxed into a grid cell was a small figure
   over a short bar with most of the cell empty — and a bar is the one thing on this page
   that genuinely wants the width, because its LENGTH is its meaning. */
/* ⚠️ `min-height: 0` — `.home-card` reserves 180px so cards in a grid ROW keep a common
   height, and this one is not in the grid and is a single line. Inherited, it drew a
   half-empty box the height of a real card under one strip of text. */
.home-usage-card { margin-bottom: 16px; min-height: 0; }
.home-usage-strip {
  display: flex; align-items: center; gap: 20px; width: 100%;
  /* Wraps rather than crushing the bar: narrow, the figure and the verdict take a line
     each and the bar keeps its own. A bar squeezed to 60px says nothing at all. */
  flex-wrap: wrap;
}
/* The bar is the elastic one — it takes whatever the two pieces of text leave. */
.home-usage-strip .home-bar { flex: 1 1 220px; min-width: 160px; margin: 0; }
.home-usage-strip .home-usage-figure,
.home-usage-strip .home-usage-foot { flex: 0 0 auto; }
.home-usage-strip .home-usage-set { margin-left: auto; width: auto; }

/* Type tabs on the router card: one bar with five counts, rather than five bars. */
/* row, explicitly: the card itself is a column flex, so an unqualified
   `display:flex` here inherited column and stretched every pill full-width. */
.home-tabs { display: flex; flex-direction: row; align-items: center;
  gap: 6px; flex-wrap: wrap; padding: 2px 0 6px; }
.home-tab { flex: 0 0 auto; }
.home-tab {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font: inherit; font-size: 12.5px; padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--border); background: transparent; color: var(--text-dim);
}
.home-tab:hover { color: var(--text); border-color: var(--text-faint); }
.home-tab.on { background: var(--bg-3); color: var(--text); border-color: var(--accent); font-weight: 600; }
/* The kind's glyph, coloured by kind (ic-* sets --ic) — the tab row is the one place on
   Home where seven things have to be told apart at a glance. */
.home-tab .ico { width: 14px; height: 14px; flex-shrink: 0; color: var(--ic, var(--text-faint)); }
.home-tab-n { font-size: 11px; color: var(--text-faint); }
.home-tab.on .home-tab-n { color: var(--text-dim); }
/* A kind with something unread earns the accent — that is the only reason to look. */
.home-tab-n.unread { color: var(--accent); font-weight: 700; }
.home-card.folded > .home-tabs { display: none; }
/* ---- Company knowledge card ---- */
.cn-knowledge-text { width: 100%; resize: vertical; font-family: var(--font); line-height: 1.55; }
/* Company knowledge is usually long — give it a tall default (still drag-resizable). */
#cn-knowledge-text { min-height: 420px; }
.cn-knowledge-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; }
.cn-knowledge-row .btn-primary { margin-left: auto; }

/* ---- Reference packs (bundled documents, inside the Knowledge base card) ---- */
.cn-refpack { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.cn-refpack-h { font-size: 13px; font-weight: 600; margin-bottom: 4px; }
.cn-refpack-row { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.cn-refpack-row:last-child { border-bottom: 0; }
.cn-refpack-row > div { display: flex; flex-direction: column; min-width: 0; }
.cn-refpack-row strong { font-size: 13.5px; font-weight: 600; }
.cn-refpack-row small { color: var(--text-dim); font-size: 11.5px; margin-top: 2px; }
/* The row's buttons. `.cn-refpack-row > div` stacks its children in a column (that is
   the title/meta block), so the action block has to say it is a row or View and
   Refresh sit on top of each other. */
.cn-refpack-act { flex-direction: row !important; gap: 8px; flex-shrink: 0; }
/* A reference document read 460px at a time is a document nobody reads. Set by
   openReferencePack and cleared by cnCloseModal. */
.cn-modal-overlay.wide .cn-modal { width: 900px; }
.cn-modal-overlay.wide .cn-modal-body textarea { width: 100%; font-size: 13px;
  line-height: 1.55; resize: vertical; }

/* ---- Home → Getting started (hidden once the required steps are done) ---- */
.home-setup { background: var(--bg-2); border: 1px solid rgba(99,102,241,.32);
  border-radius: 14px; padding: 14px 16px; margin-bottom: 16px; }
.home-setup-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.home-setup-head > div { flex: 1; min-width: 0; }
.home-setup-ico { font-size: 20px; line-height: 1; }
.home-setup-title { font-size: 14.5px; font-weight: 600; }
.home-setup-sub { font-size: 12px; color: var(--text-dim); margin-top: 2px; }
.home-setup-step { display: flex; align-items: flex-start; gap: 10px; padding: 7px 0;
  border-top: 1px solid var(--border); }
.home-setup-step:first-child { border-top: 0; }
.home-setup-step > div { display: flex; flex-direction: column; min-width: 0; }
.home-setup-step strong { font-size: 13px; font-weight: 600; }
.home-setup-step em { font-style: normal; color: var(--text-dim); font-weight: 400; }
.home-setup-step small { font-size: 11.5px; color: var(--text-dim); margin-top: 2px;
  line-height: 1.45; }
.home-setup-where { opacity: .75; }
.home-setup-step.done strong { color: var(--text-dim); font-weight: 400; }
.home-setup-mark { flex: none; width: 18px; height: 18px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; margin-top: 1px; }
.home-setup-step.done .home-setup-mark { background: rgba(34,197,94,.16); color: #22c55e; }
.home-setup-step.todo .home-setup-mark { background: rgba(99,102,241,.18); color: #818cf8; }

/* ---- Company → Skills: the author's scope recommendation ---- */
.skl-suggest { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin: 8px 0 0; padding: 7px 9px; border-radius: 8px;
  background: rgba(99,102,241,.10); border: 1px solid rgba(99,102,241,.28); }
.skl-suggest span { font-size: 11.5px; line-height: 1.4; color: var(--text-dim); }
.skl-suggest strong { color: var(--text); font-weight: 600; }
.skl-suggest .btn-mini { flex: none; }

/* ---- Company → HR: the setup checklist ---- */
.cn-hrsetup-row { display: flex; align-items: flex-start; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid var(--border); }
.cn-hrsetup-row:last-child { border-bottom: 0; }
.cn-hrsetup-row > div { display: flex; flex-direction: column; min-width: 0; }
.cn-hrsetup-row strong { font-size: 13.5px; font-weight: 600; }
.cn-hrsetup-row small { color: var(--text-dim); font-size: 11.5px; margin-top: 2px;
  line-height: 1.45; }
.cn-hrsetup-mark { flex: none; width: 18px; height: 18px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; margin-top: 1px; }
.cn-hrsetup-row.ok .cn-hrsetup-mark { background: rgba(34,197,94,.16); color: #22c55e; }
.cn-hrsetup-row.todo .cn-hrsetup-mark { background: rgba(245,158,11,.16); color: #f59e0b; }

/* ---- People dates (the HR roster supplement, Company → HR) ---- */
#cn-hrroster-card { margin-top: 18px; }
.cn-hrroster-row { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.cn-hrroster-row:last-child { border-bottom: 0; }
.cn-hrroster-row > div { display: flex; flex-direction: column; min-width: 0; }
.cn-hrroster-row strong { font-size: 13.5px; font-weight: 600; }
.cn-hrroster-row small { color: var(--text-dim); font-size: 11.5px; margin-top: 2px; }
.cn-hrroster-form { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.cn-hrroster-form .cn-input { flex: 1 1 180px; max-width: 260px; }
.cn-hrroster-f { display: flex; flex-direction: column; gap: 3px; font-size: 11.5px;
  color: var(--text-dim); }
.cn-hrroster-f .cn-input { max-width: 165px; }
.cn-hrroster-form .btn-primary { margin-left: auto; }

/* ---- Company knowledge BASE (documents, searched on demand) ----
   Distinct from the knowledge textarea above it: that is one blob carried in every
   prompt, this is a document list. The "Check what your AI would find" block at the
   bottom is deliberately part of the same card — authoring knowledge you can't test
   is how a company ends up with a full knowledge base and an AI that never cites it. */
.cn-kb-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.cn-kb-actions .cn-input { flex: 1; min-width: 200px; }
.cn-kb-flag { font-size: 12.5px; color: var(--danger, #e03131); }
.cn-kb-editor { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.cn-kb-scope-row { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--text-dim); }
.cn-kb-scope-row .cn-input { flex: 1; }
.cn-kb-badge { display: inline-block; font-size: 11px; padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--text-faint); margin-left: 6px; }
.cn-kb-badge.team { border-color: var(--accent); color: var(--accent); }
.cn-kb-list { display: flex; flex-direction: column; gap: 8px; }
.cn-kb-doc { display: flex; align-items: center; gap: 12px; background: var(--bg);
  border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; }
.cn-kb-doc-main { flex: 1; min-width: 0; }
.cn-kb-doc-title { font-size: 14px; font-weight: 600; }
.cn-kb-doc-meta { font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }
.cn-kb-doc-act { display: flex; gap: 6px; flex-shrink: 0; }
.cn-kb-doc-act button { background: transparent; border: 1px solid var(--border);
  border-radius: 8px; padding: 5px 10px; font-size: 12px; color: var(--text-dim); }
.cn-kb-doc-act button:hover { background: var(--bg-hover); color: var(--text); }
.cn-kb-empty { font-size: 13px; color: var(--text-faint); padding: 14px 2px; }
.cn-kb-try { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); }
.cn-kb-hits { display: flex; flex-direction: column; gap: 8px; }
.cn-kb-hit { background: var(--bg); border: 1px solid var(--border);
  border-left: 3px solid var(--accent); border-radius: 8px; padding: 9px 12px; }
.cn-kb-hit-h { font-size: 12.5px; font-weight: 600; display: flex; justify-content: space-between; gap: 10px; }
.cn-kb-hit-h span { color: var(--text-faint); font-weight: 500; }
.cn-kb-hit-t { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; margin-top: 4px; white-space: pre-wrap; }

/* ---- escalation inbox cards (a report's decision relayed to their manager) ---- */
.inbox-card.inbox-escalation { border-left-color: var(--danger); }

/* ---- workspace company-management (admin) ---- */
.cn-policy-row { margin-bottom: 8px; }
.cn-policy-mode { width: auto; min-width: 220px; }
.cn-member-mgr { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-faint); }
.cn-member-mgr select {
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 7px; padding: 4px 7px; font-size: 12.5px; font-family: inherit; max-width: 170px;
}

/* --- Pin the logo/online (top) + profile (bottom); only the middle scrolls --- */
#sidebar { overflow: hidden; }
.side-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; gap: 12px; }
.side-scroll .side-recents { flex: 0 0 auto; }
.side-scroll .side-recents .conversations { flex: none; overflow: visible; max-height: none; }

/* Tasks toolbar: the automation controls moved to Heartbeat, so what remains is one
   board action plus a pointer to where the recurring version is configured. */
.tasks-auto-link {
  color: var(--text-faint); font-size: 12.5px; text-decoration: none;
  padding: 7px 11px; border-radius: 9px; border: 1px solid transparent;
}
.tasks-auto-link:hover { background: var(--bg-2); color: var(--text); border-color: var(--border); }
.tasks-empty .hint { margin: 6px 0; }
.tasks-empty button { margin: 10px 0 4px; }

/* Task picker — same shell as the command palette, with its own heading. */
#taskpick-overlay { align-items: flex-start; }
.taskpick-head {
  padding: 12px 18px 0; font-size: 12px; font-weight: 600;
  color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em;
}
#taskpick-results .dotmark { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.taskpick-new { margin-left: auto; color: var(--accent-2); }

/* New / edit project dialog */
.pf-icon-input { text-align: center; font-size: 16px; }
.pf-colors { display: flex; gap: 8px; flex-wrap: wrap; padding-top: 4px; }
.pf-swatch {
  width: 26px; height: 26px; border-radius: 50%; border: 2px solid transparent;
  padding: 0; cursor: pointer;
}
.pf-swatch:hover { transform: scale(1.08); }
.pf-swatch.on { border-color: var(--text); box-shadow: 0 0 0 2px var(--bg); }
/* A project's colour on the rail — what makes it findable without reading. */
.rail-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.rail-edit { opacity: 0; color: var(--text-faint); font-size: 11px; padding: 0 2px; }
.rail-item:hover .rail-edit { opacity: 1; }
.rail-edit:hover { color: var(--text); }
.cn-warn-none { color: var(--text-dim); }
.cn-usage-lrow.drillable, .cn-usage-mrow.drillable { cursor: pointer; border-radius: 8px; }
.cn-usage-lrow.drillable:hover, .cn-usage-mrow.drillable:hover { background: var(--bg-3); }
.cn-usage-lrow.drillable:focus-visible, .cn-usage-mrow.drillable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.cn-usage-drillhead { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 14px 0 6px; }
.cn-usage-back { appearance: none; background: var(--bg-3); border: 1px solid var(--border); color: var(--text-dim); font: inherit; font-size: 12.5px; font-weight: 600; padding: 5px 11px; border-radius: 9px; cursor: pointer; }
.cn-usage-back:hover { color: var(--text); border-color: var(--accent); }
.cn-usage-drilltitle { display: flex; align-items: center; gap: 8px; font-size: 14.5px; min-width: 0; word-break: break-word; }
.cn-usage-drillsub { font-size: 12px; opacity: .55; }
.cn-usage-budgetbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 11px 13px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg); margin: 0 0 12px; }
.cn-usage-budgetbar.warn { border-color: color-mix(in srgb, var(--warn, #f5a524) 55%, var(--border)); }
.cn-usage-budgetbar.over { border-color: color-mix(in srgb, var(--danger) 55%, var(--border)); }
.cn-usage-budgetlabel { font-size: 12.5px; font-weight: 700; opacity: .65; white-space: nowrap; }
.cn-usage-budgetoff { font-size: 12.5px; opacity: .55; flex: 1; min-width: 180px; }
.cn-usage-budgetmeter { position: relative; flex: 1; min-width: 140px; height: 8px; border-radius: 5px; background: var(--bg-3); overflow: visible; }
.cn-usage-budgetfill { display: block; height: 100%; border-radius: 5px; background: var(--accent); }
.cn-usage-budgetbar.warn .cn-usage-budgetfill { background: var(--warn, #f5a524); }
.cn-usage-budgetbar.over .cn-usage-budgetfill { background: var(--danger); }
.cn-usage-projmark { position: absolute; top: -3px; width: 2px; height: 14px; background: var(--text-faint); border-radius: 1px; }
.cn-usage-projmark.bad { background: var(--danger); }
.cn-usage-budgetnums { font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cn-usage-budgetpct { opacity: .55; margin-left: 4px; }
.cn-usage-budgetedit { display: inline-flex; align-items: center; gap: 6px; }
.cn-usage-cur { opacity: .5; font-size: 13px; }
.cn-usage-budgetinput { width: 84px; padding: 5px 8px; font-size: 13px; }
.cn-usage-budgetnote { font-size: 11.5px; opacity: .55; }
.cn-usage-delta { font-weight: 700; font-variant-numeric: tabular-nums; }
.cn-usage-delta.up { color: var(--danger); }
.cn-usage-delta.down { color: var(--ok, #3fb950); }
.cn-usage-chip.warnchip { color: var(--warn); background: var(--warn-soft); }

/* ============================================================
   LIBRARY SHELL — the layout Files and Memory share
   One page shape for the two stores the AI keeps: a big title
   with the search beside it, a row of category tabs, and a
   table underneath. Prefixed `lib-` because neither page owns
   it; a third store should use it rather than invent a fourth
   toolbar. Column widths come from --lib-cols, declared by the
   page (`.files-list`, `.mem-list`) so the header row and the
   data rows can never disagree about them.
   ============================================================ */
.lib-body { padding-top: 30px; }
.lib-wrap { max-width: 1020px; margin: 0 auto; }

.lib-head { display: flex; align-items: flex-start; gap: 20px; padding-bottom: 22px; }
.lib-head-main { min-width: 0; flex: 1; }
.lib-title { margin: 0; font-size: 30px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.2; }
/* Full width. The 560px measure wrapped one sentence into three lines in a header
   that had the room for one — the reading-column max-width is what bounds body text;
   a one-line page description does not need a second, narrower bound. */
.lib-sub { margin: 7px 0 0; font-size: 13px; color: var(--text-faint); }
.lib-head-tools { margin-left: auto; display: flex; align-items: center; gap: 10px; flex-shrink: 0; }

.lib-search-wrap { position: relative; width: 264px; }
.lib-search {
  width: 100%; height: 40px; padding: 0 14px 0 38px; font: inherit; font-size: 14px;
  color: var(--text); background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 999px; outline: none;
}
.lib-search::placeholder { color: var(--text-faint); }
.lib-search:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.lib-search::-webkit-search-cancel-button { filter: grayscale(1) opacity(0.6); }
.lib-search-ico {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  width: 15px; height: 15px; color: var(--text-faint); pointer-events: none;
}
/* The one create action a library page has. Inverted, so it is unmistakably the
   primary button without spending the accent on it. */
.lib-new {
  height: 40px; padding: 0 18px; border: 0; border-radius: 999px;
  font: inherit; font-size: 14px; font-weight: 600;
  background: var(--text); color: var(--bg);
}
.lib-new:hover { opacity: .88; }

.lib-bar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding-bottom: 10px; }
.lib-tabs { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; min-width: 0; }
.lib-tab {
  display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 14px;
  border: 0; border-radius: 999px; background: none; color: var(--text-dim);
  font: inherit; font-size: 13.5px; font-weight: 600; white-space: nowrap;
  transition: background .12s, color .12s;
}
.lib-tab:hover { background: var(--bg-2); color: var(--text); }
.lib-tab.on { background: var(--bg-3); color: var(--text); }
/* --bg-3 sits a shade ABOVE the page in the dark theme and a shade BELOW it in the
   light one, where it lands within a couple of points of --bg — so the selected tab
   and the selected view button need the deeper token there or they read as unselected. */
:root[data-theme="light"] .lib-tab.on,
:root[data-theme="light"] .lib-iconbtn.on { background: var(--bg-hover); }
.lib-tab-n { font-size: 11.5px; font-weight: 600; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.lib-tab.on .lib-tab-n { color: var(--text-dim); }

.lib-bar-end { margin-left: auto; display: flex; align-items: center; gap: 6px; }
.lib-summary {
  display: inline-flex; align-items: center; gap: 10px; white-space: nowrap;
  font-size: 12.5px; color: var(--text-faint); font-variant-numeric: tabular-nums;
}
.lib-iconbtn {
  width: 34px; height: 34px; flex-shrink: 0; display: inline-flex;
  align-items: center; justify-content: center;
  border: 0; border-radius: 999px; background: none; color: var(--text-dim);
  transition: background .12s, color .12s;
}
.lib-iconbtn:hover { background: var(--bg-2); color: var(--text); }
.lib-iconbtn.on { background: var(--bg-3); color: var(--text); }
.lib-iconbtn svg { width: 17px; height: 17px; }
.lib-linkbtn {
  background: none; border: 0; padding: 0 6px; font: inherit; font-size: 12.5px;
  color: var(--text-dim); white-space: nowrap;
}
.lib-linkbtn:hover { color: var(--text); }
/* The grouping switch is a control among icon buttons here, so it shrinks to match. */
.lib-seg { border-radius: 999px; }
.lib-seg .seg { font-size: 12.5px; padding: 6px 11px; }

/* The column header — Name / Modified / Size. Deliberately NOT sticky: the day
   headings below it are section headers of their own, and two bands pinned to the
   top of one scroller (this and the bulk bar) end up drawn over each other. */
.lib-thead {
  display: grid; grid-template-columns: var(--lib-cols); align-items: center; gap: 12px;
  padding: 6px 10px 8px; background: var(--bg); border-bottom: 1px solid var(--border);
  font-size: 12px; font-weight: 600; color: var(--text-faint);
}

/* ============================================================
   HEARTBEAT — one card per thing that runs on its own
   Name · switch · ⋯ on one line, a sentence, then when it runs
   and when it runs next. Every setting it used to wear moved
   into .hb-set, which the ⋯ menu lifts into a modal — the same
   elements, moved, never a second copy.
   ============================================================ */
.hb-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(370px, 1fr)); }
.hb-card {
  display: flex; flex-direction: column; gap: 9px; min-width: 0;
  padding: 15px 16px 13px;
  background: var(--bg-1); border: 1px solid var(--border); border-radius: 14px;
}
.hb-card.failing { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }
.hb-top { display: flex; align-items: center; gap: 11px; min-width: 0; }
.hb-ico { --ico-sz: 34px; }
.hb-name {
  flex: 1; min-width: 0; padding: 0; border: 0; background: none; text-align: left;
  font: inherit; font-size: 14.5px; font-weight: 600; color: var(--text); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hb-name:hover { text-decoration: underline; }
.hb-top-end { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
/* A count chip is short enough to sit beside the name; a label naming a role or a team
   is not, so those get a line of their own rather than a share of the title's width. */
.hb-top > .skl-chip { flex-shrink: 0; }
.hb-tags { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: -2px; }

/* The switch. A real pill, because on/off is the card's headline state and a checkbox
   reads as one setting among several. */
.hb-switch {
  position: relative; width: 38px; height: 22px; flex-shrink: 0; padding: 0;
  border: 0; border-radius: 999px; background: var(--bg-hover); cursor: pointer;
  transition: background .15s;
}
.hb-switch i {
  position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
  background: var(--text-faint); transition: transform .15s, background .15s;
}
.hb-switch.on { background: var(--accent); }
.hb-switch.on i { transform: translateX(16px); background: #fff; }
.hb-switch.dim { opacity: .45; cursor: default; }

.hb-more {
  width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;
  gap: 3px; border: 0; border-radius: 8px; background: none; color: var(--text-faint);
}
.hb-more span { width: 3px; height: 3px; border-radius: 50%; background: currentColor; }
.hb-more:hover { background: var(--bg-3); color: var(--text); }

.hb-sub { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--text-faint); }
.hb-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding-top: 10px; border-top: 1px solid var(--border);
  font-size: 12px; color: var(--text-dim);
}
.hb-when { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.hb-when .ico { width: 14px; height: 14px; flex-shrink: 0; opacity: .8; }
.hb-next { margin-left: auto; color: var(--text-faint); }
.hb-card.failing .hb-when { color: var(--danger); }

/* The ⋯ menu. Fixed, so it is never clipped by the card's own overflow. */
.hb-menu {
  position: fixed; z-index: 80; min-width: 186px; padding: 6px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: var(--shadow-2);
}
.hb-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px;
  border: 0; border-radius: 8px; background: none; color: var(--text);
  font: inherit; font-size: 13.5px; text-align: left;
}
.hb-menu-item:hover { background: var(--bg-3); }
.hb-menu-item .ico { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-faint); }
.hb-menu-item > span:not(.hb-menu-check) { flex: 1; min-width: 0; }
.hb-menu-gap { width: 16px; flex-shrink: 0; }
/* A toggle in a menu is a row with a tick, not a checkbox — the tick column is reserved
   whether or not it is on, so the labels do not shift when you switch one. */
.hb-menu-check { width: 14px; text-align: right; color: var(--accent); font-size: 13px; }
.hb-menu-item.danger, .hb-menu-item.danger .ico { color: var(--danger); }
.hb-menu-item.danger:hover { background: var(--danger-soft); }
/* A row that is only telling you something (why a thing is unavailable) — no hover,
   because there is nothing to click. */
.hb-menu-item.quiet { color: var(--text-faint); font-size: 12px; cursor: default; }
.hb-menu-item.quiet:hover { background: none; }

/* The settings sheet — the card's own controls, shown in a modal. */
.hb-set-modal { max-width: 520px; }
.hb-set-body .hb-set { display: flex; flex-wrap: wrap; gap: 12px; }
.hb-set-note { font-size: 12px; color: var(--text-faint); margin-right: auto; }

/* ============================================================
   ICON TILES — one outline set, coloured by subject
   The tile owns the colour (--ic) and the glyph inherits it, so
   an icon is styled by WHAT IT IS rather than per placement.
   Sizes come from the host (.cat-ico, .hb-ico, …) via --ico-sz;
   the tile is otherwise identical everywhere it appears.
   ============================================================ */
/* ⚠️ The size is a FALLBACK inside the value, never a declaration of its own.
   `--ico-sz: 40px` here and `--ico-sz: 24px` on the host are both one-class selectors,
   so the winner is whichever sits later in this file — and this block sits after most
   of them. Every tile whose host asked for a smaller size (a memory row's 24, a
   Heartbeat card's 34, a Company skill's 26, a detail head's 52) silently rendered at
   40 and overflowed its grid track: on Memory the tile spilled into the text column and
   the sentence started hard against the icon with no gap left. Written as a fallback,
   the host's value always wins, because there is nothing here to lose to. */
.ico-tile {
  --ic: var(--text-dim);
  width: var(--ico-sz, 40px); height: var(--ico-sz, 40px); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: calc(var(--ico-sz, 40px) / 3.6);
  color: var(--ic);
  background: color-mix(in srgb, var(--ic) 13%, transparent);
  border: 1px solid color-mix(in srgb, var(--ic) 24%, transparent);
}
.ico-tile .ico { width: 55%; height: 55%; display: block; }
/* Bare glyph, no tile — for a row that is already inside one (a menu item, a footer). */
.ico-bare { background: none; border: 0; width: auto; height: auto; }

.ic-rose { --ic: #f2708a; }
.ic-amber { --ic: #e0a458; }
.ic-green { --ic: #34c37e; }
.ic-cyan { --ic: #35b7c8; }
.ic-violet { --ic: #8b7ff8; }
.ic-blue { --ic: #4f8ff7; }
.ic-teal { --ic: #2fb8a0; }
.ic-slate { --ic: #9aa1ad; }
/* The light theme's page is near-white, so a 13% wash of a light hue vanishes and the
   stroke itself is too pale to read. Deepen both rather than re-picking the palette. */
:root[data-theme="light"] .ico-tile {
  background: color-mix(in srgb, var(--ic) 15%, #fff);
  border-color: color-mix(in srgb, var(--ic) 34%, transparent);
  color: color-mix(in srgb, var(--ic) 78%, #000);
}

/* ============================================================
   CATALOG LIST — Skills and Connections
   The library shell above, with a two-column list under it. A
   catalog card is for READING: an icon, a name, one line, and
   one mark on the right saying where you stand with it. The
   work happens on the thing's own page, so the card carries at
   most one control and never a row of chips.
   ============================================================ */
/* A catalog is two columns of cards, so it gets more room than a table of files. */
.lib-wide { max-width: 1180px; }

.cat-list {
  display: grid; gap: 12px; align-content: start;
  grid-template-columns: repeat(auto-fill, minmax(370px, 1fr));
}
/* Loading / empty / "nothing matches" lines are sentences, not cards — they span. */
.cat-list > .hint, .cat-list > .mk-empty, .cat-list > p { grid-column: 1 / -1; }

.cat-card {
  display: grid; grid-template-columns: 40px minmax(0, 1fr) auto;
  align-items: center; gap: 14px; min-width: 0;
  padding: 13px 16px; cursor: pointer;
  background: var(--bg-1); border: 1px solid var(--border); border-radius: 14px;
  transition: background .12s, border-color .12s;
}
.cat-card:hover { background: var(--bg-2); border-color: var(--bg-hover); }
/* Part of QuorieAI: nothing to open, so it must not offer to be opened. */
.cat-static, .cat-static:hover { cursor: default; background: var(--bg-1); border-color: var(--border); }
.cat-locked { opacity: .72; }

/* The tile is an .ico-tile now; this only says how big it is here. The rule stays for
   the few cards still carrying an emoji (extensions, roles), which get the old plate. */
.cat-ico {
  --ico-sz: 40px;
  width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
  font-size: 21px; border-radius: 11px;
}
.cat-ico:not(.ico-tile) { background: var(--bg-3); border: 1px solid var(--border); }
.cat-main { min-width: 0; }
.cat-name {
  font-size: 14px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* One line, always — the full text is in the card's tooltip and in full on its page.
   Two cards whose summaries wrap to different heights make a ragged column. */
.cat-sum {
  margin-top: 2px; font-size: 12.5px; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cat-sum.muted { opacity: .8; font-style: italic; }

.cat-act { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
.cat-check { font-size: 15px; color: var(--text-dim); line-height: 1; }
.cat-lock { font-size: 13px; opacity: .8; line-height: 1; }
.cat-note { font-size: 11.5px; color: var(--text-faint); white-space: nowrap; }
.cat-btn, .cat-act .cap-status { white-space: nowrap; }

@media (max-width: 620px) {
  .cat-list { grid-template-columns: 1fr; }
  .cat-card { padding: 12px 13px; gap: 12px; }
}

/* ============================================================
   Files view (#view-files)
   Namespaced `fv-` on purpose: `.file-row` / `.file-name` /
   `.file-meta` already belong to the chat download cards
   (renderFilesCard), and reusing them restyled those too.
   ============================================================ */
/* pick · icon · name · modified · size · actions */
/* The last column has to FIT its buttons: at 118px a row holding source, copy, preview,
   download and delete overflowed left and drew the copy icon on top of the size. Grid
   does not push a neighbour aside — it lets the cell spill. Widen here and size the
   buttons below, and the two stay in step. */
.files-list.lib-table { --lib-cols: 18px 40px minmax(0, 1fr) 140px 88px 158px; }

/* Bulk bar — only rendered while something is ticked. */
.files-bulk {
  position: sticky; top: 0; z-index: 6;
  display: flex; align-items: center; gap: 10px; margin: 0 0 10px;
  padding: 8px 14px; border: 1px solid var(--accent); border-radius: 999px;
  /* The tint over an OPAQUE ground: --accent-soft is translucent, and a sticky bar you
     can read the scrolling rows through is a bar nobody trusts. */
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--bg);
}
.files-bulk-count { font-size: 13px; font-weight: 600; color: var(--accent-2); margin-right: auto; }
.files-bulk-del { color: var(--danger); border-color: var(--danger); }
.files-bulk-del:hover:not(:disabled) { background: var(--danger-soft); }
.files-bulk-del:disabled { opacity: 0.6; cursor: default; }

/* Day groups — a quiet label between stretches of table, not a card. */
.fv-group { margin-top: 14px; }
/* The first group sits right under the column header (list) or the toolbar (grid) —
   both count, since the header row is only rendered for the list view. */
.fv-group:first-child, .lib-thead + .fv-group { margin-top: 0; }
.fv-group-head {
  display: flex; align-items: center; gap: 8px; margin: 0;
  padding: 14px 10px 8px; font-size: 12px; font-weight: 600;
  color: var(--text-faint);
}
.fv-group-n { font-size: 11.5px; font-weight: 600; color: var(--text-faint); font-variant-numeric: tabular-nums; }
/* Take a whole day in one tick. Dim until the group (or the header) is hovered, so a
   column of boxes doesn't compete with the file names for attention. */
.fv-gpick { opacity: .5; transition: opacity .12s ease; }
.fv-group:hover .fv-gpick,
.fv-gpick:has(:checked),
.fv-gpick:has(:indeterminate) { opacity: 1; }
/* "Select all" sits with the count it acts on. A link, not a button: it changes what is
   ticked, it never touches a file. */
.fv-selall {
  background: none; border: none; padding: 0; cursor: pointer; font: inherit;
  font-size: 12px; color: var(--accent); white-space: nowrap;
}
.fv-selall:hover { color: var(--accent-2); text-decoration: underline; }

/* List rows */
.fv-row {
  display: grid; grid-template-columns: var(--lib-cols); align-items: center; gap: 12px;
  padding: 9px 10px; border-radius: 10px; border-bottom: 1px solid var(--border);
  transition: background .12s, opacity .15s;
}
.fv-row:last-child { border-bottom: 0; }
.fv-row:hover { background: var(--bg-2); }
.fv-row.sel { background: var(--accent-soft); }
.fv-row.going { opacity: 0.4; pointer-events: none; }

.fv-pick { display: flex; align-items: center; width: 16px; flex-shrink: 0; }
.fv-check { cursor: pointer; accent-color: var(--accent); margin: 0; transition: opacity .12s; }

/* One rounded tile whatever the file is, so a column of mixed types still lines up. */
.fv-ico {
  --ico-sz: 40px;
  width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
  font-size: 19px; border-radius: 11px;
}
.fv-ico:not(.ico-tile) { background: var(--bg-3); border: 1px solid var(--border); }
/* A grid card has a whole preview panel to fill, so the tile is simply bigger there. */
.fv-card-ico.ico-tile { --ico-sz: 60px; }
.fv-thumb {
  width: 40px; height: 40px; flex-shrink: 0; object-fit: cover;
  border-radius: 9px; background: var(--bg-3); border: 1px solid var(--border);
}
.fv-main { min-width: 0; }
.fv-name {
  display: block; width: 100%; text-align: left; padding: 0; border: 0; background: none;
  font: inherit; font-size: 13.5px; font-weight: 500; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
button.fv-name { cursor: pointer; }
button.fv-name:hover { text-decoration: underline; }
/* Versions. Nothing renames a generated file, so re-running a report writes the same
   name again and the library grew rows that differed only in byte size. The name line
   carries which revision this is, and the fold carries the rest of them. */
.fv-nameline { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.fv-nameline .fv-name { flex: 0 1 auto; }
.fv-ver {
  flex-shrink: 0; font-size: 10.5px; font-weight: 700; letter-spacing: .02em;
  padding: 1px 5px; border-radius: 5px; color: var(--text-faint);
  background: var(--bg-3); border: 1px solid var(--border); font-variant-numeric: tabular-nums;
}
/* The newest is the one people mean by the bare filename, so it is the one that reads
   as current — the older revisions stay quiet rather than shouting a different number. */
.fv-ver.latest { color: var(--accent-2); border-color: var(--accent); background: var(--accent-soft); }
.fv-vtoggle {
  flex-shrink: 0; cursor: pointer; font: inherit; font-size: 11px; font-weight: 600;
  padding: 1px 7px; border-radius: 999px; color: var(--text-dim);
  background: var(--bg-3); border: 1px solid var(--border); white-space: nowrap;
}
.fv-vtoggle:hover { color: var(--accent-2); border-color: var(--accent); }
.fv-vtoggle.on { color: var(--accent-2); border-color: var(--accent); background: var(--accent-soft); }
/* An unfolded revision is indented and dimmed: still a real row with its own preview,
   download and delete, but plainly subordinate to the one above it. */
.fv-row.fv-vchild { padding-left: 26px; background: var(--bg-2); }
.fv-row.fv-vchild .fv-name { font-weight: 400; color: var(--text-dim); }
.fv-card.fv-vchild { border-style: dashed; }
.fv-cell { font-size: 12.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Size and time also live in the row itself, for the narrow layout that drops the
   columns. Hidden here rather than rebuilt there, so the two can't drift apart. */
.fv-meta { display: none; font-size: 11.5px; color: var(--text-faint); margin-top: 2px; }
.fv-acts { display: flex; align-items: center; justify-content: flex-end; gap: 2px; }
/* Icon-only in a row, so they are squares rather than pills: `.btn-mini`'s 14px side
   padding is sized for a word, and five of them do not fit a column that also has to
   leave the filename room to breathe. 5 × 30 + 4 × 2 gaps = 158, the column above. */
.fv-acts .btn-mini {
  width: 30px; height: 26px; padding: 0; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
}

.fv-src {
  width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center;
  padding: 0; font: inherit; font-size: 12px; cursor: pointer;
  color: var(--text-dim); background: none; border: 0; border-radius: 999px; flex-shrink: 0;
}
.fv-src:hover { background: var(--bg-3); color: var(--accent-2); }
.fv-del { flex-shrink: 0; }
.fv-del:hover { color: var(--danger); border-color: var(--danger); background: var(--danger-soft); }

/* Quiet the SECONDARY actions until a row is engaged, so a long list scans cleanly.
   Download is deliberately not among them: it is the reason this page exists, and a
   primary action you have to hover to discover is a hidden one.
   Gated on `hover: hover`, not a width — a tablet has a wide viewport and no pointer,
   so a max-width query would leave it unable to reach delete at all. */
@media (hover: hover) {
  .fv-row .fv-src, .fv-row .fv-del, .fv-check { opacity: 0; transition: opacity .12s; }
  .fv-row:hover .fv-src, .fv-row:hover .fv-del,
  .fv-row:focus-within .fv-src, .fv-row:focus-within .fv-del,
  .fv-row.sel .fv-src, .fv-row.sel .fv-del { opacity: 1; }
  .fv-row:hover .fv-check, .fv-row:focus-within .fv-check,
  .fv-row.sel .fv-check, .fv-check:checked { opacity: 1; }
  /* The whole tick-box chrome hides on a card, not just the input — a bordered
     empty square sitting on every thumbnail reads as a rendering fault. */
  .fv-card-pick { opacity: 0; transition: opacity .12s; }
  .fv-card:hover .fv-card-pick, .fv-card:focus-within .fv-card-pick,
  .fv-card.sel .fv-card-pick { opacity: 1; }
}

/* Grid — earns its place once the AI is producing charts and screenshots. */
.fv-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); padding-top: 4px; }
.fv-card {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 14px; transition: border-color .12s, box-shadow .12s, opacity .15s;
}
/* A card is the one place the stacked meta line is the layout, not a fallback. */
.fv-card .fv-meta { display: block; }
.fv-card .fv-name { white-space: nowrap; }
.fv-card:hover { border-color: var(--accent); box-shadow: var(--shadow-1); }
.fv-card.sel { border-color: var(--accent); background: var(--accent-soft); }
.fv-card.going { opacity: 0.4; pointer-events: none; }
.fv-card-pick {
  position: absolute; top: 8px; left: 8px; z-index: 2; width: auto;
  padding: 3px; border-radius: 5px; background: var(--bg-1); border: 1px solid var(--border);
}
.fv-card-prev {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 4 / 3; background: var(--bg-3); border-bottom: 1px solid var(--border);
}
.fv-card-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fv-card-ico { font-size: 40px; opacity: 0.85; }
.fv-card-body { padding: 9px 10px 4px; }
.fv-card-acts { display: flex; align-items: center; gap: 6px; padding: 4px 10px 9px; margin-top: auto; }
.fv-card-acts .file-dl, .fv-card-acts .fv-del { margin-left: auto; }
.fv-card-acts .fv-src { margin-right: auto; }
/* Preview joins the right-hand action group rather than opening a third gap. */
.fv-card-acts .file-open { margin-left: auto; }
.fv-card-acts .file-open + .file-dl { margin-left: 6px; }

/* The thumbnail is the other natural way to ask for a preview — it is a button
   wrapping the image, so it must add no chrome of its own. */
.fv-thumbbtn { display: flex; flex-shrink: 0; padding: 0; border: 0; background: none; cursor: pointer; }
button.fv-card-prev { width: 100%; padding: 0; border: 0; border-bottom: 1px solid var(--border); cursor: pointer; }

.linkish {
  font: inherit; font-size: inherit; color: var(--accent-2); background: none;
  border: 0; padding: 0 2px; cursor: pointer; text-decoration: underline;
}

/* Narrow: the Modified and Size columns fold away and the row's own meta line takes
   over — the same two numbers, stacked instead of ruled. */
@media (max-width: 760px) {
  .files-list.lib-table { --lib-cols: 18px 40px minmax(0, 1fr) 158px; }
  .fv-row > .fv-cell, .lib-thead > .fv-cell { display: none; }
  .fv-row .fv-meta { display: block; }
}
@media (max-width: 620px) {
  .lib-body { padding-top: 20px; }
  .lib-head { flex-wrap: wrap; gap: 14px; padding-bottom: 16px; }
  .lib-title { font-size: 24px; }
  .lib-head-tools { margin-left: 0; width: 100%; }
  .lib-search-wrap { flex: 1; width: auto; }
  .lib-bar-end { width: 100%; margin-left: 0; }
}

/* ===========================================================================
   Marketplace skill cards — who it's for, what it needs, and whether it's yours
   =========================================================================== */

/* The tab row is a .lib-bar; it needs nothing here beyond the space under it. */
#skills-controls { margin-bottom: 4px; }

/* Grouped, #skills-grid stops being a card list and becomes a column of sections, each
   holding its own .cat-list. The class is swapped in renderSkillsGrid rather than
   nesting, because a <section> inside a grid container becomes a grid ITEM and the
   whole group would collapse into one column. Headings are Company → Skills'
   (.skl-group-h), deliberately — the same shape wherever a skill list is grouped.
   This page no longer groups (the tabs do it); Heartbeat and Company → Skills still do. */
.mk-groups { display: block; }
.mk-groups > .skl-group:first-child { margin-top: 0; }

.mk-empty { color: var(--text-faint); font-size: 13px; padding: 26px 4px; grid-column: 1 / -1; }

/* ---- chat queue: messages typed while the AI was still working -------------
   These sit above the composer, NOT in the transcript, because nothing here has
   been said yet — each row is still editable and still cancellable. All colour
   comes from the token block at the top, so both themes follow automatically. */
.queue-list {
  display: flex; flex-direction: column; gap: 4px;
  margin: 0 0 8px; padding: 8px 10px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}
.queue-head { font-size: 12px; color: var(--text-dim); margin-bottom: 2px; }
.queue-note { opacity: .75; margin-left: 4px; }
/* Shown only while ↑ is walking back through what you've already asked, so the
   keys that got you here are named on screen rather than being folklore. */
.recall-hint {
  font-size: 12px; color: var(--text-dim);
  margin: 0 0 6px; padding: 2px 4px;
}
.recall-hint kbd {
  font: inherit; padding: 0 4px; border-radius: 3px;
  border: 1px solid var(--border); background: var(--accent-soft);
}
.queue-item {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--text);
  padding: 4px 6px; border-radius: var(--radius-sm);
}
.queue-item:hover { background: var(--accent-soft); }
.queue-n {
  flex: none; min-width: 18px; height: 18px; line-height: 18px; text-align: center;
  font-size: 11px; border-radius: 50%;
  background: var(--accent-soft); color: var(--text-dim);
}
/* The message can be any length; it must never widen the composer. */
.queue-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.queue-att { flex: none; font-size: 11px; color: var(--text-dim); }
.queue-actions { flex: none; display: flex; gap: 2px; }
.queue-btn {
  background: none; border: none; cursor: pointer;
  color: var(--text-dim); font-size: 12px; line-height: 1;
  padding: 3px 5px; border-radius: 4px;
}
.queue-btn:hover:not(:disabled) { background: var(--border); color: var(--text); }
.queue-btn:disabled { opacity: .3; cursor: default; }
.queue-x:hover { color: var(--warn); }

/* Send doubles as "queue" while a turn runs — same button, different promise. */
.btn-send.queueing { opacity: .85; }
.btn-send.queueing::after {
  content: "+"; font-size: 11px; vertical-align: super; margin-left: 1px;
}

/* ===================================================================
   THE RAIL STANDARD (2026-08)
   -------------------------------------------------------------------
   Group Chat, Company and the Inbox each replace the app sidebar with a
   left rail of their own, and each had grown its own idea of what a rail
   is: 280px vs 264, a padded scrolling container vs a fixed head over a
   scrolling list, flat nav rows vs bordered cards, three different active
   states. Walking between the three views, the furniture moved.

   One definition, applied to all three by grouping the selectors they
   already use — no class in app.js changes. The reference is the app
   sidebar itself (`.side-item`), because that is what these rails stand in
   for: same 264px, same row metrics, same accent-soft active state.

   Appended last, so it wins over the per-view rules above.
   =================================================================== */

/* 1 · Width. One number, from the grid parent that owns each rail. */
.gc-hub { grid-template-columns: var(--rail-w) minmax(0, 1fr) 460px; }
@media (max-width: 1040px) { .gc-hub { grid-template-columns: var(--rail-w) minmax(0, 1fr); } }

/* 2 · The container. The HEAD stays put and the LIST scrolls — a rail whose
   whole body scrolls takes its own title away when you go looking down it. */
.gc-rail, .cn-rail, .ibx-rail, .tsk-rail, .phot-rail, .mm-rail {
  border-right: 1px solid var(--border); background: var(--bg-2);
  display: flex; flex-direction: column; min-height: 0; padding: 0; gap: 0;
}
/* Company's body is one long scroller (nav + admin + companies + actions), so it
   keeps scrolling — but its children now carry the inset the container used to. */
.cn-rail { overflow-y: auto; padding-bottom: 10px; }
.cn-rail > * { padding-inline: 12px; }
/* …and the two pinned pieces stop compensating for a padding that is gone. */
.cn-rail .rail-chrome-top, .cn-rail .rail-foot.account-row { margin: 0; }

/* 3 · The head: one line, one weight, one border. */
.gc-rail-head, .ibx-rail-head, .tsk-rail-head, .cn-rail-nav-head,
.phot-rail-head, .mm-rail-head {
  display: flex; align-items: center; gap: 8px;
  padding: 14px 12px 10px; margin: 0; border-bottom: 1px solid var(--border);
}
.gc-rail-title, .ibx-rail-title, .tsk-rail-title, .cn-rail-nav-title,
.phot-rail-title, .mm-rail-title {
  font-weight: 700; font-size: 15px; color: var(--text); flex: 1; min-width: 0;
}
/* Company's identity block sits UNDER that head as content, not as a second head. */
.cn-rail-head { padding-top: 12px; padding-bottom: 4px; gap: 10px; }

/* 4 · The list. */
.gc-rail-list, .ibx-rail-list, .tsk-rail-list, .cn-nav, .cn-nav-admin, .cn-rail-list,
.phot-rail-list, .mm-rail-list {
  display: flex; flex-direction: column; gap: 1px;   /* `.side-nav`'s */
}
/* Company is the only rail that stacks BLOCKS (identity, nav, admin, switcher,
   actions) rather than one list, so it carries the sidebar's 10px between them. */
.cn-rail { gap: 10px; }
/* An empty count is not a count. Without this the badge draws as a grey dot next to a
   row that simply has nothing to report. */
.cn-nav-badge:empty, .ibx-fold-n:empty, .nav-badge:empty { display: none; }
.gc-rail-list, .ibx-rail-list, .tsk-rail-list,
.phot-rail-list, .mm-rail-list { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 12px; }

/* 5 · The row — the app sidebar's `.side-item`, to the pixel. Flat, not a card: a
   bordered box per chat made the Group Chat rail read as a list of objects to manage
   rather than places to go. The numbers are not arbitrary; they are `.side-item`'s, so
   a nav row is the same object wherever you meet it.
     gap 12 · padding 7/10 · radius 8 · 13.5px/500 · text-dim */
.gc-item, .cn-nav-item, .cn-rail-item, .ibx-fold, .split-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: none; border: 1px solid transparent; border-radius: 8px;
  padding: 7px 10px; cursor: pointer; font: inherit; font-size: 13.5px;
  font-weight: 500; color: var(--text-dim);
}
/* The icon box, also the sidebar's: a 20×18 slot so labels line up whether or not the
   row's glyph is a wide one, dimmed until the row is yours. */
.cn-nav-ico, .ibx-fold-ico {
  width: 20px; height: 18px; flex: 0 0 auto; display: inline-flex;
  align-items: center; justify-content: center; opacity: .82;
}
.cn-nav-ico svg, .ibx-fold-ico svg, .ibx-ico { width: 17px; height: 17px; display: block; }
.gc-item:hover .cn-nav-ico, .cn-nav-item:hover .cn-nav-ico, .ibx-fold:hover .ibx-fold-ico,
.cn-nav-item.active .cn-nav-ico, .ibx-fold.on .ibx-fold-ico { opacity: 1; }
/* A nested row hangs under its parent's LABEL, not under its icon: 10 (row) + 20 (icon
   slot) + 12 (gap) = 42. Guessed indents are what made these lists look ragged. */
.cn-nav-team { padding-left: 42px; }
/* A section label: the sidebar's, 10px and wide-tracked, so it reads as a label rather
   than competing with the rows under it. (The Inbox's fold heads are BUTTONS — a
   destination, not a label — so they keep their weight.) */
.cn-nav-section-title, .cn-rail-section {
  font-size: 10px; letter-spacing: .09em; color: var(--text-faint);
  padding: 8px 10px 3px; font-weight: 700; text-transform: uppercase;
}
.gc-item:hover, .cn-nav-item:hover, .cn-rail-item:hover, .ibx-fold:hover, .split-row:hover {
  background: var(--bg-hover); border-color: transparent; color: var(--text);
}
.gc-item.active, .cn-nav-item.active, .cn-rail-item.active, .ibx-fold.on, .split-row.on {
  background: var(--accent-soft); border-color: transparent;
  color: var(--text); font-weight: 600;
}
/* A folder still indents under its section head — that is structure, not decoration. */
.ibx-fold { padding-left: 22px; }
.ibx-fold-head { padding-left: 10px; }
/* Group Chat's rows carry an avatar and a second line, so they need the height back. */
.gc-item { padding: 8px 10px; }
/* Smaller here — and NOT re-declaring the radius. This rule sits 5400 lines below
   the base one and silently won the cascade: the tile was a circle everywhere the
   author looked and an 8px square in the rail that is actually on screen. A size
   override should override the size. */
.gc-item-ava { width: 30px; height: 30px; font-size: 15px; }
.gc-item-name { font-weight: 600; }
.gc-item.active .gc-item-name, .gc-item.active .gc-item-sub { color: var(--text); }

/* 6 · Counts, right-aligned, one shape. */
.cn-nav-badge, .ibx-fold-n {
  margin-left: auto; min-width: 18px; height: 18px; padding: 0 6px;
  border-radius: 999px; font-size: 11px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}

/* ===================================================================
   TASKS — the four-panel hub (2026-08)
   -------------------------------------------------------------------
   Projects | the list | the task | your AI. The board and the table are
   gone: two ways to look at the same rows, neither of which could show a
   task beside its own context, because opening one slid a drawer over the
   list you picked it from.

   The rail, its head and its rows come from THE RAIL STANDARD above — this
   block only lays the columns out and styles what is genuinely new here:
   the list rows, the task pane, and the panel's two envelopes.
   =================================================================== */
/* Calendar: the Tasks hub with the list column taken out. Three columns, not four —
   there is no list beside the month because the month IS the list, and a 330px column
   repeating what the grid already shows would be the same information twice. The rail
   and the AI column keep the exact widths Tasks and the Inbox use, so moving between
   the three does not move the furniture. */
.cal-hub {
  flex: 1; min-height: 0; display: grid; background: var(--bg); position: relative;
  grid-template-columns: var(--rail-w) minmax(0, 1fr) 460px;
}
.cal-rail {
  display: flex; flex-direction: column; min-height: 0;
  background: var(--bg-2); border-right: 1px solid var(--border);
}
.cal-rail-list { flex: 1; min-height: 0; overflow-y: auto; padding: 6px 8px 10px; }
.cal-main { min-width: 0; overflow-y: auto; padding: 18px 22px 36px; }
.cal-mainhead {
  display: flex; align-items: center; gap: 12px; margin-bottom: 14px; flex-wrap: wrap;
}
.cal-mainhead .cal-month { margin: 0; flex: 1; min-width: 0; }
/* One row per thing you can show or hide. A count, because "Birthdays" with nothing in
   the window is worth knowing before you go looking for one. */
.cal-cat {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 9px;
  border: 0; border-radius: 9px; background: none; color: var(--text-dim);
  font: inherit; font-size: 13.5px; text-align: left; cursor: pointer;
}
.cal-cat:hover { background: var(--bg-3); color: var(--text); }
.cal-cat.on { color: var(--text); }
.cal-cat-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; background: var(--text-faint); }
/* Off reads as OUTLINE, not as a dimmer colour: a hidden category has to be legible as
   a choice somebody made, and two greys are not a state anybody can name. */
.cal-cat.off .cal-cat-dot { background: none; box-shadow: inset 0 0 0 1.5px var(--text-faint); }
.cal-cat.off .cal-cat-label { text-decoration: line-through; opacity: .65; }
.cal-cat-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-cat-n { font-size: 11.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.cal-rail-note { padding: 10px 11px 4px; color: var(--text-faint); font-size: 11.5px; line-height: 1.5; }
.cal-rail-group {
  padding: 12px 11px 5px; font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-faint);
}
@media (max-width: 1560px) { .cal-hub { grid-template-columns: var(--rail-w) minmax(0, 1fr) 420px; } }
@media (max-width: 1180px) { .cal-hub { grid-template-columns: var(--rail-w) minmax(0, 1fr); }
  #cal-ai { display: none; } }
.tsk-hub {
  flex: 1; min-height: 0; display: grid; background: var(--bg); position: relative;
  grid-template-columns: var(--rail-w) 330px minmax(0, 1fr) 460px;
}
.tsk-rail-head .cn-icon-btn svg { width: 18px; height: 18px; }
.tsk-rail-foot { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); }

/* ---- the list ---- */
.tsk-listcol { border-right: 1px solid var(--border); background: var(--bg-2);
  display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.tsk-listhead { padding: 12px 12px 10px; border-bottom: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 8px; }
.tsk-search { width: 100%; background: var(--bg); border: 1px solid var(--border);
  border-radius: 9px; color: var(--text); padding: 7px 10px; font: inherit; font-size: 13px; }
.tsk-search:focus { outline: none; border-color: var(--accent); }
.tsk-listhead-row { display: flex; align-items: center; gap: 6px; }
.tsk-listhead-row .tasks-select { flex: 1; min-width: 0; font-size: 12px; padding: 5px 7px; }
.tsk-listhead-row .tasks-count { margin-left: auto; font-size: 11.5px; color: var(--text-faint);
  white-space: nowrap; }
.tsk-list { flex: 1; min-height: 0; overflow-y: auto; padding: 6px; display: flex;
  flex-direction: column; gap: 1px; }
/* With nothing in it the list must not hold the column open, or the empty state that
   explains WHY it is empty gets pushed to the bottom of the screen. */
.tsk-list:empty { flex: 0 0 auto; padding: 0; }
.tsk-listcol .tasks-empty { padding: 26px 18px; text-align: center; }
/* A row is a row, on the standard's metrics — but two lines, because a task without its
   project and owner is a title you have to open to identify. */
.tsk-row { display: flex; align-items: flex-start; gap: 9px; width: 100%; text-align: left;
  background: none; border: 1px solid transparent; border-radius: 8px; padding: 7px 10px;
  cursor: pointer; font: inherit; color: var(--text-dim); }
.tsk-row:hover { background: var(--bg-hover); color: var(--text); }
.tsk-row.active { background: var(--accent-soft); color: var(--text); }
.tsk-row.pinned { box-shadow: inset 2px 0 0 var(--accent); }
.tsk-row-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.tsk-row-title { font-size: 13px; font-weight: 500; color: var(--text); line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.tsk-row-meta { font-size: 11px; color: var(--text-faint); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.tsk-row-due { flex: 0 0 auto; font-size: 11px; }
.tsk-row-pr { flex: 0 0 auto; color: var(--danger); font-weight: 700; font-size: 12px; }
/* One dot, coloured by status: the fastest read on a list this dense, and the same
   vocabulary the board's columns used. */
.tsk-row-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; margin-top: 5px;
  background: var(--text-faint); }
.dot-todo { background: var(--text-faint); }
.dot-in_progress { background: var(--accent); }
.dot-ready { background: var(--info); }
.dot-blocked { background: var(--danger); }
.dot-done { background: var(--ok); }

/* ---- the task ---- */
.tsk-read { min-width: 0; overflow-y: auto; padding: 20px 26px 40px; background: var(--bg); }
.tsk-read-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.tsk-read-tools { margin-left: auto; display: flex; gap: 6px; }
.tsk-chip { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px;
  color: var(--text-dim); border: 1px solid var(--border); border-radius: 999px; padding: 2px 10px; }
.tsk-chip .tsk-row-dot { margin-top: 0; }
.tsk-chip.pr-high { color: var(--danger); border-color: rgba(239, 68, 68, .4); }
/* A hand-over that never arrived. Warning, not error: the task is safe, it is just
   still here — so it borrows the same weight as a high-priority chip, not a red one. */
.tsk-chip.warn { color: var(--warn); border-color: var(--warn); }
.tsk-handover { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 10px; padding: 8px 10px; border-radius: 8px; font-size: 12.5px;
  color: var(--text-dim); border: 1px solid var(--warn); background: var(--warn-soft); }
.tsk-handover > span { flex: 1; min-width: 220px; }
.tsk-link-row { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: none; border-radius: 8px; padding: 6px 8px; cursor: pointer;
  color: var(--text-dim); font: inherit; font-size: 13px; text-decoration: none; }
.tsk-link-row:hover { background: var(--bg-hover); color: var(--text); }
.tsk-controls { display: flex; gap: 12px; flex-wrap: wrap; }
.tsk-ctl { display: flex; flex-direction: column; gap: 4px; min-width: 150px; flex: 1; }
.tsk-ctl > span { font-size: 11px; color: var(--text-faint); }
.tsk-ctl select { background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px;
  color: var(--text); padding: 6px 8px; font: inherit; font-size: 13px; }

/* ---- the panel's two envelopes ---- */
/* ONE inset, stated once. The Inbox panel puts its padding on the container
   (`.ibx-ai`), which cannot work here: the tab row and the composer both need a full-
   width border, and the feed between them scrolls. So the container has none and each
   of the three regions carries the SAME 14px — which is what stops the eye seeing three
   different margins down one 460px column. */
.tsk-ai { --tsk-pad: 14px;
  border-left: 1px solid var(--border); background: var(--bg-2); display: flex;
  flex-direction: column; min-height: 0; min-width: 0; padding: 0; }
.tsk-tabs { display: flex; gap: 6px; flex-shrink: 0;
  padding: var(--tsk-pad); border-bottom: 1px solid var(--border); }
.tsk-ai .ibx-ai-body { flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: var(--tsk-pad); gap: 12px; }
/* The composer: same inset, its own rule rather than the Inbox's margin-plus-padding
   pair, which put an 11px gap above a 12px border and read as neither. */
.tsk-ai .ibx-ai-act { flex-shrink: 0; max-height: none; overflow: visible;
  margin: 0; padding: var(--tsk-pad); border-top: 1px solid var(--border); }
.tsk-ai .ibx-ai-tell { margin: 0; }
.tsk-tab { flex: 1; background: none; border: 1px solid var(--border); border-radius: 9px;
  padding: 7px 8px; cursor: pointer; font: inherit; font-size: 12px; font-weight: 600;
  color: var(--text-dim); display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.tsk-tab:hover { color: var(--text); border-color: var(--text-faint); }
.tsk-tab.on { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }
.tsk-tab-n { font-size: 10.5px; font-weight: 700; color: var(--text-faint);
  background: var(--bg-3); border-radius: 999px; padding: 0 6px; min-width: 17px; }
.tsk-tab.on .tsk-tab-n { background: var(--accent); color: var(--on-accent); }
.tsk-note { display: flex; flex-direction: column; gap: 8px; }

/* ---- the log ---- */
.tsk-log { display: flex; flex-direction: column; gap: 1px; }
.tsk-log-row { display: flex; align-items: flex-start; gap: 9px; width: 100%; text-align: left;
  background: none; border: none; border-radius: 8px; padding: 7px 9px; cursor: pointer;
  color: var(--text-dim); font: inherit; }
.tsk-log-row:hover { background: var(--bg-hover); }
.tsk-log-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--border);
  flex: 0 0 auto; margin-top: 6px; }
.tsk-log-row.note .tsk-log-dot { background: var(--accent); }
.tsk-log-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.tsk-log-top { font-size: 12.5px; color: var(--text); }
.tsk-log-verb { color: var(--text-dim); font-weight: 400; }
.tsk-log-when { color: var(--text-faint); font-size: 11px; margin-left: 4px; }
.tsk-log-body { font-size: 12px; color: var(--text-faint); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.tsk-log-open { padding: 8px 12px 12px 27px; font-size: 12.5px; color: var(--text-dim); }
.tsk-log-detail { display: flex; flex-direction: column; gap: 4px; overflow-wrap: anywhere; }
.tsk-log-k { font-size: 11px; color: var(--text-faint); text-transform: uppercase;
  letter-spacing: .05em; font-weight: 700; margin-right: 6px; }
.tsk-log-empty { padding: 18px 4px; }

/* Narrow screens give ground in the same order the Inbox does: the list and the panel
   tighten, then the panel becomes a drawer under the task rather than disappearing —
   every verb the AI offers lives in it. */
@media (max-width: 1560px) { .tsk-hub { grid-template-columns: var(--rail-w) 300px minmax(0, 1fr) 420px; }
  .tsk-read { padding: 18px 20px 36px; } }
@media (max-width: 1320px) { .tsk-hub { grid-template-columns: var(--rail-w) 264px minmax(0, 1fr) 360px; } }
@media (max-width: 1120px) {
  .tsk-hub {
    grid-template-columns: var(--rail-w) minmax(0, 300px) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto;
    grid-template-areas: "rail list read" "rail list ai";
  }
  .tsk-rail { grid-area: rail; }
  .tsk-listcol { grid-area: list; }
  .tsk-read { grid-area: read; }
  .tsk-ai { grid-area: ai; max-height: 46vh; border-left: 1px solid var(--border);
    border-top: 1px solid var(--border); }
}
@media (max-width: 920px) { .tsk-hub { grid-template-columns: 58px minmax(0, 260px) minmax(0, 1fr); }
  .tsk-rail-title, .tsk-rail .ibx-fold-label, .tsk-rail .ibx-fold-head { display: none; }
  .tsk-rail-head { justify-content: center; padding: 12px 4px 8px; }
  .tsk-rail .ibx-fold { justify-content: center; padding: 9px 4px; } }

/* ---- the panel's third envelope, and the pointer to it ---- */
/* Facts as a two-column key/value grid: eight labelled boxes down a 460px column is a
   scroll; eight rows is a glance. */
.tsk-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 4px 14px; padding: 4px 2px 14px; font-size: 12.5px; }
.tsk-fact-k { color: var(--text-faint); font-size: 11.5px; white-space: nowrap; }
.tsk-fact-v { color: var(--text); overflow-wrap: anywhere; }
.tsk-info-grp { padding-top: 12px; margin-top: 12px; border-top: 1px solid var(--border); }
.tsk-info-h { display: flex; align-items: center; gap: 8px; font-size: 11px;
  font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-faint); margin-bottom: 6px; }
.tsk-info-hint { font-size: 11.5px; color: var(--text-faint); line-height: 1.5;
  margin: -2px 0 7px; }
.tsk-link-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
/* The middle pane keeps a one-line pointer rather than five half-empty boxes. */
.tsk-rel-note { font-size: 12.5px; color: var(--text-faint); margin: 2px 0 14px; }
.tsk-rel-open { background: none; border: none; color: var(--accent-2); cursor: pointer;
  font: inherit; font-size: 12.5px; padding: 0; }
.tsk-rel-open:hover { text-decoration: underline; }

/* ===================================================================
   COMPANY, ON THE LIBRARY PAGE STYLE (2026-08)
   -------------------------------------------------------------------
   Skills, Connections, Memory and Files share one page look: a wide
   quiet title over a centred reading column, pill tabs, and cards that
   sit a shade above the page with a soft border. Company — including
   every admin sub-page — had its own: a 21px title in a bordered bar, a
   full-bleed pane, underlined tabs, and cards a shade BELOW the page.

   Same treatment as THE RAIL STANDARD above: the library's rules are
   applied to the classes Company already uses, so nothing in app.js
   changes. Appended last, so it wins.
   =================================================================== */

/* 1 · The page frame. FULL WIDTH with a margin, not a centred column: this is a
   directory and a set of control panels, not an article. Members is a grid of
   `auto-fill` cards, so the width buys columns rather than stretched cards, and the
   admin pages are label↔control rows that read better wide than tall.

   Still padding, never a max-width: the pane is the SCROLLER, and capping a scroller
   is what put Home's scrollbar 190px in from the window edge. Prose keeps its own cap
   further down — a 1,800px line of explanatory text is unreadable however wide the
   window is. */
.cn-pane { padding: 30px 32px 44px; }
.cn-pane[data-pane="chat"] { padding: 0; }

/* 2 · The head, in the library's voice: the title carries the page, the line
   under it is quiet, and there is no rule beneath — the content starts the
   moment the sentence ends. Aligned to the same column as the pane. */
.cn-main-head {
  padding: 30px 32px 0;
  border-bottom: 0; align-items: flex-end;
}
.cn-main-head h1, .cn-admin-pagehead h2 {
  margin: 0; font-size: 30px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.2;
}
.cn-main-head p, .cn-admin-pagehead p {
  margin: 7px 0 0; font-size: 13px; color: var(--text-faint); max-width: 560px;
}
.cn-admin-pagehead { margin: 0 0 22px; }

/* 3 · Tabs as pills, not underlines. */
.cn-tabs { gap: 2px; padding: 8px 32px 10px; border-bottom: 0; }
.cn-tab {
  display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 14px;
  border: 0; border-bottom: 0; border-radius: 999px; background: none;
  color: var(--text-dim); font-size: 13.5px; font-weight: 600; margin-bottom: 0;
  transition: background .12s, color .12s;
}
.cn-tab:hover { background: var(--bg-2); color: var(--text); }
.cn-tab.active { background: var(--bg-3); color: var(--text); border-bottom-color: transparent; }
:root[data-theme="light"] .cn-tab.active { background: var(--bg-hover); }
.cn-tab-count { background: none; color: var(--text-faint); font-size: 11.5px;
  padding: 0; margin-left: 0; font-variant-numeric: tabular-nums; }

/* 4 · Cards. The library's card sits a shade ABOVE the page (`--bg-1`) with a
   14px radius and a soft shadow; Company's sat on `--bg`/`--bg-2`, which reads
   as a well rather than a card. One treatment, every kind of card here. */
.cn-ppl-card, .cn-member-row, .cn-team-tile, .cn-member-tile,
.cn-settings-card, .cn-identity-card, .cn-knowledge-row {
  background: var(--bg-1); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: var(--shadow-1);
}
.cn-ppl-card, .cn-member-row, .cn-team-tile, .cn-member-tile { padding: 14px 16px; }
.cn-settings-card { padding: 18px 20px; }
.cn-ppl-card:hover, .cn-member-tile:hover, .cn-team-tile:hover {
  border-color: var(--accent); background: var(--bg-2);
}
/* A card's own heading, at the library's weight rather than a bold sentence. */
.cn-settings-h { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; margin-bottom: 7px; }
.cn-settings-desc { font-size: 13px; color: var(--text-faint); max-width: 78ch; }
/* The explanatory paragraph on an admin page, likewise: the CARD goes full width
   because its controls do, but the sentence explaining it does not. */
.cn-admin-pagehead p { max-width: 78ch; }
.cn-settings-pane { gap: 18px; }

/* 5 · Grids breathe like the library's. */
.cn-ppl-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.cn-members-grid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
.cn-members-list { gap: 10px; }

/* ===========================================================================
   Mind maps (frontend/mindmap.js)

   The SVG itself carries no CSS — its colours are written in as literals, read
   from the tokens at draw time, so a DOWNLOADED .svg still looks like the map
   you were looking at. Only the frame around it is styled here.
   =========================================================================== */
.mm-embed { margin: 14px 0; }

/* A wide map scrolls inside its own box. Without this a big map widens the chat
   column and the whole page scrolls sideways. */
.mm-canvas {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-1);
  padding: 6px;
}
.mm-svg { display: block; max-width: 100%; height: auto; }

/* Interactive map: drag a box onto another to re-parent, drag the background to
   pan, ctrl/cmd+scroll to zoom. `touch-action: none` is required or the browser
   claims the gesture for scrolling and pointermove never fires on touch. */
.mm-live { touch-action: none; cursor: grab; user-select: none; }
.mm-live.mm-panning { cursor: grabbing; }
.mm-live:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.mm-node { cursor: grab; }
.mm-node rect { transition: filter .12s; }
.mm-node:hover rect:first-of-type { filter: brightness(1.14); }
.mm-node.mm-dragging { opacity: .45; cursor: grabbing; }

/* The node the drop would land on. A ring rather than a fill change, so the
   node's own colour still reads while it is highlighted. */
.mm-node.mm-drop rect:first-of-type {
  stroke: var(--ok); stroke-width: 3px; filter: brightness(1.2);
}

.mm-toggle { cursor: pointer; }
.mm-toggle:hover circle { filter: brightness(1.25); }

.mm-hint {
  font-size: 11px; color: var(--text-faint); align-self: center;
  margin-left: 2px;
}
@media (max-width: 640px) { .mm-hint { display: none; } }

/* The selected node, and the overlaid input used to rename it. The input is real
   HTML positioned over the SVG rect — SVG <text> cannot be typed into, and
   contenteditable drags in a caret model and pasted rich text we do not want. */
.mm-node.mm-sel rect:first-of-type { stroke: var(--accent-2); stroke-width: 2.5px; }
.mm-canvas { position: relative; }
.mm-edit {
  position: absolute; z-index: 5; font: inherit; font-size: 13px; font-weight: 600;
  padding: 0 12px; border-radius: 8px;
  border: 2px solid var(--accent); background: var(--bg-2); color: var(--text);
  box-shadow: var(--shadow-2);
}
.mm-edit:focus { outline: none; }

/* ---- the Mind maps page ---- */
.mm-grid {
  display: grid; gap: 14px; margin-top: 18px;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.mm-card {
  text-align: left; font: inherit; cursor: pointer;
  padding: 16px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--bg-1); color: var(--text);
  transition: border-color .15s, transform .15s, box-shadow .15s;
}
.mm-card:hover {
  border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow-1);
}
.mm-card-title { font-weight: 600; margin-bottom: 6px; }
.mm-card-meta { font-size: 12px; color: var(--text-faint); }

.mm-empty { margin-top: 24px; color: var(--text-dim); max-width: 52ch; }
.mm-empty p { margin: 0 0 10px; }

.mm-detail-head { display: flex; align-items: center; gap: 14px; margin: 4px 0 14px; }
.mm-detail-title { margin: 0; font-size: 18px; font-weight: 600; }

/* The page canvas gets a real height and the SVG scales into it. A chat map keeps
   its natural size — blowing a three-node map up to full width helps nobody. */
.mm-page-embed { flex: 1; display: flex; }
.mm-page-embed .mm-canvas { flex: 1; height: 68vh; overflow: hidden; padding: 0; }
.mm-canvas-fit { display: flex; }
.mm-svg-fit { width: 100%; height: 100%; max-width: none; }

.mm-btn.mm-danger:hover { border-color: var(--danger); color: var(--danger); }
.mm-btn.mm-save { border-color: var(--accent); color: var(--text); }
/* The primary action on this page: it is what people look for and could not find. */
/* The style bar. Acts on the selected node; a fixed palette rather than a colour
   picker, because these colours are written into a DOWNLOADED svg opened where our
   stylesheet does not exist — a free picker makes black-on-black one click away. */
/* The insertion caret, shown when a drag would drop a node BETWEEN two siblings
   rather than inside one. Positioned in screen space over the canvas, for the same
   reason drop targeting is: the browser already knows where the boxes are. */
.mm-caret {
  position: absolute; height: 3px; border-radius: 2px;
  background: var(--ok); box-shadow: 0 0 0 1px var(--bg-1);
  pointer-events: none; display: none; z-index: 4;
}
/* The org-chart layout stacks siblings ACROSS, so its insertion line stands up.
   Width and height are set inline by showCaret(); this only swaps which of the two
   is the thin one, because a 3px-tall vertical caret is a dot. */
.mm-caret-v { height: auto; width: 3px; }

.mm-style {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: 10px; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-1);
}
.mm-style-label {
  font-size: 11px; color: var(--text-faint); margin-left: 10px; margin-right: 2px;
}
.mm-style-label:first-child { margin-left: 0; }
.mm-swatch {
  width: 20px; height: 20px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--border); padding: 0; background: transparent;
  transition: transform .12s, border-color .12s;
}
.mm-swatch:hover { transform: scale(1.15); border-color: var(--text); }
.mm-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* "Default" is a slash, not a colour — it CLEARS the key so the node goes back to
   its depth-based colour rather than baking today's theme in as a literal. */
.mm-swatch-none {
  background: linear-gradient(135deg, transparent 44%, var(--danger) 44%,
              var(--danger) 56%, transparent 56%);
}
.mm-btn.mm-mini { padding: 4px 9px; font-size: 11px; min-width: 28px; }

/* Connection state. Shown, not assumed — a person needs to know whether their
   edits are being shared or only kept locally. */
.mm-pill {
  align-self: center; font-size: 11px; padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--border); color: var(--text-faint); white-space: nowrap;
}
.mm-pill-live { border-color: var(--ok); color: var(--ok); background: var(--ok-soft); }
.mm-pill-retry { border-color: var(--warn); color: var(--warn); background: var(--warn-soft); }
.mm-save-needed { border-color: var(--warn); color: var(--warn); }

.mm-btn.mm-add { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.mm-btn.mm-add:hover { background: var(--accent-2); border-color: var(--accent-2); color: var(--on-accent); }

.mm-bar { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.mm-btn {
  font: inherit; font-size: 12px; line-height: 1;
  padding: 7px 12px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--bg-2); color: var(--text-dim);
  cursor: pointer; transition: background .15s, color .15s, border-color .15s;
}
.mm-btn:hover { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
.mm-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The format picker is a real <select> so it is keyboard- and screen-reader-correct
   without a hand-built popover. It only has to LOOK like the buttons beside it. */
.mm-pick { padding-right: 26px; appearance: none; -webkit-appearance: none; }
.mm-pick { background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                             linear-gradient(135deg, currentColor 50%, transparent 50%);
           background-position: calc(100% - 14px) 50%, calc(100% - 9px) 50%;
           background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
.mm-pick option { background: var(--bg-2); color: var(--text); }

/* An outline we could not parse is SHOWN, never swallowed — otherwise a model's
   malformed map looks like the answer simply stopped. */
.mm-failed pre {
  margin: 0; padding: 12px; overflow-x: auto;
  border: 1px dashed var(--warn); border-radius: var(--radius);
  background: var(--warn-soft); color: var(--text-dim);
  font-family: var(--mono); font-size: 12px; white-space: pre;
}

/* ---- Usage: the same breakdown as a table you can interrogate ----------------
   Bars answer "which is biggest" and nothing else. The table carries the three
   numbers they cannot: the share of the total, the average run, and the unit
   price — which is the only figure that actually compares two models. */
.cn-usage-tabend { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
.cn-usage-find {
  height: 28px; min-width: 180px; padding: 0 10px; font: inherit; font-size: 12.5px;
  color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: 999px;
}
.cn-usage-find:focus { outline: none; border-color: var(--accent); }
.cn-usage-seg { display: inline-flex; gap: 2px; background: var(--bg-3); border-radius: 8px; padding: 2px; }
.cn-usage-vbtn {
  border: 0; background: none; color: var(--text-faint); cursor: pointer;
  font: inherit; font-size: 12px; padding: 3px 9px; border-radius: 6px;
}
.cn-usage-vbtn.on { background: var(--bg-2); color: var(--text); }

.cn-ut { width: 100%; border-collapse: collapse; font-size: 12.5px; margin-top: 4px; }
.cn-ut th {
  text-align: right; font-size: 11px; font-weight: 600; color: var(--text-faint);
  padding: 7px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; cursor: pointer;
  user-select: none;
}
.cn-ut th.l, .cn-ut td.l { text-align: left; }
.cn-ut th:hover { color: var(--text); }
.cn-ut th.on { color: var(--text); }
.cn-ut td {
  padding: 8px 10px; text-align: right; color: var(--text);
  border-bottom: 1px solid var(--border); font-variant-numeric: tabular-nums;
}
.cn-ut tbody tr { cursor: pointer; }
.cn-ut tbody tr:hover td { background: var(--bg-hover); }
.cn-ut-name { display: flex; align-items: center; gap: 7px; min-width: 0; }
.cn-ut-none { color: var(--text-faint); }

/* ============================================================================
   The account menu and the tabbed Settings modal
   ============================================================================ */
/* The account row is one button now — Settings, the theme and Log out were three
   controls crammed onto it, two of them unlabelled glyphs and one of them a sign-out. */
.account-caret { margin-left: auto; color: var(--text-faint); font-size: 16px; line-height: 1; }

.acct-menu {
  position: fixed; z-index: 300; min-width: 244px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: var(--shadow-2); padding: 6px;
}
.acct-menu-head { display: flex; align-items: center; gap: 10px; padding: 10px 10px 8px; }
.acct-menu-who { display: flex; flex-direction: column; min-width: 0; }
.acct-menu-who strong { font-size: 14px; line-height: 1.2; }
.acct-menu-who small { color: var(--text-faint); font-size: 12px; }
.acct-menu-rule { height: 1px; background: var(--border); margin: 4px 8px; }
.acct-menu-item {
  display: flex; align-items: center; gap: 11px; width: 100%;
  background: none; border: 0; border-radius: 9px; cursor: pointer;
  color: var(--text); font: inherit; font-size: 14px; padding: 9px 10px; text-align: left;
}
.acct-menu-item:hover { background: var(--bg-hover); }
.acct-menu-item svg { width: 17px; height: 17px; flex-shrink: 0; color: var(--text-dim); }

/* ---- the shell: a rail of sections, the settings beside it ----------------------
   A row of tabs above a scrolling column meant the section names scrolled away with the
   content and a long pane read as one endless form. The rail stays put. */
.set-body { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 0; padding: 0; }
.set-nav {
  display: flex; flex-direction: column; gap: 2px; padding: 14px 10px;
  border-right: 1px solid var(--border); background: var(--bg-1);
  border-radius: var(--radius) 0 0 var(--radius);
}
.set-content { padding: 20px 22px; overflow-y: auto; min-width: 0; }
.set-search { position: relative; margin: 2px 2px 10px; }
.set-search svg {
  position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
  width: 14px; height: 14px; color: var(--text-faint); pointer-events: none;
}
.set-search input {
  width: 100%; padding: 7px 10px 7px 29px; font-size: 13px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 9px; color: var(--text);
}
.set-search input:focus { outline: none; border-color: var(--accent); }
.set-tab {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  appearance: none; background: none; border: 0; border-radius: 9px;
  color: var(--text-dim); font: inherit; font-size: 13.5px; font-weight: 600;
  padding: 8px 10px; cursor: pointer;
}
.set-tab svg { width: 16px; height: 16px; flex-shrink: 0; }
.set-tab:hover { color: var(--text); background: var(--bg-hover); }
.set-tab.on { color: var(--text); background: var(--bg-3); }
/* A section with nothing matching the search is not a section you can open. */
.set-tab.dim { opacity: .35; }

@media (max-width: 700px) {
  .set-body { grid-template-columns: minmax(0, 1fr); }
  .set-nav { flex-direction: row; overflow-x: auto; border-right: 0;
    border-bottom: 1px solid var(--border); border-radius: var(--radius) var(--radius) 0 0; }
  .set-search { display: none; }
  .set-tab span { white-space: nowrap; }
}
.set-pane-head { margin: 0 0 14px; }
.set-pane-head h3 { margin: 0 0 3px; font-size: 15px; }
.set-pane-head p { margin: 0; color: var(--text-faint); font-size: 12.5px; line-height: 1.5; }
.set-sub {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-faint); margin: 20px 0 8px;
}

/* ---- the library ---- */
.lib-list { display: flex; flex-direction: column; gap: 6px; }
.lib-row {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 10px;
}
.lib-row-main { min-width: 0; flex: 1; }
.lib-row-name { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 13.5px; font-weight: 600; }
.lib-row-sub { color: var(--text-faint); font-size: 12px; margin-top: 2px; }
/* An account with no key can be listed but never quietly selected as if it worked. */
.lib-row-cold { opacity: .72; }
.lib-tag {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px; background: var(--bg-hover); color: var(--text-faint);
}
.lib-tag.you { background: var(--accent-soft); color: var(--accent-2); }
.lib-tag.warn { background: var(--warn-soft); color: var(--warn); }
.lib-add { display: flex; gap: 8px; flex-wrap: wrap; }

/* The Settings modal is two columns now, so it needs the room a 480px dialog does not
   have. Scoped to this one overlay: every other modal in the app is a single column and
   480 is right for them. */
#settings-overlay .modal { max-width: 860px; }
/* ⚠️ `.modal-body` is a flex column with a gap — correct for every other dialog, and
   wrong for a grid. The rail and the content are the two grid tracks; the gap between
   them would be a stripe of background down the middle. */
#settings-overlay .modal-body.set-body { display: grid; gap: 0; }
.set-content .field { margin-bottom: 16px; }
.set-content .field:last-child { margin-bottom: 0; }
/* Its own class, never `.hidden`: a field can be hidden because it does not apply (a
   provider with no key field) AND hidden because it does not match a search. One class
   doing both means clearing the search un-hides fields that were never meant to show. */
.hidden-by-search { display: none !important; }

/* ⚠️ A checkbox is not a text field. `.field input:not([type=range])` sets width:100%,
   which stretched the BOX to the width of the dialog — so it centred itself and pushed
   its label into whatever space was left, which is what "the tick box is misaligned"
   looks like. `:not()` carries its argument's specificity, so the old rule is (0,2,1)
   and this has to match it and come later. */
.field input[type=checkbox], .field input[type=radio] {
  width: auto; flex: 0 0 auto; margin: 0; accent-color: var(--accent);
}
/* Label beside the box, reading left to right, and aligned to the FIRST line so a
   two-line label does not float its box to the middle. */
.mining-optout-row { display: flex; align-items: flex-start; gap: 9px; }
.mining-optout-row > span { font-weight: 500; color: var(--text); }

/* ---- the library table and the job rows ---------------------------------------- */
.lib-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 8px; }
.lib-head .set-sub { margin: 0; }
.lib-tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
.lib-tbl th {
  text-align: left; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-faint); font-weight: 700; padding: 0 10px 6px; white-space: nowrap;
}
.lib-tbl td { padding: 9px 10px; border-top: 1px solid var(--border); vertical-align: middle; }
.lib-tbl tr:hover td { background: var(--bg-3); }
.lib-name { font-weight: 600; }
.lib-id { color: var(--text-faint); font-size: 11.5px; font-family: var(--mono); }
.lib-act { text-align: right; width: 1%; white-space: nowrap; }
.lib-row-cold td { opacity: .7; }

/* The keys nobody on a managed seat needs, folded away with a summary that says so. */
.lib-keys { margin-top: 20px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-1); }
.lib-keys > summary {
  cursor: pointer; padding: 11px 13px; display: flex; align-items: baseline; gap: 9px;
  font-size: 13px; font-weight: 600; user-select: none;
}
.lib-keys > summary small { color: var(--text-faint); font-weight: 400; font-size: 11.5px; }
.lib-keys-body { padding: 4px 13px 14px; display: flex; flex-direction: column; gap: 14px; }

/* A job and its thinking depth read as one decision, so they sit on one line. */
.job-row { display: flex; gap: 12px; align-items: flex-end; margin-bottom: 16px; }
.job-row .field { margin-bottom: 0; }
.job-model { flex: 1 1 auto; min-width: 0; }
.job-think { flex: 0 0 132px; }
@media (max-width: 620px) { .job-row { flex-direction: column; align-items: stretch; } .job-think { flex: 1 1 auto; } }

/* ---- the add-a-model form -------------------------------------------------------
   ⚠️ These controls are NOT inside a `.field`, so `.field input`/`.field select` never
   reached them and they rendered as the browser's own grey boxes on a dark dialog —
   which is what "the add model is not styled" was. Styled here against the same tokens,
   rather than by wrapping them in a `.field` that would stack them vertically. */
.lib-add { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin: 10px 0 0; }
.lib-add select, .lib-add input {
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 9px; padding: 9px 11px; font-size: 13.5px; font-family: inherit; outline: none;
  height: 38px; min-width: 0;
}
.lib-add select { flex: 0 0 168px; }
.lib-add #lib-model { flex: 2 1 190px; }
.lib-add #lib-model-other { flex: 2 1 190px; }
.lib-add #lib-key { flex: 2 1 200px; }
.lib-add select:focus, .lib-add input:focus { border-color: var(--accent); }
.lib-add .btn-primary, .lib-add .btn-secondary { height: 38px; padding: 0 16px; flex: 0 0 auto; }

/* ---- one row per account that needs a key ---- */
.key-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 0; border-top: 1px solid var(--border);
}
.key-row:first-child { border-top: 0; }
.key-who { display: flex; align-items: center; gap: 8px; flex: 0 0 190px; font-size: 13.5px; }
.key-in { display: flex; gap: 8px; flex: 1 1 260px; min-width: 0; }
.key-in input {
  flex: 1; min-width: 0; background: var(--bg); border: 1px solid var(--border);
  color: var(--text); border-radius: 9px; padding: 9px 11px; font-size: 13.5px; outline: none;
}
.key-in input:focus { border-color: var(--accent); }
.lib-tag.ok { background: var(--ok-soft); color: var(--ok); }
/* The inline key editor a "no key" row swaps itself for. */
.lib-key-in {
  background: var(--bg); border: 1px solid var(--border); color: var(--text);
  border-radius: 8px; padding: 6px 9px; font-size: 12.5px; width: 190px; outline: none;
}
.lib-key-in:focus { border-color: var(--accent); }
.lib-act .btn-mini + .btn-mini { margin-left: 6px; }

/* Multi-select. Every selected box gets the ring; the PRIMARY one gets a brighter
   one, because with six highlighted "Tab adds a child" has to say which box it
   will hang off. */
.mm-node.mm-sel rect:first-of-type { stroke: var(--accent-2); stroke-width: 2.5px; }
.mm-node.mm-sel-primary rect:first-of-type { stroke: var(--ok); stroke-width: 3px; }

/* ---- per-message actions (hover) --------------------------------------------------
   Copy on any message, Edit on your own. They live INSIDE the row and only appear on
   hover, so a quiet transcript stays quiet — a toolbar always on every message turns a
   conversation into a control panel.
   ⚠️ They sit IN FLOW under the bubble, as the last child of `.msg-body`, which is what
   aligns them with the message on both sides — the body is a flex column already ending
   `flex-start` for the AI and `flex-end` for you. Absolutely positioning them instead
   drew them ON TOP of the bubble: a row is only as tall as its message, so `bottom: -2px`
   is inside a one-line bubble, not under it, and the two magic offsets it needed
   (`right: 58px` for the avatar and gap) were a second copy of a layout the flexbox
   already knew.
   ⚠️ `visibility` rather than `display`, and a real reserved height, so revealing them
   does not reflow the message underneath: a row that grows on hover makes the thing you
   were about to click move out from under the pointer. An absolute element reserves
   nothing, so that promise was only ever written down. */
.msg-row { position: relative; }
/* The strip is reserved on the BODY and the toolbar is pulled back into it, so the row
   is the same height before and after `ensureMsgActs` runs. It is injected lazily, on
   the first hover — so an in-flow toolbar with no reservation would make every message
   grow by its own height the first time you pointed at it, shunting the rest of the
   transcript down. Reserved unconditionally, the toolbar costs its space once, at
   render, whether or not it is ever built. */
.msg-row.user .msg-body,
.msg-row.assistant .msg-body { padding-bottom: 26px; }
.msg-acts {
  display: flex; gap: 2px; height: 26px; margin-bottom: -26px;
  opacity: 0; visibility: hidden; transition: opacity .12s;
}
.msg-row:hover .msg-acts,
.msg-row:focus-within .msg-acts { opacity: 1; visibility: visible; }
/* Not while the message is being rewritten. Edit over the box already editing it can
   only do nothing, and Copy would hand you the old text the box has replaced.
   ⚠️ `visibility`, not `display`, so the reserved strip keeps its height and the editor
   does not jump the moment you open it. Specificity beats the :hover rule above without
   an !important — `.msg-row.editing:focus-within` is one class heavier. */
.msg-row.editing .msg-acts,
.msg-row.editing:hover .msg-acts,
.msg-row.editing:focus-within .msg-acts { opacity: 0; visibility: hidden; }
.msg-act {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; border-radius: 7px; cursor: pointer;
  background: transparent; border: 1px solid transparent; color: var(--text-faint);
}
.msg-act:hover { background: var(--bg-3); border-color: var(--border); color: var(--text); }
/* ⚠️ Load-bearing. `hidden` is how Edit is withheld from a message that cannot be
   rewound yet, but the UA's `[hidden] { display: none }` loses to the `display:
   inline-flex` above it — without this line the attribute sets, the tests pass and the
   button stays on screen. */
.msg-act[hidden] { display: none; }
.msg-act svg { width: 14px; height: 14px; }

/* The inline editor. Full width of the bubble column, because a message being rewritten
   is being read as a whole — an editor the width of the old text makes you scroll to
   see what you are changing. */
.msg-edit { display: flex; flex-direction: column; gap: 8px; width: 100%; min-width: 320px; }
.msg-edit-box {
  width: 100%; resize: none; font: inherit; line-height: 1.5;
  color: var(--text); background: var(--bg-2);
  border: 1px solid var(--accent); border-radius: 12px; padding: 10px 12px; outline: none;
}
.msg-edit-row { display: flex; gap: 8px; justify-content: flex-end; }
.msg-edit-row .btn-primary, .msg-edit-row .btn-secondary { padding: 6px 14px; font-size: 13px; }

/* ---- Calendar -----------------------------------------------------------------------
   A month grid plus a "coming up" list. Everything is token-driven, so it themes with
   the rest of the app and needs no dark/light branches of its own.
   ⚠️ The grid is SEVEN fixed columns with `minmax(0, 1fr)`, not `1fr`. A bare `1fr` is
   min-content by default, so one long event title stretches its column and the week
   stops lining up with the day names above it. */
.cal-nav { display: inline-flex; gap: 6px; margin-right: 8px; }
.cal-monthbar { display: flex; align-items: baseline; gap: 10px; margin: 4px 0 10px; }
.cal-month { font-size: 17px; font-weight: 650; margin: 0; }

.cal-grid {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px; background: var(--border); border: 1px solid var(--border);
  border-radius: 12px; overflow: hidden;
}
.cal-dow {
  background: var(--bg-2); padding: 7px 8px; font-size: 11px; font-weight: 650;
  letter-spacing: .03em; text-transform: uppercase; color: var(--text-faint);
}
.cal-day {
  background: var(--bg-1); min-height: 96px; padding: 6px 6px 8px;
  display: flex; flex-direction: column; gap: 4px;
}
/* Days spilling in from the neighbouring months are dimmed rather than blanked: the
   week they belong to still has to read as a week. */
.cal-out { background: var(--bg-2); }
.cal-out .cal-daynum { color: var(--text-faint); }
.cal-today { box-shadow: inset 0 0 0 2px var(--accent); }
.cal-daynum { font-size: 12px; font-weight: 650; color: var(--text-dim); }
.cal-today .cal-daynum { color: var(--accent); }
.cal-chips { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

/* One chip shape, four colours. The colour carries the KIND, so a glance at a month
   separates "we are closed" from "somebody has a deadline" without reading a word. */
.cal-chip {
  display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-size: 11px; line-height: 1.5; padding: 1px 6px;
  border-radius: 6px; border: 1px solid transparent;
}
.cal-chip-holiday  { background: var(--ok-soft);    color: var(--ok); }
.cal-chip-event    { background: var(--accent-soft); color: var(--accent); }
.cal-chip-birthday { background: var(--warn-soft);  color: var(--warn); }
.cal-chip-task     { background: var(--bg-3); color: var(--text-dim); border-color: var(--border); }
/* A finished task stays on the day it was due — removing it would rewrite the month —
   but stops competing for attention with the ones that are not done. */
.cal-chip-task.cal-done { opacity: .55; text-decoration: line-through; }

.cal-upcoming { margin-top: 18px; }
.cal-up-title { font-size: 13px; font-weight: 650; color: var(--text-dim); margin: 0 0 8px; }
.cal-up-row {
  display: flex; align-items: center; gap: 10px; padding: 5px 0;
  border-bottom: 1px solid var(--border);
}
.cal-up-when { font-size: 12px; color: var(--text-faint); min-width: 96px; flex-shrink: 0; }
.cal-up-note { font-size: 12px; color: var(--text-faint); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-up-empty { font-size: 13px; color: var(--text-faint); }

/* Narrow screens: the seven columns stay (a week that wraps is not a week), but the
   squares shrink and the chips lose their text rather than the grid losing its shape. */
@media (max-width: 720px) {
  .cal-day { min-height: 64px; }
  .cal-up-when { min-width: 72px; }
}

/* A model probe's verdict, in the three colours the three outcomes deserve: it works,
   the vendor refused something, or we could not tell (which is not the same as bad). */
.hint.good { color: var(--ok); }
.hint.warn { color: var(--warn); }
#lib-test-result:empty { display: none; }
.lib-act .btn-mini { margin-left: 6px; }

/* A single choice inside a confirm dialog (e.g. how long an egress approval lasts).
   In flow under the body text, so the decision and its duration read as one thing. */
.dialog-choice { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; }
.dialog-choice label { font-size: 12px; color: var(--text-dim); }
.dialog-choice select {
  width: 100%; padding: 8px 10px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--bg-3); color: var(--text);
  font-size: 13px;
}

/* ---- Handing a chat to a real person -----------------------------------------------
   The @mention picker, IN the transcript rather than over it. It was a modal, which dimmed
   the very messages you are deciding from and made a small choice feel like a dialog you
   had to escape. On the chat's own column (the 780px/22px `.msg-row` uses) so it reads as
   part of the conversation and not as a panel that has landed on top of it. */
.ho-card {
  max-width: 780px; margin: 10px auto; padding: 0 22px; width: 100%;
  animation: ho-in .16s ease-out;
}
@keyframes ho-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; } }
/* The accent edge is what separates it from a message without making it a dialog: this is
   the app asking, not somebody speaking. */
.ho-card > .ho-head, .ho-card > .ho-body, .ho-card > .ho-foot {
  background: var(--bg-2); border-left: 2px solid var(--accent);
  border-top: 1px solid var(--border); border-right: 1px solid var(--border);
  padding: 10px 14px;
}
.ho-card > .ho-head { border-radius: 12px 12px 0 0; display: flex; align-items: center; gap: 10px; }
.ho-card > .ho-foot {
  border-radius: 0 0 12px 12px; border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.ho-card > .ho-body { display: flex; flex-direction: column; gap: 7px; }
.ho-title { font-size: 13.5px; font-weight: 650; flex: 1; min-width: 0; }
.ho-x {
  background: none; border: 0; color: var(--text-faint); cursor: pointer;
  font-size: 13px; line-height: 1; padding: 2px 4px; border-radius: 6px; flex: none;
}
.ho-x:hover { color: var(--text); background: var(--bg-3, var(--bg-2)); }
.ho-lead { margin: 0 0 1px; font-size: 12.5px; color: var(--text-dim); }
.ho-loading { font-size: 12.5px; color: var(--text-faint); }
.ho-topic {
  display: flex; flex-direction: column; gap: 2px; text-align: left; width: 100%;
  background: var(--bg-1); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; cursor: pointer; color: var(--text);
  transition: border-color .14s ease, background .14s ease;
}
.ho-topic:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg-1)); }
.ho-topic:disabled, .ho-topic.is-busy { opacity: .55; cursor: default; }
.ho-topic-title { font-size: 13px; font-weight: 600; line-height: 1.3; }
.ho-topic-why { font-size: 11.5px; color: var(--text-dim); line-height: 1.4; }
/* The note carries the privacy rule, because that is what somebody needs to know BEFORE
   they click, not after. It takes the space so the button stays its own width — as a
   modal footer the button was squeezed to two wrapped lines. */
.ho-note { font-size: 11px; color: var(--text-faint); flex: 1 1 180px; min-width: 0; line-height: 1.4; }
.ho-skip {
  flex: none; background: none; border: 1px solid var(--border); border-radius: 8px;
  padding: 5px 11px; font-size: 12px; color: var(--text-dim); cursor: pointer;
  white-space: nowrap;
}
.ho-skip:hover { color: var(--text); border-color: var(--accent); }

/* ---- "lsheng is typing…" ----------------------------------------------------------
   Lives in two places that both already have a line for something else: the chat
   header's participant list, and the rail row's last-message preview. It REPLACES
   both rather than squeezing in beside them — the two compete for one line, and while
   somebody is mid-sentence the transient one is the one being waited on.

   Coloured, so it reads as live rather than as another piece of grey metadata. The
   dots carry the motion; the words do not blink, because text that pulses is harder to
   read than text that sits still next to something that moves. */
.gc-typing {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--accent); font-weight: 500;
}
.gc-typing-dots { display: inline-flex; align-items: flex-end; gap: 3px; height: 10px; }
.gc-typing-dots i {
  width: 4px; height: 4px; border-radius: 50%;
  background: currentColor; opacity: .35;
  animation: gc-typing-bounce 1.25s infinite ease-in-out;
}
/* Staggered so the three read as one travelling wave rather than a triple flash. */
.gc-typing-dots i:nth-child(2) { animation-delay: .18s; }
.gc-typing-dots i:nth-child(3) { animation-delay: .36s; }
@keyframes gc-typing-bounce {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30%           { opacity: 1;  transform: translateY(-3px); }
}
/* Someone who has asked for less motion still gets the indicator — it just holds
   steady instead of bouncing. The information is in the words either way. */
@media (prefers-reduced-motion: reduce) {
  .gc-typing-dots i { animation: none; opacity: .75; }
}
/* In the rail the row is narrow and the preview is already truncated; the typing line
   inherits that and simply outranks what it replaced. */
.gc-item-sub .gc-typing { max-width: 100%; overflow: hidden; }

/* In the THREAD it is the last bubble, sitting where the message being written is about
   to land. It reuses `.cn-smsg` so the alignment, max-width and gap are the ones every
   other incoming message already has — the only thing added is that it is smaller and
   quieter, because it is a placeholder and not a thing anybody said. */
.gc-typing-body {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 13px;
  color: var(--text-faint);
  font-size: 12.5px;
}
.gc-typing-who { color: var(--accent); font-weight: 500; }
/* It appears and disappears constantly, so it arrives rather than blinking — the
   conversation above it shifts by this bubble's height either way. */
.gc-typing-row { animation: gc-typing-in .16s ease-out; }
@keyframes gc-typing-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) { .gc-typing-row { animation: none; } }

/* The preparation box inside the handoff card. It IS a `.wb-box` — the same component the
   AI's own turns use, so "the app is working" looks the same wherever it happens — and
   only needs to sit properly inside the card's column. Its head stays clickable: it is a
   real Workbench, so collapsing it works here exactly as it does on a turn. */
/* ⚠️ Phase one carries NO layout of its own. It is a `.wb-box` inside a real assistant
   row, so the column, the width (`.msg-body > .wb-box` stretches it) and the avatar all
   come from the row — the same rules a turn's Workbench obeys. Every override added here
   was a way of re-deriving that geometry by hand and getting it slightly wrong. */
/* Said under the finished Workbench when there was nothing to compact — the steps stay,
   because the run that went wrong is the one worth reading. */
.ho-inline-note { font-size: 13px; color: var(--text-dim); padding: 6px 0 0; }

/* ---- "What needs you" ---------------------------------------------------------------
   The line every AI panel leads with, in Messages, Tasks and the Inbox. One block, one
   look — three panels answering the same question in three shapes is how they stopped
   feeling like one product.

   Quiet on purpose: it is a fact, not an alert. Only `act` — something waiting on YOU
   personally — gets the accent, because a panel where everything is highlighted has
   highlighted nothing. */
.ny-host:empty { display: none; }
.ny-box {
  display: flex; flex-direction: column; gap: 1px;
  padding: 8px 12px; border-bottom: 1px solid var(--border); background: var(--bg-2);
}
.ny-row { display: flex; align-items: flex-start; gap: 8px; font-size: 12.5px; line-height: 1.45; }
.ny-dot {
  flex: none; width: 6px; height: 6px; border-radius: 50%; margin-top: 6px;
  background: var(--text-faint);
}
.ny-act .ny-dot { background: var(--accent); }
.ny-watch .ny-dot { background: var(--warn); }
.ny-act .ny-txt { color: var(--text); font-weight: 550; }
.ny-watch .ny-txt { color: var(--text); }
.ny-calm .ny-txt { color: var(--text-faint); }
.ny-txt { min-width: 0; }
/* The second line is the evidence for the first — the date behind "overdue by 4 days".
   Its own line, because a fact and its proof read badly run together. */
.ny-detail { display: block; font-size: 11px; color: var(--text-faint); line-height: 1.4; }
/* Nothing waiting is still worth saying, but it should not look like news. */
.ny-is-quiet { background: transparent; }

/* ---- Sidebar sections fold away --------------------------------------------------
   The heading is the control, so it looks like one: full width, a caret that turns,
   and a hover state. It was a static label, which is why it has to gain affordance
   rather than just gain a handler — a thing that does something and looks like text
   is a thing nobody clicks. */
.side-section-title {
  display: flex; align-items: center; gap: 6px; width: 100%;
  background: none; border: 0; cursor: pointer; text-align: left;
  /* ⚠️ Only what a <button> brings of its own. The typography is NOT restated: the
     existing `.side-section-title` rules are class selectors and already apply here,
     and repeating them in a block that sits last would win the cascade and silently
     resize every section label in the app. `font: inherit` is the same trap wearing a
     shorthand — it would reset font-size with it. */
  font-family: inherit;
  border-radius: 8px;
}
.side-section-title:hover { color: var(--text-dim); }
.side-section-title:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.sec-caret {
  margin-left: auto; display: inline-flex; flex-shrink: 0;
  transition: transform .16s ease; opacity: .55;
}
.sec-caret svg { width: 12px; height: 12px; }
.side-section.folded .sec-caret { transform: rotate(-90deg); }
.side-section.folded .side-sec-body { display: none; }

/* ⚠️ The count of what a folded group is hiding. Without it, folding "My work" would
   silently swallow the Inbox badge — the one thing in this sidebar that means somebody
   is waiting on you. It uses the unread colour, not the accent, for the same reason. */
.sec-count {
  margin-left: auto; flex-shrink: 0;
  min-width: 16px; height: 16px; padding: 0 5px;
  border-radius: 8px; background: var(--danger); color: #fff;
  font-size: 10px; font-weight: 700; letter-spacing: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.sec-count + .sec-caret { margin-left: 6px; }

/* Recents pairs the toggle with its search button, so they share a row. */
.side-sec-head { display: flex; align-items: center; }
.side-sec-head .side-section-title { flex: 1; min-width: 0; }
.side-sec-head .recents-search-btn { flex-shrink: 0; }
/* Recents is the one whose body must keep scrolling when it is open. */
.side-recents .side-sec-body { display: flex; flex-direction: column; min-height: 0; flex: 1; }
@media (prefers-reduced-motion: reduce) { .sec-caret { transition: none; } }

/* ---- Home, tidied (2026-08-21) ---------------------------------------------------
   The page allocated space by CATEGORY rather than by whether the category had
   anything in it: on a quiet morning three blocks each took a full card to say
   nothing, above a block holding fifty items. These rules are the fix. */

/* A section with nothing in it keeps its heading and drops its body — one quiet line
   instead of a card. ⚠️ It is not hidden outright: "no reports today" and "reports are
   broken" would then look identical, and everything below would jump each time a
   count reached zero. */
.home-card.is-empty > .home-card-body { display: none; }
.home-card.is-empty {
  background: none; border-color: transparent; box-shadow: none;
  padding-top: 4px; padding-bottom: 4px;
}
.home-card.is-empty .home-card-head { margin: 0; }
.home-card.is-empty h2 { color: var(--text-faint); font-weight: 600; }

/* Nothing waiting is good news, so it reads as a settled line rather than as an alarm
   with a big number. The count and the call to action both stand down. */
.home-act.clear { padding-block: 12px; }
.home-act.clear .home-act-n { font-size: 20px; color: var(--ok, #1f9d55); opacity: .8; }
.home-act.clear .home-act-title { font-weight: 600; }
.home-act.clear .home-act-go {
  background: none; border: 0; color: var(--text-dim);
  font-size: 12.5px; padding: 4px 6px; box-shadow: none;
}
.home-act.clear .home-act-go:hover { color: var(--accent); background: var(--bg-hover); }

/* "+N more" is one control for the whole grid, so it sits under it rather than
   becoming a cell that looks like another card. */
.home-card-router > .home-more { margin: 10px 0 0; }

/* Getting started, once every remaining step is somebody else's to take. It stops
   being a checklist you work and becomes a note about what you are waiting for. */
.home-setup--waiting {
  background: none; border-style: dashed; box-shadow: none; opacity: .85;
}
.home-setup--waiting .home-setup-ico { opacity: .5; }

/* ---- "Ask about the calendar" suggestions -----------------------------------------
   `.ibx-sugs` / `.ibx-sug` had NO rule anywhere in this stylesheet. The names read as
   if they belonged to the Inbox panel's family, so they looked styled in the markup —
   but nothing had ever written them, and four browser-default buttons stacked up grey,
   square and ragged at the bottom of the panel.

   ⚠️ Deliberately NOT the app's pill (.gc-chip / .ibx-chip). That language is for one
   or two WORDS — "Summarize", "Catch me up" — and a 999px radius around a
   forty-character question in a 380px column wraps into a lozenge with a curved gap
   down each side. A suggestion you have to READ before clicking wants a row, so these
   are full-width and left-aligned: the four questions line up on their first word and
   scan as a list rather than as debris. */
.ibx-sugs { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.ibx-sug {
  display: block; width: 100%; text-align: left;
  font: inherit; font-size: 12.5px; line-height: 1.45;
  padding: 9px 12px; border-radius: 10px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg-2); color: var(--text-dim);
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.ibx-sug:hover {
  border-color: var(--accent); background: var(--accent-soft); color: var(--text);
}
/* Keyboard users get the same affordance as the mouse — these are real buttons. */
.ibx-sug:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ibx-sug:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) {
  .ibx-sug { transition: none; }
  .ibx-sug:active { transform: none; }
}

/* ---- Inbox approval policy ----------------------------------------------------------
   One switch at the foot of the Inbox rail, and a line saying what it means.
   ⚠️ The first version was a bare <label> and a raw system <select>, which read as an
   unfinished form field wedged into a sidebar. Two answers do not need a menu. */
.ibx-policy {
  padding: 11px 12px 13px; border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 6px;
}
.ibx-policy-head { display: flex; align-items: center; gap: 10px; }
.ibx-policy-ttl {
  flex: 1; min-width: 0; font-size: 11px; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-faint);
}
/* A real switch: 30x17, the size the rest of the app uses for a toggle. */
.ibx-switch { flex: none; display: inline-flex; cursor: pointer; }
.ibx-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.ibx-switch-track {
  width: 30px; height: 17px; border-radius: 999px; background: var(--bg-3, var(--bg-2));
  border: 1px solid var(--border); display: inline-flex; align-items: center;
  padding: 0 2px; transition: background .16s ease, border-color .16s ease;
}
.ibx-switch-dot {
  width: 11px; height: 11px; border-radius: 50%; background: var(--text-faint);
  transition: transform .16s ease, background .16s ease;
}
.ibx-switch input:checked + .ibx-switch-track {
  background: color-mix(in srgb, var(--accent) 30%, transparent);
  border-color: var(--accent);
}
.ibx-switch input:checked + .ibx-switch-track .ibx-switch-dot {
  transform: translateX(13px); background: var(--accent);
}
.ibx-switch input:focus-visible + .ibx-switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The note carries the safety rule, so it is never the thing that gets truncated. */
.ibx-policy-note { font-size: 11px; line-height: 1.5; color: var(--text-faint); }
/* The half people misread — that sending still asks — is the half that gets the colour. */
.ibx-policy-em { color: var(--text-dim); }

/* ---- Connections: one list, connected first --------------------------------------
   The page was a connected grid plus a second "add a connection on its own" list in a
   disclosure — the plugin shop the skills-only-catalog decision removed, smuggled back
   under another name. Two lists of the same kind of thing on one page is what made it
   read as two places. One list solves it, and these two rules are all it needs. */

/* Where the groups change. A heading rather than a rule, because "Not connected" is the
   thing a reader is looking for; a bare line makes them work out what changed. */
.cat-sep {
  /* ⚠️ `.cat-list` is an auto-fill GRID, so without this the heading is placed as a
     CELL — it sits in the first column and the cards flow around it. The rule two
     blocks up already does this for the empty/loading sentences, for the same reason. */
  grid-column: 1 / -1;
  margin: 18px 2px 6px; font-size: 10.5px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint);
}
.cat-sep:first-child { margin-top: 0; }

/* An unconnected row is a real option, not a disabled one — it is one click from being
   set up. So it recedes rather than greys out: full contrast on hover, and the name
   stays legible, because the whole point of listing it is that you can read what it
   would unlock. */
.cat-card.cat-off { opacity: .72; }
.cat-card.cat-off:hover { opacity: 1; }

/* ═══════════════════════════════════════════════════════════════════════════════════
   THE RAIL SHELL — opt-in via `ui_shell: "rail"` (storage.DEFAULT_CONFIG)
   -----------------------------------------------------------------------------------
   Four views are hubs whose own rail is the FIRST of four columns, and each hides the
   app sidebar to make room (`body.cn-fullbleed`). So the left edge of the product moves
   as you navigate, and there is no persistent place to stand.

   This mode keeps the sidebar and makes the hub's rail the SECOND column. The width is
   paid for by the AI panel, which becomes a corner bubble.

   ⚠️ It is a LAYOUT MODE over the same DOM. No id moves, no renderer changes, every
   handler stays bound — which is what makes it switchable back on a live seat, and what
   keeps the whole feature to one CSS block plus a class toggle. Anything here that
   needed a renderer to change belongs in the renderer, not in this block.
   ═══════════════════════════════════════════════════════════════════════════════════ */

/* 1 · the sidebar stays. `cn-fullbleed` is never set in this mode, but the rule is
       repeated defensively: if any path sets it, the sidebar must still win. */
body.shell-rail.cn-fullbleed #sidebar { display: flex; }

/* 2 · each hub drops its AI column — that is what pays for the sidebar. The remaining
       tracks are unchanged, so the list and reading panes keep their proportions.
   ⚠️ Each is scoped to the range where the hub STILL HAS an AI track. Unscoped,
       these four out-specify every breakpoint below them (a class selector plus `body`
       beats a bare class inside a media query), so a narrow window would keep three wide
       columns beside a visible sidebar and overflow. Below its own breakpoint each hub
       has already dropped the AI column by itself, which is exactly the layout this mode
       wants — so the right move is to stand down, not to restate it. And below 920px the
       app sidebar goes off-canvas anyway, which is where classic collapses the rail to
       icons; this mode must not fight that either.
       The rail width is `var(--rail-w)` everywhere, never a literal: it is the same
       measure as `#sidebar`, and a rail that is 16px different on one view reads as the
       app having shifted under you (the note at the Inbox breakpoints says so). */
@media (min-width: 1121px) {
  body.shell-rail .ibx-hub { grid-template-columns: auto 280px minmax(0, 1fr); }
  body.shell-rail .tsk-hub { grid-template-columns: auto 330px minmax(0, 1fr); }
}
@media (min-width: 1041px) {
  body.shell-rail .gc-hub { grid-template-columns: auto minmax(0, 1fr); }
}
@media (min-width: 1181px) {
  body.shell-rail .cal-hub { grid-template-columns: auto minmax(0, 1fr); }
}

/* 3 · the rail's Back button. It exists to return from a full-bleed hub to the app; with
       the sidebar right beside it there is nothing to go back to. Hidden, not removed —
       `mountRailChrome` still builds it and anything reading it still finds it. */
body.shell-rail .rail-top .cn-icon-btn,
body.shell-rail #ibx-back,
body.shell-rail #gc-back { display: none; }

/* 4 - the AI panel. The floating bubble and the floating card it opened used to live
       here; both are replaced by a docked rail and a panel that takes room, defined at
       the END of this file. Two answers to one question is worse than either, and the
       earlier one is the answer a reader finds first. */


/* --- the nav | panel | main shell (second pass) ------------------------------------
   The mode's real shape. `#app-body` was already `nav | main`; the panel is simply the
   column in the middle, so no offset is restated anywhere and every existing width keeps
   working. ⚠️ The panel is a real flex child, never a fixed overlay: an overlay would
   need every view's left edge re-stated, and would be wrong the moment one scrolls. */
:root { --nav-w: 196px; --panel-w: 236px; }

/* 6 - the nav is slimmer than the classic sidebar, because it is now only a nav: the
       lists that used to live in it (Recents, Active peers) are panels of their own. */
body.shell-rail #sidebar { width: var(--nav-w); }
body.shell-rail .shell-nav-off { display: none !important; }

/* 7 - the panel. Folding is a width transition to 0, so the width comes back to the view
       beside it rather than the panel merely turning invisible. */
#shell-panel {
  width: var(--panel-w); flex-shrink: 0; min-width: 0;
  background: var(--bg-1); border-right: 1px solid var(--border);
  display: none; flex-direction: column; overflow: hidden;
  transition: width .22s cubic-bezier(.4, 0, .2, 1), opacity .16s;
}
body.shell-rail #shell-panel { display: flex; }
body.shell-rail #shell-panel.shell-panel-off {
  width: 0; opacity: 0; border-right-color: transparent;
}
body.shell-rail.shell-panel-shut #shell-panel {
  width: 0; opacity: 0; border-right-color: transparent;
}
.shell-panel-head { display: flex; align-items: center; gap: 8px; padding: 13px 12px 8px; }
.shell-panel-head h2 { margin: 0; font-size: 13px; font-weight: 650; color: var(--text); }
.shell-panel-collapse {
  margin-left: auto; width: 25px; height: 25px; border-radius: 7px; border: 0;
  background: transparent; color: var(--text-faint); cursor: pointer;
  display: grid; place-items: center;
}
.shell-panel-collapse svg { width: 15px; height: 15px; }
.shell-panel-collapse:hover { background: var(--bg-hover); color: var(--text); }
.shell-panel-body { padding: 0 8px 10px; overflow-y: auto; }
.shell-panel-grp {
  font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-faint); padding: 12px 8px 5px;
}
.shell-panel-row {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 8px 9px; border: 0; border-radius: 9px; background: transparent;
  color: var(--text-dim); font-size: 13px; cursor: pointer;
}
.shell-panel-row:hover { background: var(--bg-hover); color: var(--text); }
.shell-panel-row.on { background: var(--accent-soft); color: var(--accent-2); font-weight: 600; }

/* 8 - Recents is the Chat panel. It keeps its own markup and its own renderer; only the
       section head it carried inside the sidebar is redundant here, because the panel has
       a title of its own. Shown only when the panel is serving Chat. */
body.shell-rail #shell-panel .side-recents { display: none; flex: 1; min-height: 0; }
body.shell-rail #shell-panel[data-section="chat"] .side-recents { display: flex; flex-direction: column; }
body.shell-rail #shell-panel[data-section="chat"] .shell-panel-body { display: none; }
/* 38 - "Recents" stays, and the magnifier goes to the right of it. Hiding the label left
        a bare search icon floating over the list with nothing to say what the list WAS -
        the panel's own title is "Chat", which names the section, not the thing under it.
        ⚠️ The label keeps the nav's small-uppercase proportions: it is a marker over a
        list you skim, not a heading you read. */
body.shell-rail #shell-panel .side-recents .side-section-title {
  font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-faint); padding: 0;
}
body.shell-rail #shell-panel .side-recents .side-sec-head {
  display: flex; align-items: center; gap: 8px; padding: 6px 12px 6px 10px;
}
body.shell-rail #shell-panel .side-recents .recents-search-btn { margin-left: auto; }
body.shell-rail #shell-panel .side-recents .side-sec-head { padding: 0 8px 4px; }
body.shell-rail #shell-panel .side-recents .side-sec-body { overflow-y: auto; padding: 0 8px 10px; }

/* 9 - a hub's own rail IS the panel there, so the same fold applies to its grid column.
       ⚠️ Restated per hub because each has its own track widths; a shared rule would have
       to know all four. */




body.shell-rail.shell-panel-shut .ibx-rail,
body.shell-rail.shell-panel-shut .tsk-rail,
body.shell-rail.shell-panel-shut .cal-rail,
body.shell-rail.shell-panel-shut .gc-rail,
body.shell-rail.shell-panel-shut .cn-rail { overflow: hidden; opacity: 0; }

/* 10 - the hub rails carried their own account row and Back button, because in the
        classic shell the rail STANDS IN for the sidebar. Here the sidebar is right beside
        them, so both are duplicates - two account rows on one screen. ⚠️ Hidden rather
        than not-built: `mountRailChrome` still mounts them, so anything reading them
        still finds them, and switching back to classic needs no rebuild. */
body.shell-rail .rail-foot { display: none; }
body.shell-rail .rail-top { display: none; }

/* 11 - the re-homed items. Each keeps the handler it was born with; only where it sits
        changes, so it has to read like a row of its new host rather than of the nav. */
body.shell-rail .shell-rehomed.side-item { width: calc(100% - 16px); margin: 0 8px; }
body.shell-rail #connections-section.shell-rehomed { margin-top: auto; padding: 6px 0 4px; border-top: 1px solid var(--border); }


/* --- the prototype's chrome, ported ------------------------------------------------
   The palette needed nothing: the prototype was built FROM this token block, so all 20
   of its colours are already these. What differed was the chrome's own measurements, and
   one of them carried most of the "this doesn't look like the mock" feeling - the active
   nav item, which was a grey `--bg-3` block here and the indigo `--accent-soft` there.
   ⚠️ Values, not new tokens. A second palette scoped to a mode is how a theme rots:
   the light theme redefines the tokens and would never reach a hard-coded copy. */

/* 12 - nav items: slightly tighter, a size down, and dim until they matter. */
body.shell-rail .side-item {
  gap: 10px; padding: 8px 10px; border-radius: 9px;
  font-size: 13.5px; font-weight: 500; color: var(--text-dim);
}
body.shell-rail .side-item:hover { background: var(--bg-hover); color: var(--text); }
body.shell-rail .side-item.active {
  background: var(--accent-soft); color: var(--text); font-weight: 600;
}
body.shell-rail .side-item .si-ico svg { width: 17px; height: 17px; }

/* 13 - group labels read as labels, not as items: smaller, wider tracked, more air above
        so the eye groups what follows rather than reading one long list.
        ⚠️ The caret stays - these fold, which the prototype's static mock had no need
        to show, and a control that silently stopped working would be the worse trade. */
body.shell-rail .side-section-title {
  font-size: 10px; font-weight: 700; letter-spacing: .09em;
  padding: 12px 8px 5px; color: var(--text-faint);
}

/* 14 - the count, and the one animation the prototype had that this file did not: it
        POPS when it changes, so a number arriving is something you see rather than
        something you notice later. */
@keyframes pop { 0% { transform: scale(1); } 45% { transform: scale(1.28); } 100% { transform: scale(1); } }
body.shell-rail .nav-badge {
  min-width: 17px; height: 17px; padding: 0 5px; border-radius: 9px;
  display: grid; place-items: center; line-height: 1;
  animation: pop .28s cubic-bezier(.3, 1.6, .5, 1);
}
@media (prefers-reduced-motion: reduce) { body.shell-rail .nav-badge { animation: none; } }

/* 15 - the account row. The avatar is the accent here rather than its own red gradient:
        one accent per screen, and the red read as an alert on a row that is not one. */
body.shell-rail .account { padding: 9px 10px; margin-top: 6px; gap: 9px; }
body.shell-rail .account:hover { background: var(--bg-hover); }
body.shell-rail .avatar-mini {
  width: 30px; height: 30px; background: var(--accent);
  font-size: 11.5px; font-weight: 700;
}
body.shell-rail #account-name { font-size: 13px; font-weight: 600; }
body.shell-rail #account-sub { font-size: 11.5px; color: var(--text-faint); }
body.shell-rail .account-caret { color: var(--text-faint); font-size: 13px; }


/* 16 - the page frame. A bounded reading column, CENTRED in whatever room is left.

        ⚠️ REVERSED 2026-08-23, at the owner's request, and the reason is a monitor. This
        ran LEFT-ALIGNED against the panel — the prototype's alignment, on the argument
        that it was most of what remained of the difference between the two shells. That
        holds at 1400px, where the column nearly fills the space. At 2580 it does not:
        Memory drew 1090px of content against the left edge with more than a screen's
        width of empty black beside it, and the page read as broken rather than roomy.

        The max-width still stays, for the reason it always did — dropping it would run
        body text the full width of the monitor, which is what the column exists to
        prevent. So the measure is unchanged and only the alignment moved. */
body.shell-rail .lib-wrap { margin: 0 auto; max-width: 1180px; }
body.shell-rail .lib-wrap.lib-wide { max-width: 1320px; }
body.shell-rail .lib-body { padding: 22px 26px; }


/* 17 - the slide. The panel's own width transition only covers the two sections that use
        `#shell-panel`; on a hub the panel IS the hub's rail, which is a GRID COLUMN, so
        the same movement has to be a transition on grid-template-columns.
        ⚠️ Animating a grid's tracks needs the from-value and the to-value both stated
        in the same units - going from `var(--rail-w)` to `0` works, going to `none` or
        `auto` snaps. That is why the shut rules restate every track rather than zeroing
        one. */
body.shell-rail .ibx-hub, body.shell-rail .tsk-hub,
body.shell-rail .cal-hub, body.shell-rail .gc-hub {
  transition: grid-template-columns .22s cubic-bezier(.4, 0, .2, 1);
}
body.shell-rail .ibx-rail, body.shell-rail .tsk-rail,
body.shell-rail .cal-rail, body.shell-rail .gc-rail, body.shell-rail .cn-rail {
  transition: opacity .16s ease;
}
@media (prefers-reduced-motion: reduce) {
  body.shell-rail #shell-panel,
  body.shell-rail .ibx-hub, body.shell-rail .tsk-hub,
  body.shell-rail .cal-hub, body.shell-rail .gc-hub { transition: none; }
}


/* 18 - the group labels. In the mock they are plain words - no control on them at all.
        These fold, though, and that was asked for, so the caret is kept and revealed on
        hover/focus instead of removed: at rest the nav reads exactly like the reference,
        and the control is still there for the person who wants it.
        ⚠️ Focus-visible is in the selector on purpose - hover-only would make the fold
        unreachable from the keyboard, which is worse than the caret being visible. */
body.shell-rail .side-section-title .sec-caret {
  opacity: 0; transition: opacity .14s ease;
}
body.shell-rail .side-section-title:hover .sec-caret,
body.shell-rail .side-section-title:focus-visible .sec-caret,
body.shell-rail .side-section-title[aria-expanded="false"] .sec-caret { opacity: .8; }
@media (prefers-reduced-motion: reduce) {
  body.shell-rail .side-section-title .sec-caret { transition: none; }
}

/* 19 - the panel's rows carry a count on the right, like the reference. */
.shell-panel-row .spr-n {
  margin-left: auto; font-size: 11.5px; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.shell-panel-row.on .spr-n { color: var(--accent-2); }


/* 20 - the Inbox, as the reference draws it. The ROWS already matched almost exactly
        (`.inbox-row.active` was already accent-soft with an accent left edge); what
        differed is the frame. Here the list and the reading pane run edge to edge as two
        bare panes; there they sit inside ONE rounded bordered card with air around it,
        which is most of why the reference reads as a page and this reads as a console.

        ⚠️ Built by rounding the OUTER corners of the two existing columns rather than
        by adding a wrapper element: a wrapper would be a DOM change to a hub, which is
        the one thing this mode does not do. Tasks gets the same treatment, because the
        two hubs share a grid and having only one of them framed would read as a bug. */
body.shell-rail .ibx-hub, body.shell-rail .tsk-hub { padding: 16px 16px 16px 0; gap: 0; }
body.shell-rail .ibx-listcol, body.shell-rail .tsk-listcol {
  border: 1px solid var(--border); border-right: 0;
  border-radius: 12px 0 0 12px; background: var(--bg-1);
}
body.shell-rail .ibx-read, body.shell-rail .tsk-read {
  border: 1px solid var(--border); border-radius: 0 12px 12px 0;
}
/* The rail is OUTSIDE the card - it is the panel, not part of the split. */
body.shell-rail .ibx-rail, body.shell-rail .tsk-rail { margin: -16px 16px -16px 0; }

/* 21 - the list head. The reference puts the search and the count on one quiet line above
        the rows, inside the card, rather than floating them over the pane. */
/* (ibx-listHEAD, not ibx-list-head: the name was guessed the first time and styled
   nothing, the same slip as .lib-page. Checked against index.html now.) */
body.shell-rail .ibx-listhead,
body.shell-rail .tsk-listhead {
  border-bottom: 1px solid var(--border); border-radius: 12px 0 0 0;
}


/* 22 - CONTAINMENT. A layout mode must not be able to widen the document; if it can, the
        page grows a horizontal scrollbar and everything appears shoved sideways, which
        looks like the shell is broken rather than like one child overflowing.

        ⚠️ Recents is the specific reason. Inside the sidebar its width was contained
        by `.side-scroll { overflow-x: hidden }`, and `.side-scroll .side-recents
        .conversations` set `overflow: visible`. Moving it into the panel leaves BOTH
        behind: the descendant rule stops matching, and the clip that made it safe is
        gone. Every long conversation title is then free to size the list.

        So the panel restates the clip, is capped at its own width so no content can
        stretch it, and the row is told to fill rather than to size itself. */
body.shell-rail #app-body { overflow-x: hidden; }
body.shell-rail #shell-panel { max-width: var(--panel-w); overflow-x: hidden; }
body.shell-rail #shell-panel .side-recents,
body.shell-rail #shell-panel .side-sec-body,
body.shell-rail #shell-panel .conversations { min-width: 0; max-width: 100%; overflow-x: hidden; }
body.shell-rail #shell-panel .conversations > * {
  min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}


/* 23 - the Chat panel's New chat button, and the list under it. In the reference the
        button is the first thing in the panel, full width, outlined rather than filled:
        it is the common action, not the loud one. */
/* New chat is an ACTION, not a card: accent-coloured text on the panel's top row,
   weighted by colour rather than by a box around it. A full-width outlined button read as
   the panel's SUBJECT rather than as something you do to it. */
/* A `+` in the head, the same control the Tasks rail puts in the same corner. */
.shell-new-chat {
  display: grid; place-items: center;
  width: 25px; height: 25px; margin: 0; padding: 0;
  background: transparent; border: 0; border-radius: 7px;
  color: var(--text-faint); font-size: 17px; font-weight: 500; cursor: pointer;
}
.shell-new-chat:hover { background: var(--bg-hover); color: var(--text); }
.shell-new-chat span { line-height: 1; }
body.shell-rail #shell-panel:not([data-section="chat"]) .shell-new-chat { display: none; }

/* The re-homed rows read as panel rows, not as nav items - they are in a different
   container now and the nav's own measurements do not belong to them. */
body.shell-rail #shell-panel .conversations > * {
  border-radius: 9px; padding: 7px 10px; font-size: 13px;
}
body.shell-rail #shell-panel .side-sec-head { padding: 10px 12px 4px; }

/* 24 - Photometrics and Mind maps, now rows of the Company rail. ⚠️ They keep the
        handler they were born with; only where they sit changed, so they have to read
        like rows of their new host rather than of the nav they left. */
body.shell-rail .cn-rail-list .shell-rehomed {
  width: auto; margin: 0 8px; border-radius: 9px;
  padding: 8px 10px; font-size: 13px; color: var(--text-dim);
}
body.shell-rail .cn-rail-list .shell-rehomed:hover { background: var(--bg-hover); color: var(--text); }
body.shell-rail .cn-rail-list .shell-rehomed.active { background: var(--accent-soft); color: var(--text); }


/* 25 - the hub slide, done properly. ⚠️ `grid-template-columns` is barely
        interpolatable: browsers will not smoothly animate between two track LISTS, so the
        rail simply snapped from its width to zero and the Inbox, Tasks, Calendar and
        Messages had no animation at all while the Marketplace and Chat panel slid.

        The fix is to animate a plain `width` on the RAIL, which every browser does
        smoothly, and let the grid track follow it by making that track `auto`. The track
        then measures its content, and its content is an element whose width is
        transitioning — so the column moves because the rail does, rather than the grid
        being asked to interpolate a list.

        `overflow: hidden` and `max-width` are not decoration here: an `auto` track sizes
        to CONTENT, so without them a long conversation title or a wide chip could make the
        rail wider than the rail is supposed to be. */
body.shell-rail .ibx-rail, body.shell-rail .tsk-rail, body.shell-rail .cal-rail,
body.shell-rail .gc-rail, body.shell-rail .cn-rail {
  width: var(--rail-w); max-width: var(--rail-w); overflow: hidden;
  transition: width .22s cubic-bezier(.4, 0, .2, 1), opacity .16s ease;
}
/* (hub rails no longer collapse: the fold belongs to #shell-panel only) */
@media (prefers-reduced-motion: reduce) {
  body.shell-rail .ibx-rail, body.shell-rail .tsk-rail, body.shell-rail .cal-rail,
  body.shell-rail .gc-rail, body.shell-rail .cn-rail { transition: none; }
}


/* 26 - the hub rails slide IN, like the Marketplace panel does.
        ⚠️ They needed a different mechanism for a real reason, not a stylistic one.
        `#shell-panel` is one element that persists across sections, so it can TRANSITION
        from width 0 to its width. A hub's rail does not arrive from anywhere - it is part
        of a view that was `display: none` a moment ago, and there is no previous width to
        transition FROM.

        So it is an animation rather than a transition. A element going from `display:
        none` to visible restarts its animations, which is exactly the moment we want it
        to play, and it costs no JavaScript and no state: showing the view IS the trigger.

        Same curve and duration as the panel, so the two read as one behaviour. */
@keyframes railIn {
  from { width: 0; opacity: 0; }
  to   { width: var(--rail-w); opacity: 1; }
}
body.shell-rail .ibx-rail, body.shell-rail .tsk-rail, body.shell-rail .cal-rail,
body.shell-rail .gc-rail, body.shell-rail .cn-rail {
  animation: railIn .22s cubic-bezier(.4, 0, .2, 1);
}
@media (prefers-reduced-motion: reduce) {
  body.shell-rail .ibx-rail, body.shell-rail .tsk-rail, body.shell-rail .cal-rail,
  body.shell-rail .gc-rail, body.shell-rail .cn-rail { animation: none; }
}


/* 27 - the nav is 196px here, down from the sidebar's 264. "New AI Chat" plus its
        `Ctrl K` badge does not fit in that, so the button grew WIDER THAN THE NAV -
        and clicking it focused an element that overflows its scroll container, which
        the browser answers by scrolling it into view. That scroll is the "right push":
        not a layout bug at all, but the nav shifting to show something too big for it.

        ⚠️ So the fix is containment, not a width tweak. The badge goes (the reference
        nav has no keyboard hint, and the shortcut still works), every item is told it may
        not exceed its column, and the nav itself refuses to scroll sideways so no future
        item can do this again. */
body.shell-rail .side-item-kbd { display: none; }
body.shell-rail #sidebar, body.shell-rail .side-scroll { overflow-x: hidden; }
body.shell-rail .side-item, body.shell-rail .side-section-title {
  min-width: 0; max-width: 100%; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
}


/* 28 - the panel follows the TASKS RAIL, which is the template every second sidebar now
        uses: a leading collapse chevron, then the title; sentence-case group headings
        rather than the nav's tiny uppercase labels (they head a list you read, not a
        section you scan past); and rows with the icon of the thing they open.

        ⚠️ Group headings deliberately do NOT match `.side-section-title`. The nav's
        labels are wayfinding you skim; a panel's are headings over a short list, and the
        Tasks rail has always drawn them larger and in sentence case. Matching the nav here
        would make the panel read as a second nav. */
.shell-panel-head { gap: 6px; padding: 14px 12px 8px; }
.shell-panel-head h2 { font-size: 14px; font-weight: 700; }
.shell-panel-collapse { margin-left: 0; order: -1; }
.shell-panel-grp {
  font-size: 12.5px; font-weight: 700; letter-spacing: 0; text-transform: none;
  color: var(--text); padding: 12px 10px 6px;
}
.shell-panel-row { gap: 10px; padding: 8px 10px; }
.shell-panel-row .spr-ico { display: grid; place-items: center; width: 17px; flex-shrink: 0; }
.shell-panel-row .spr-ico svg { width: 17px; height: 17px; }
.shell-panel-row.on { background: var(--accent-soft); color: var(--text); font-weight: 600; }


/* 29 - the shell must never scroll sideways, and nothing in it may be wider than its
        column. ⚠️ `shellDiag()` proved there is no document overflow at all - the page
        is exactly the width of the window - so every containment rule above this was
        aimed at the wrong thing. What actually happens is that a child wider than its box
        gets FOCUSED (clicking a chat focuses the composer, measured at 262 inside 250),
        and the browser reveals it by scrolling every scrollable ancestor. The shifts
        accumulate up the tree, which is why #app, #sidebar and the top bar of the day
        were all displaced by the same 112px while the document was never too wide.

        Two halves: the shell refuses to be a scroll container at all, so there is nothing
        for the browser to scroll; and the children that provoked it are told they may not
        exceed their column. */
body.shell-rail #app, body.shell-rail #app-body,
body.shell-rail #main { overflow-x: clip; }
body.shell-rail #app, body.shell-rail #app-body { width: 100%; max-width: 100%; }
body.shell-rail #main { min-width: 0; }
body.shell-rail #main .composer, body.shell-rail #main .msg-pane,
body.shell-rail #main .msg-attachments, body.shell-rail #main .attach-chip {
  min-width: 0; max-width: 100%;
}
body.shell-rail #main .msg-attachments { flex-wrap: wrap; }

/* ==========================================================================
   A page with a list beside it — Photometrics, Mind maps
   ==========================================================================
   ⚠️ APPENDED LAST, DELIBERATELY. These elements carry `page-body` as well as
   `split-body`, both single-class selectors, so specificity TIES and document order
   decides. Written where it reads best — up beside the other layout rules — the
   `padding: 0` lost to `.page-body { padding: 22px 32px 44px }` 3800 lines below it, and
   the list column drew inset in its own box with a gap either side instead of flush
   against the sidebar. It looked exactly like the bug it was meant to fix, and the test
   passed throughout because it asserted the rule EXISTED rather than that it won.
   Same convention the rail standard and the 2026-07 redesign layer use: last wins. */
/* A page with a list beside it. NOT the four-column hub Tasks and the Inbox use: the
   app sidebar STAYS, because these are pages you arrive at from it and leave again —
   replacing it would make a photometric file feel like somewhere you go to work for an
   hour. Two columns, list first, content filling the rest. */
/* ⚠️ It carries `page-body` too, which brings `padding: 22px 32px 44px` and its own
   `overflow-y: auto`. Both had to go: the padding pushed the list column inward so it
   read as a floating card with a gap on either side rather than a column, and the outer
   scroll meant the whole page scrolled as one instead of each column scrolling itself.
   The padding moves INTO the content column, which is where it was doing its job. */
.split-body {
  display: grid; grid-template-columns: 260px minmax(0, 1fr);
  min-height: 0; flex: 1; padding: 0;
  /* Both axes named. The shorthand alone does override the earlier
     `.page-body { overflow-y: auto }`, but only by being a shorthand — and a
     rule whose correctness depends on knowing that is a rule the next person
     reads wrong. */
  overflow: hidden; overflow-y: hidden;
}
/* ⚠️ `split-SIDE`, not `split-rail`. In this codebase a "rail" is the thing that
   REPLACES the app sidebar (`body.cn-fullbleed`), and `test_rail_standard` finds them by
   that suffix to hold the three of them to one shape. This sits BESIDE the sidebar and
   is a list column — the same role as the Inbox's, at the same width. */
.split-list { flex: 1; min-height: 0; overflow-y: auto; padding: 0 8px 12px; }
.split-main { min-width: 0; overflow-y: auto; padding: 22px 32px 44px; }
/* The inner page already spaced itself for a full-width page; inside a column the
   content column's own padding is doing that job. */
.split-main .lib-body { padding-top: 0; }
/* ⚠️ NOT `max-width: none`. It was, so a photometric report ran the full width of the
   column while Memory and Files sat in a measured one — the same content-width question
   answered two ways in one product. It takes the shared measure and centres with it. */
.split-main .lib-wrap { margin-inline: auto; }
/* Sits where the drop zone was, so the eye finds it in the place it last looked. */
.phot-swap { margin-bottom: 14px; }
/* The row. Two lines — what it is, and when — because a list of eight files all called
   something.ies is a list of eight identical rows. */
.split-row {
  display: block; width: 100%; text-align: left; padding: 8px 10px; border: 0;
  border-radius: 9px; background: none; color: var(--text-dim); font: inherit; cursor: pointer;
}
.split-row:hover { background: var(--bg-3); color: var(--text); }
.split-row.on { background: var(--accent-soft); color: var(--accent); }
/* ⚠️ Only what the shared row rule does NOT cover. These carry a second line — a file
   name means little without a date when eight of them start "VASARI-PRO-" — and the nav
   rows they otherwise match are single-line. */
.split-row { display: block; text-align: left; }
.split-row-name {
  font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.split-row-sub { font-size: 11.5px; color: var(--text-faint); margin-top: 1px; }
.split-row.on .split-row-sub { color: inherit; opacity: .8; }
.split-empty { padding: 14px 12px; color: var(--text-faint); font-size: 12px; line-height: 1.5; }
/* Below the split's own breakpoint the list would leave the content too narrow to read
   a polar plot in, so it goes above instead of away — the same rule the Inbox panel
   follows: it MOVES, it does not vanish. */
@media (max-width: 1000px) {
  .split-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .split-side { border-right: 0; border-bottom: 1px solid var(--border); max-height: 190px; }
}


/* ⚠️ `shell-panel-shut` is a STATE class on <body> ("the panel is folded"). It must
   never also be a component class: a bare `.shell-panel-shut { width: 25px; display: grid }`
   then matches <body> the moment the panel folds, and the whole application collapses into
   a 25px grid box - which is exactly what happened, and read as overflow, as scroll, and as
   every other thing it was not. State classes and component classes share one namespace.
   The chevron is `.shell-panel-collapse`. */


/* 30 - the empty chat, as a greeting rather than a logo floating over some buttons. The
        mark stays OURS - the animated one - because it is the product's own and a still
        logo is a different logo. Everything around it is what changed: a question, one
        line saying what this thing can do, and the suggestions in a proper grid. */
.empty-state { max-width: 900px; }
.empty-title {
  margin: 18px 0 6px; font-size: 30px; font-weight: 650; letter-spacing: -.02em;
  color: var(--text); text-wrap: balance;
}
.empty-lede {
  margin: 0 auto 26px; max-width: 620px; font-size: 15px; line-height: 1.6;
  color: var(--text-dim); text-wrap: balance;
}
.empty-hero-logo { width: 104px; height: 104px; }
.empty-cards { max-width: 900px; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
/* ⚠️ Below the three-up breakpoint the grid falls back to what it always did, rather
   than squeezing three columns no reader can use. */
@media (max-width: 900px) {
  .empty-cards { grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); }
}


/* 31 - the suggestion cards, laid out as the reference has them: the icon and the title
        on one row, the description on its own line beneath, spanning the full card. Ours
        put the icon left of a stacked title+description, which reads as a list row rather
        than a card.

        ⚠️ Done with `display: contents` on `.empty-card-body` so its two children join
        the CARD's grid directly. The alternative was re-nesting the markup, and the markup
        is shared with `fillEmptyCards` and its data — a layout change should not make the
        renderer describe the layout. */
.empty-card {
  /* `align-content: start` because the description is clamped to one line: the card is a
     fixed shape now, so the row cannot go ragged. Folded into THIS rule rather than
     appended as a second `.empty-card` — two rules for one component is how a look drifts,
     and it is what kept breaking the test that reads the effective one. */
  display: grid; align-items: center; align-content: start;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "ico title" "desc desc";
  column-gap: 11px; row-gap: 9px;
  padding: 15px 16px 16px; border-radius: 12px;
}
.empty-card-ico { grid-area: ico; }
.empty-card-body { display: contents; }
.empty-card-title { grid-area: title; font-size: 14.5px; font-weight: 650; }
.empty-card-desc {
  grid-area: desc; font-size: 13px; line-height: 1.5; color: var(--text-dim);
  white-space: normal;
}

/* 32 - the conversation list. The reference gives each thread real room: a line you can
        read at a glance and a row you can hit without aiming. ⚠️ One line, ellipsised —
        wrapping to two would make the list a wall of text and destroy the scan. */
body.shell-rail #shell-panel .conversations > * {
  padding: 11px 10px; font-size: 14px; line-height: 1.35;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.shell-rail #shell-panel .side-recents .side-sec-body { padding: 0 10px 12px; }


/* 33 - the composer STAYS AT THE BOTTOM, and the greeting sits in the middle of what is
        left. `chat-empty` floats the composer up under the logo (the Kimi-style new-chat
        treatment); that makes the one control you always reach for move depending on
        whether the conversation is empty, so it is somewhere different the moment you send
        your first message.

        ⚠️ The centring is done by the MESSAGE AREA centring its own content, not by
        margins on the greeting: `#messages` is the box that flexes, so it is the box that
        should decide where the thing inside it sits. Margins would fight the scroll the
        moment there is a conversation to scroll. */
body.shell-rail .chat-main.chat-empty { justify-content: flex-start; }
body.shell-rail .chat-main.chat-empty #messages {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 20px 0;
  display: flex; flex-direction: column; justify-content: center;
}
body.shell-rail .chat-main.chat-empty #empty-state { margin: 0 auto; }
body.shell-rail .chat-main.chat-empty #empty-cards { margin: 0 auto; }
body.shell-rail .chat-main.chat-empty #composer {
  border-top: 1px solid var(--border); padding-top: 12px; margin-top: 0;
}

/* 34 - the model pill above the conversation goes. ⚠️ Hidden, not removed:
        `updateModelBadge()` still writes to #model-badge on every config change and on
        first paint, and deleting the element would make that a silent null dereference.
        The composer's own Model and Thinking pickers are the live controls and are
        untouched - this was a read-only echo of them, one line above. */
body.shell-rail #chat-header { display: none; }


/* 35 - the suggestions belong ABOVE the composer, with the greeting. `#empty-cards` is a
        sibling that sits AFTER `#composer` in the markup, so it rendered below the thing
        you type in - the suggestions were behind the control they are meant to fill.

        ⚠️ Reordered with flex `order`, not by moving the element: `fillEmptyCards`
        writes into #empty-cards where it is, and the classic shell reads the same markup.
        A DOM move would be a change to both for the sake of one.

        The greeting and the cards are centred as a PAIR: the message area grows and packs
        its content to the BOTTOM, the cards grow and pack to the TOP, so the two meet in
        the middle of whatever room is left. Centring each on its own would leave a gap
        between them that changes size with the window. */
body.shell-rail .chat-main.chat-empty #messages { order: 1; justify-content: flex-end; padding-bottom: 18px; }
body.shell-rail .chat-main.chat-empty #empty-cards {
  order: 2; flex: 1 1 auto; align-content: flex-start; min-height: 0; overflow-y: auto;
}
body.shell-rail .chat-main.chat-empty #participants-bar { order: 3; }
body.shell-rail .chat-main.chat-empty #composer { order: 4; }


/* 36 - New chat is an ACTION, not a card. In the reference it is accent-coloured text on
        the panel's top row beside the history control - the thing you reach for, weighted
        by colour rather than by a box around it. Ours was a full-width outlined button,
        which reads as the panel's subject rather than as something you do to it. */

/* It shares the head's row: the title moves aside, the chevron stays leading. */
/* ⚠️ The title STAYS on the chat panel: every other section shows one, and the
   panels share a template. Hiding it here made Chat the odd one out. */
body.shell-rail #shell-panel[data-section="chat"] .shell-new-chat { margin-left: auto; }

/* 37 - the conversation rows. The one you are reading is the accent, the rest are quiet;
        the group labels are small uppercase markers you skim past rather than headings you
        read. ⚠️ These are the NAV's label proportions, not the panel heading's: a date
        marker is wayfinding, which is exactly what .side-section-title is for. */
body.shell-rail #shell-panel .conv-title { font-size: 14px; }
body.shell-rail #shell-panel .conversations > *.active,
body.shell-rail #shell-panel .conversations > *.is-active {
  background: var(--accent-soft); color: var(--accent-2); border-radius: 10px;
}
body.shell-rail #shell-panel .conversations > *.active .conv-title,
body.shell-rail #shell-panel .conversations > *.is-active .conv-title {
  color: var(--accent-2); font-weight: 600;
}
body.shell-rail #shell-panel .conv-daygroup {
  font-size: 10.5px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-faint); padding: 14px 10px 5px;
}

/* The finished setup steps, behind their own disclosure. Four ticks were the largest
   block on the page, above the one step still to do. */
.home-setup-done { margin-top: 2px; }
.home-setup-done > summary {
  cursor: pointer; list-style: none; font-size: 12px; color: var(--text-faint);
  padding: 4px 0 4px 26px; user-select: none;
}
.home-setup-done > summary::-webkit-details-marker { display: none; }
.home-setup-done > summary::before { content: "▸ "; }
.home-setup-done[open] > summary::before { content: "▾ "; }
.home-setup-done > summary:hover { color: var(--text-dim); }


/* ==========================================================================
   Your AI: a docked rail, a panel that takes room, and a sidebar that yields it
   ==========================================================================
   It was a round button floating over the bottom-right corner that opened a 400x620
   card floating beside it. Two things hovering over the page, covering whatever was
   under them, belonging to nothing.

   Now: a thin rail welded to the right edge, always there on a view that has an AI to
   show. Press it and the panel takes ROOM — `#app` gives up its right side and the
   shell reflows, the same mechanism the file preview uses, so the two behave alike
   rather than being two ideas about drawers in one product.

   And the left sidebar collapses to icons at the same moment, which is the point: the
   middle column is what you are working in, and it should not be the thing that pays
   for opening the panel. 264px of labels you already know become 60px of icons.

   ⚠️ Scoped by ID or by `body.` state on purpose, and appended last — the two ways a
   rule survives. Three bugs this session came from rules that read correctly and lost
   the cascade on position. */
:root { --ai-rail-w: 52px; --ai-panel-w: min(420px, 34vw); --sidebar-mini-w: 60px; }

/* 1 - the rail. Welded to the edge, full height under the top bar. */
.ai-rail { display: none; }
body.shell-rail .ai-rail {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 61;
  width: var(--ai-rail-w); padding: 12px 0;
  background: var(--bg-2); border-left: 1px solid var(--border);
}
/* A view with no AI to show has no rail — and gives its width back with it. */
body.shell-rail .ai-rail.hidden { display: none; }
.ai-rail-btn {
  display: grid; place-items: center; width: 38px; height: 38px; flex: none;
  border: 0; border-radius: 11px; background: none; cursor: pointer;
  color: var(--text-dim); transition: background .14s ease, color .14s ease;
}
.ai-rail-btn:hover { background: var(--bg-3); color: var(--text); }
body.ai-open .ai-rail-btn { background: var(--accent-soft); color: var(--accent); }
.ai-rail-btn .gc-ai-logo { width: 26px; height: 26px; }
.ai-rail-btn .gc-ai-logo svg { width: 24px; height: 24px; }

/* 2 - the panel, docked against the rail rather than floating near it. */
body.shell-rail #inbox-ai,
body.shell-rail #tasks-ai,
body.shell-rail #gc-ai,
body.shell-rail #cal-ai,
body.shell-rail #mailv-ai {
  position: fixed; top: 0; right: var(--ai-rail-w); bottom: 0;
  z-index: 60; width: var(--ai-panel-w); max-width: none; height: auto;
  background: var(--bg-1); border-left: 1px solid var(--border);
  border-radius: 0; box-shadow: none;
  display: none; flex-direction: column; overflow: hidden;
  animation: aiPanelIn .22s cubic-bezier(.4, 0, .2, 1);
}
body.shell-rail.ai-open #inbox-ai,
body.shell-rail.ai-open #tasks-ai,
body.shell-rail.ai-open #gc-ai,
body.shell-rail.ai-open #cal-ai,
body.shell-rail.ai-open #mailv-ai { display: flex; }
@keyframes aiPanelIn { from { transform: translateX(100%); } to { transform: none; } }

/* 3 - the room. The rail always costs its own width; the panel costs its width too, and
       only while it is open.
       ⚠️ On `#app-body`, NOT `#app`. The top bar is `#app`'s first child, so padding
       there narrowed the nav as well — the brand and the mode switch shuffled left every
       time the panel opened, and the rail appeared to sit BESIDE the bar rather than
       under it. The top bar is fixed chrome: it spans the window and nothing pushes it.
       `#app-body` is the row below it (sidebar | content), which is what should yield. */
body.shell-rail #app-body { transition: padding-right .22s cubic-bezier(.4, 0, .2, 1); }
body.shell-rail.has-ai-rail #app-body { padding-right: var(--ai-rail-w); }
body.shell-rail.has-ai-rail.ai-open #app-body {
  padding-right: calc(var(--ai-rail-w) + var(--ai-panel-w));
}

/* 4 - and the sidebar yields. ⚠️ `font-size: 0` rather than hiding a label element:
       the nav labels are BARE TEXT NODES between the icon span and the badge span, so
       there is nothing to select. The icon puts its own size back. */
body.shell-rail #sidebar { transition: width .22s cubic-bezier(.4, 0, .2, 1); }
body.shell-rail.ai-open #sidebar { width: var(--sidebar-mini-w); }
body.shell-rail.ai-open #sidebar .side-item,
body.shell-rail.ai-open #sidebar .side-section-title,
body.shell-rail.ai-open #sidebar .account { font-size: 0; }
/* ⚠️ `gap: 0`, and it is the whole bug. A nav label is a BARE TEXT NODE between the icon
   span and the badge span (the comment above says so, which is why `font-size: 0` is used
   to hide it) — and a contiguous text run inside a flex container becomes an ANONYMOUS
   FLEX ITEM. `font-size: 0` makes that item zero-WIDTH; it does not remove it, so the
   12px gap in front of it is still laid out. `justify-content: center` then centres
   `[icon][12px][0px]`, which puts the icon 6px LEFT of the rail's centre on every row.
   Nothing looks broken — it just looks slightly wrong, in the same direction, forever. */
body.shell-rail.ai-open #sidebar .side-item {
  justify-content: center; padding-inline: 0; gap: 0;
}
body.shell-rail.ai-open #sidebar .si-ico { font-size: 15px; margin: 0; }
body.shell-rail.ai-open #sidebar .side-section-title { padding-block: 6px; }
body.shell-rail.ai-open #sidebar .sec-caret,
body.shell-rail.ai-open #sidebar .side-item-kbd,
body.shell-rail.ai-open #sidebar .account-text,
body.shell-rail.ai-open #sidebar .account-caret,
body.shell-rail.ai-open #sidebar .side-recents { display: none; }
/* A badge on a 60px rail cannot sit after a label that is not there. */
body.shell-rail.ai-open #sidebar .nav-badge {
  position: absolute; top: 3px; right: 6px; font-size: 9px; padding: 0 4px;
}
body.shell-rail.ai-open #sidebar .side-item { position: relative; }
/* The account row is the same shape — avatar + a text span — with its own 10px gap. */
body.shell-rail.ai-open #sidebar .account {
  justify-content: center; padding-inline: 0; gap: 0;
}
body.shell-rail.ai-open #sidebar .avatar-mini { font-size: 11px; }

@media (prefers-reduced-motion: reduce) {
  body.shell-rail #app-body, body.shell-rail #sidebar { transition: none; }
  body.shell-rail #inbox-ai, body.shell-rail #tasks-ai,
  body.shell-rail #gc-ai, body.shell-rail #cal-ai { animation: none; }
}
/* Below ~1200px there is no middle column left to protect, so the panel covers rather
   than squeezes and the sidebar keeps its labels. */
@media (max-width: 1200px) {
  :root { --ai-panel-w: min(380px, 100vw - var(--ai-rail-w)); }
  body.shell-rail.has-ai-rail.ai-open #app-body { padding-right: var(--ai-rail-w); }
  body.shell-rail.ai-open #sidebar { width: var(--nav-w); }
  body.shell-rail.ai-open #sidebar .side-item,
  body.shell-rail.ai-open #sidebar .side-section-title,
  body.shell-rail.ai-open #sidebar .account { font-size: inherit; }
}


/* 39 - one line of description, ellipsised. These are prompts to click, not documentation:
        the longest of them ran six lines and made one card three times the height of its
        neighbours, which broke the grid into a ragged pile and buried the short ones.

        ⚠️ `-webkit-line-clamp` needs all four of its companions to work at all, and it
        is still the only way to clamp a wrapped line — `white-space: nowrap` would clip
        mid-word without the ellipsis landing where the text actually ends. */
.empty-card-desc {
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
  overflow: hidden; text-overflow: ellipsis;
}

/* The category tile at card size: a line icon carrying its hue, not a filled block. */
.empty-card-ico .ico-tile, .empty-card-ico.ico-tile { width: 30px; height: 30px; border-radius: 9px; }
.empty-card-ico svg.ico { width: 17px; height: 17px; }

/* ---- Company email (Company → People & roles) ------------------------------
   Every colour is a token, so the card follows the theme without a second
   definition — the whole point of the token block at the top of this file. */
#cn-mail-pane { margin-top: 18px; }
.cn-mail-new { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 12px 0 4px; }
.cn-mail-new .cn-input { width: auto; min-width: 160px; }
.cn-mail-at { color: var(--text-dim); }
.cn-mail-domain { color: var(--text-dim); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.cn-mail-list { margin-top: 14px; display: flex; flex-direction: column; gap: 6px; }
.cn-mail-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg);
}
.cn-mail-addr { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.cn-mail-quota { color: var(--text-dim); font-size: 12px; }
.cn-mail-acts { margin-left: auto; display: flex; gap: 6px; flex-wrap: wrap; }

/* The password panel. Deliberately loud: this is the ONLY time the value exists
   anywhere, so it must not read as an ordinary confirmation that can be skimmed
   past and dismissed. */
.cn-mail-secret {
  border: 1px solid var(--accent); background: var(--accent-soft);
  border-radius: var(--radius); padding: 14px 16px; margin: 12px 0 16px;
}
.cn-mail-secret-h { font-weight: 600; margin-bottom: 6px; }
.cn-mail-secret p { margin: 6px 0; }
.cn-mail-pw { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0; }
.cn-mail-pw code {
  font-size: 15px; letter-spacing: .5px; padding: 8px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg); user-select: all; word-break: break-all;
}
/* 40 - a category heading inside each half of the Connections list. Quieter than the
        connected / not-connected divider above it, because it is the SECOND level: the
        first answers "does this work?", which is the page's job, and this one answers
        "what kind of thing is it?".
        ⚠️ `.cat-list` is an auto-fill grid, so a heading without `grid-column: 1 / -1`
        is placed as a CELL — it lands in the first column and cards flow around it. The
        divider already does this; a second heading needs it just as much. */
.cat-sep-sub {
  grid-column: 1 / -1;
  font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--text-faint); padding: 14px 2px 2px; border: 0;
}

/* ---- Channels ----------------------------------------------------------------------
   The inbound section. Reads as a set-up sheet rather than a shop: there is one thing to
   do here, in three steps, and the card says what happens afterwards. */
/* ⚠️ The cards TILE, they do not stack down a 640px column. A channel is set up once
   and then left alone, so the page is mostly a list of what exists — and a list of two
   things down the left of a wide screen reads as a page that failed to load the rest. */
/* ⚠️ NOT `align-items: start`. Grid stretches its items by default, which is what
   makes cards in a row share a height; starting them aligns their tops and leaves the
   shorter one floating in a taller row, which reads as a rendering fault rather than as
   one card having less to say. */
.chan-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; }
.chan-card { display: flex; flex-direction: column; }
.chan-card {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px 16px; margin: 0;
}
/* Closed by default: everything below the head is setup for a thing you touch once. */
.chan-body { display: none; }
.chan-card.is-open .chan-body { display: block; }
.chan-card:not(.is-off) .chan-head:hover .chan-id strong { color: var(--accent-2); }
.chan-card.is-open .chan-head { margin-bottom: 12px; }
.chan-card:not(.is-open) .chan-head { padding-bottom: 0; border-bottom: 0; }
/* The head gets a rule under it: the card holds a set-up procedure, and a procedure
   reads better under a title than continuous with it. */
.chan-head { padding-bottom: 12px; border-bottom: 1px solid var(--border); }
.chan-id strong { font-size: 14.5px; font-weight: 650; }
.chan-why { margin-top: 12px; }
.chan-card.is-off { opacity: .62; }
.chan-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
/* A tinted tile, the same object every catalogue row on this product uses, rather than
   an emoji — which renders as a different picture per operating system and sits on a
   different baseline than the text beside it. */
.chan-ico { width: 36px; height: 36px; border-radius: 10px; flex-shrink: 0; }
.chan-ico svg { width: 20px; height: 20px; }
.chan-id { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.chan-id small { color: var(--text-faint); font-size: 11.5px; }
.chan-state {
  font-size: 11px; font-weight: 650; padding: 3px 9px; border-radius: 999px;
  background: var(--bg-3, var(--bg-2)); color: var(--text-faint); border: 1px solid var(--border);
}
.chan-state.on { background: var(--ok-soft); color: var(--ok); border-color: transparent; }
.chan-why { font-size: 12.5px; line-height: 1.5; color: var(--text-dim); margin: 0 0 10px; }
.chan-steps { margin: 0 0 12px; padding-left: 20px; font-size: 12.5px; line-height: 1.7; color: var(--text-dim); }
.chan-steps code { background: var(--bg-1); padding: 1px 5px; border-radius: 5px; }
.chan-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.chan-input {
  flex: 1; min-width: 220px; background: var(--bg-1); color: var(--text);
  border: 1px solid var(--border); border-radius: 9px; padding: 7px 10px; font-size: 12.5px;
}
.chan-code code { background: var(--bg-1); padding: 3px 8px; border-radius: 6px; font-size: 13px; }
.chan-code small { display: block; color: var(--text-faint); font-size: 11px; margin-top: 3px; }
.chan-links { border-top: 1px solid var(--border); padding-top: 10px; }
.chan-links-h { font-size: 11px; font-weight: 650; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 6px; }
.chan-link { display: flex; align-items: center; gap: 10px; font-size: 12.5px; padding: 4px 0; }
.chan-link small { flex: 1; color: var(--text-faint); font-size: 11px; }
.chan-unlink { background: none; border: 1px solid var(--border); border-radius: 7px; padding: 3px 9px; font-size: 11.5px; color: var(--text-dim); cursor: pointer; }
.chan-unlink:hover { border-color: var(--warn); color: var(--warn); }
.chan-none { font-size: 12.5px; color: var(--text-faint); }


/* 41 - the Heartbeat's two sections. ⚠️ The toggle means different things in each: on
        your own beats it turns the schedule on and off; on somebody else's it is INERT,
        because they set the schedule and the most you can do is run it once yourself.
        Mixed into one grid, a row of switches implies you can work the lot, and the only
        way to find out which ones lie is to try them. */
.hb-sect { margin-bottom: 26px; }
.hb-sect:last-of-type { margin-bottom: 12px; }
.hb-sect .hb-grid { margin-top: 10px; }
/* A card somebody else answers for reads a shade quieter — it is on the page so you know
   it runs, not so you can operate it. */
.hb-sect + .hb-sect .hb-card { background: var(--bg-1); }

/* 3D preview. The canvas fills the drawer and the caption sits under it. */
.m3d-host { display: flex; flex-direction: column; height: 100%; min-height: 340px; gap: 8px; }
.m3d-canvas {
  flex: 1; min-height: 0; width: 100%; display: block; cursor: grab;
  border-radius: 12px; background:
    radial-gradient(120% 120% at 50% 20%, var(--bg-3), var(--bg));
}
.m3d-canvas:active { cursor: grabbing; }
.m3d-note { flex: none; color: var(--text-faint); font-size: 12px; font-variant-numeric: tabular-nums; }
/* A file we cannot draw says why, in the place the model would have been — not a blank
   panel that reads as a viewer that failed to start. */
.m3d-err { padding: 22px 18px; color: var(--text-dim); font-size: 13px; line-height: 1.55; }

/* ==========================================================================
   Company admin — Job roles · People & roles · Skills
   One pass over the three pages that assign access. They shared one defect:
   every long list was a bare column of checkboxes in a 220px box, clipped
   mid-row, with no search, no grouping and no count — so "what does this role
   have" could only be answered by scrolling all of it and remembering.
   Appended last on purpose: `.cn-role-opts` and `.cn-jobrole-*` are restyled
   here, and the originals sit ~2000 lines up.
   ========================================================================== */

/* ---- the shared tick-list (optPicker / wireOptPickers) ---- */
.opick { margin-top: 6px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg); overflow: hidden; }
.opick-bar { display: flex; align-items: center; gap: 8px; padding: 7px 9px;
  border-bottom: 1px solid var(--border); background: var(--bg-2); }
.opick-q { flex: 1 1 auto; min-width: 0; background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 7px; font: inherit; font-size: 12.5px;
  padding: 5px 9px; }
.opick-q:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.opick-count { flex: none; font-size: 11px; color: var(--text-faint); white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.opick-count.on { color: var(--accent); font-weight: 600; }
.opick-clear { flex: none; }
/* Taller than the old 220px, and with a bar above it, so it reads as a list you can
   scroll rather than one that has been cut off. */
.cn-role-opts.opick-body { max-height: 304px; gap: 1px; padding: 0 6px 6px; }
.opick-group + .opick-group { margin-top: 6px; }
.opick-gh { position: sticky; top: 0; z-index: 1; display: flex; align-items: baseline;
  gap: 6px; padding: 8px 5px 4px; background: var(--bg); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--text-faint); }
.opick-gh span { font-weight: 500; opacity: .65; }
.opick-none { margin: 0; padding: 16px 10px; text-align: center; font-size: 12px; color: var(--text-faint); }
/* A ticked row stays findable once the list has been scrolled past it. */
.opick .cn-role-opt:has(input:checked) { background: var(--accent-soft); }
.opick .cn-role-opt:has(input:checked) .cn-role-optname { color: var(--text); font-weight: 600; }

/* ---- Job roles: the card head, and copying as a secondary route ---- */
.cn-card-head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 10px; }
.cn-card-head-text { flex: 1; min-width: 0; }
.cn-card-head-text .cn-settings-desc { margin-bottom: 0; }
.cn-card-head > .btn-primary { flex: none; }
.cn-role-copyrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.cn-role-copylbl { font-size: 11.5px; color: var(--text-faint); }
.cn-role-copyrow .cn-role-copy-src { flex: 0 1 280px; min-width: 0; }

/* ---- Job roles: the editor form ---- */
.cn-role-formhead { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; }
.cn-role-formtitle { font-size: 13.5px; font-weight: 700; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cn-role-sec { margin-top: 22px; padding-bottom: 6px; border-bottom: 1px solid var(--border);
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-faint); }
.cn-role-form .cn-role-sec + .cn-corole-grid,
.cn-role-form .cn-role-sec + .cn-label { margin-top: 12px; }

/* ---- People & roles: the job-role list ---- */
.cn-jobrole-list { gap: 3px; max-height: 360px; overflow-y: auto; padding-right: 2px; }
.cn-jobrole-row { align-items: flex-start; gap: 10px; padding: 7px 9px; }
.cn-jobrole-row > input[type="checkbox"] { flex: none; margin-top: 2px; }
.cn-jobrole-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.cn-jobrole-text small { font-size: 11px; line-height: 1.35; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cn-jobrole-row.held { background: var(--accent-soft); }
.cn-jobrole-row.held .cn-jobrole-name { font-weight: 600; }
/* ⚠️ The primary control is reserved on every row but only SHOWN on a ticked one — it
   means nothing on a role the member does not hold, and twenty dead radios pinned to
   the far edge of a wide panel is what made this list unreadable. `visibility`, not
   `display`, so ticking a row does not reflow the twenty rows under it. */
.cn-jobrole-primary { visibility: hidden; flex: none; margin-left: auto; gap: 5px;
  padding: 2px 8px 2px 6px; border: 1px solid var(--border); border-radius: 20px;
  background: var(--bg); font-size: 10px; text-transform: uppercase; letter-spacing: .05em; }
.cn-jobrole-row.held .cn-jobrole-primary { visibility: visible; }
.cn-jobrole-primary:has(input:checked) { border-color: var(--accent); color: var(--accent);
  background: var(--bg-2); font-weight: 700; }
.cn-warn-full { color: var(--warn); }
.cn-warn-full strong { color: var(--warn); }

/* ---- Company → Skills: the author's recommendation, said once ---- */
.skl-suggest { padding: 5px 8px; gap: 8px; border-color: transparent;
  background: var(--accent-soft); }
.skl-suggest span { font-size: 11px; }
.skl-group-apply { flex: none; }
/* The name used to BE the flexible child of the row; it is now inside .cn-jobrole-text,
   a column, where `flex: 1` would stretch it vertically against the description. */
.cn-jobrole-text .cn-jobrole-name { flex: 0 0 auto; }


/* ==========================================================================
   PHONE (max-width 760px) - the five hub views stop pretending to be desktop panes
   ==========================================================================
   Tasks, the Inbox, Company, Messages and the Calendar are three- and
   four-COLUMN grids. Every breakpoint above this one narrows a column; not one
   of them ever drops the ARRANGEMENT, so at 390px the rail still took 58-264px,
   the list took another 240-265, and what was left for the thing you actually
   came to read was 70-125px - a detail pane printing one word per line beside a
   month grid clipped at "M T V T F S S".

   So this is the breakpoint where the columns become ROWS. It is deliberately
   one block, not five patches, because the fault was one fault: five views
   keeping a layout that needs 1100px on a screen that has 390.

   Three rules it lives by:
   - The hub SCROLLS as a page (Tasks / Inbox / Company). Their panes are
     documents; stacking them and letting the page scroll is the only shape that
     can never clip something unreachable. Messages and the Calendar stay grids
     because their main pane pins something to its own bottom edge (a composer)
     or scrolls a fixed month - those keep an internal scroller and give the rail
     a capped strip above (Messages) or below (the Calendar, where the month is
     what you came for and must come first).
   - A rail is a STRIP, not a column: capped, scrolling inside itself, so the
     content it navigates to is always within a screen of it.
   - Navigation comes back. `body.cn-fullbleed` hides the app sidebar AND its
     hamburger to make room for a hub's own rail - right at 1400px, and the
     reason these five views were the only ones on the phone with no menu button
     at all. Below this width the sidebar is off-canvas anyway, so it costs no
     room. */
@media (max-width: 760px) {

  /* ---- 1 - the hamburger, and one account row instead of two ---- */
  body.cn-fullbleed #sidebar { display: flex; }
  body.cn-fullbleed .sidebar-toggle { display: block; }
  /* The rail's account footer is a stand-in for the sidebar's, and the sidebar is
     back - two of them is what drew "Mobile Tester" over "Settings" over a stray
     circle-slash button in one 58px corner. The real one lives in the drawer. */
  .gc-rail .rail-foot, .cn-rail .rail-foot, .ibx-rail .rail-foot,
  .tsk-rail .rail-foot, .cal-rail .rail-foot { display: none; }

  /* ---- 2 - every rail is a full-width strip ---- */
  .tsk-rail, .ibx-rail, .cn-rail, .gc-rail {
    max-height: 34vh; overflow-y: auto;
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  /* ...and at full width it can say what it is again. The 920px rules squeezed
     these rails to 58px of icons, which is where the Inbox's approvals switch
     became "AI-o-V" with its caption one word per line. */
  /* ⚠️ `.tsk-rail .ibx-fold-label` at 920px is a two-class selector and a one-class
     rule here loses the cascade to it however far down the file it sits — which drew
     the Tasks rail as a column of unlabelled icons at FULL width. Match the shape of
     the rule being undone, not just its property. */
  .ibx-rail-title, .tsk-rail-title, .ibx-fold-label,
  .tsk-rail .ibx-fold-label { display: block; }
  .tsk-rail .ibx-fold-head { display: block; margin-top: 10px; }
  .ibx-rail-head, .tsk-rail-head { justify-content: flex-start; padding: 14px 12px 10px; }
  .ibx-fold, .tsk-rail .ibx-fold { justify-content: flex-start; padding: 7px 10px 7px 22px; }
  .ibx-fold-n { position: static; min-width: 18px; padding: 0 6px; font-size: 11px; }

  /* ---- 3 - Tasks / Inbox / Company: columns become one scrolling page ---- */
  .tsk-hub, .ibx-hub, .cn-hub {
    display: block; overflow-y: auto; -webkit-overflow-scrolling: touch;
  }
  .tsk-listcol, .ibx-listcol { border-right: 0; border-bottom: 1px solid var(--border); }
  .tsk-listhead, .ibx-listhead { flex: none; }
  .tsk-ai, .ibx-ai, .cn-detail {
    border-left: 0; border-top: 1px solid var(--border); max-height: none;
  }
  /* An emptied panel is a bordered box holding nothing. */
  .tsk-ai:empty, .ibx-ai:empty { display: none; }
  /* ⚠️ The panel is NOT hidden here, and that is deliberate rather than timid: it
     holds every verb the Inbox has — `tests/test_inbox_read_and_reach.py` says so by
     name — and stacked under the list it costs only what it draws. What goes is the
     132px animated mark in its RESTING state: decoration, sitting above the one
     sentence or the one button that is the whole of what the panel is offering. */
  .ibx-ai-empty { height: auto; gap: 10px; padding: 12px 0; }
  .ibx-ai-empty .ibx-ai-mark { display: none; }
  /* An empty state is 70px of padding above and below one sentence — right in a
     column that has a whole screen to fill, and 300px of nothing to scroll past when
     the same sentence is stacked under a list that has already said it. */
  .tsk-read:has(> .inbox-reading-empty),
  .ibx-read:has(> .inbox-reading-empty) { padding: 0; }
  .inbox-reading-empty { padding: 26px 18px; }
  .tsk-listcol .tasks-empty, .ibx-listcol .tasks-empty { padding: 26px 18px; }
  /* Something IS picked: cap the list so the thing you opened is half a screen
     below the row you tapped, not eighty rows below it. */
  .tsk-hub:not(:has(#tasks-reading > .inbox-reading-empty)) .tsk-listcol,
  .ibx-hub:not(:has(#inbox-reading > .inbox-reading-empty)) .ibx-listcol {
    max-height: 44vh;
  }
  /* …and the rail gives up most of its own height in the same moment: once you are
     reading something, the folder list is navigation you are not using. */
  .tsk-hub:not(:has(#tasks-reading > .inbox-reading-empty)) .tsk-rail,
  .ibx-hub:not(:has(#inbox-reading > .inbox-reading-empty)) .ibx-rail {
    max-height: 20vh;
  }
  .tsk-read, .ibx-read { padding: 18px 16px 28px; }

  /* ---- 4 - Company: its own inset, and the onboarding pair stacks ---- */
  .cn-main-head { padding: 22px 16px 0; }
  .cn-tabs { padding: 8px 16px 10px; }
  .cn-pane { padding: 16px 16px 30px; }
  .cn-onboarding { padding: 22px 4px; }
  .cn-onb-cards { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .cn-onb-card .btn-primary { min-width: 0; width: 100%; }

  /* ---- 5 - Messages: the list, then the conversation ---- */
  /* A grid, not a page scroll: the composer is pinned to the bottom of its own
     pane and a page-scrolling chat would push it off the end of the thread. */
  .gc-hub {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }
  .gc-ai { display: none; }
  .gc-rail { max-height: 32vh; }
  /* Nobody picked yet - there is no conversation to show, so the list gets the
     screen rather than sharing it with "Pick somebody to talk to." */
  .gc-hub:has(.gc-empty-main) { grid-template-rows: minmax(0, 1fr); }
  .gc-hub:has(.gc-empty-main) .gc-rail { max-height: none; border-bottom: 0; }
  .gc-hub:has(.gc-empty-main) .gc-main { display: none; }
  .gc-thread { padding: 14px 12px; }
  .gc-main-head { padding: 10px 12px; }

  /* ---- 6 - Calendar: the month first, the legend under it ---- */
  /* The one hub whose rail is not navigation - it is a legend and a note about
     what the month means, which is worth nothing above a month you cannot see. */
  .cal-hub {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto;
    grid-template-areas: "main" "rail";
  }
  #cal-ai { display: none; }
  .cal-main { grid-area: main; padding: 16px 14px 28px; }
  .cal-rail {
    grid-area: rail; max-height: 32vh; overflow-y: auto;
    border-right: 0; border-top: 1px solid var(--border);
  }
  .cal-mainhead { flex-wrap: wrap; gap: 8px; }

  /* ---- 7 - Photometrics / Mind maps: no strip that only repeats the title ---- */
  /* `.split-body` already stacks at 1000px, but the rule that caps the list
     names `.split-side` - a class neither view carries - so the list column kept
     its full auto height and drew ~100px of head above the page's own H1. */
  .split-body > .phot-rail, .split-body > .mm-rail {
    max-height: 180px; border-right: 0; border-bottom: 1px solid var(--border);
  }
  /* An EMPTY list is a strip with nothing in it but a second copy of the title
     and a sentence pointing "on the right" at a pane that is now below. */
  .split-body:has(.split-empty) { grid-template-rows: minmax(0, 1fr); }
  .split-body:has(.split-empty) > .phot-rail,
  .split-body:has(.split-empty) > .mm-rail { display: none; }
  .split-main { padding: 18px 16px 32px; }

  /* ---- 8 - filter chips: one scrolling row, and they look tappable ---- */
  /* Skills wrapped to four rows, Heartbeat to five, so the first card of any of
     them started below the fold. A chip row that scrolls sideways costs one row
     of height whatever is in it. */
  .lib-tabs {
    flex: 1 1 100%; flex-wrap: nowrap; gap: 6px; overflow-x: auto;
    padding-bottom: 4px; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .lib-tabs::-webkit-scrollbar { display: none; }
  .lib-tab {
    flex: 0 0 auto; padding: 0 13px;
    background: var(--bg-2); border: 1px solid var(--border);
  }
  .lib-tab.on { border-color: var(--accent); color: var(--text); }
}

/* ==========================================================================
   PHONE (max-width 620px) - the controls that were sized for a mouse
   ========================================================================== */
@media (max-width: 620px) {
  /* Home. The action row landed on two ragged rows of three different heights:
     a pill wrapped to two lines beside one that did not, and the primary CTA
     broken across "+ New" / "AI Chat". Nothing here wraps INSIDE itself. */
  .home-head { align-items: stretch; gap: 10px; padding: 12px 0 16px; }
  .home-quick { width: 100%; flex-wrap: wrap; gap: 8px; }
  .home-chips { margin-right: 0; }
  .home-chip, #home-new-chat, .home-quick .btn-mini { white-space: nowrap; }
  .home-chip { padding: 7px 12px; }
  /* "Getting started": the summary line was wrapping into the side of a button
     pinned to the same row. The button takes its own line instead. */
  .home-setup-head { flex-wrap: wrap; }
  .home-setup-head .btn-mini { flex: 0 1 100%; margin-left: 30px; }

  /* The composer. Both pickers wrapped to two lines each, which inflated the one
     control row used on every turn to ~90px and squeezed emoji/attach/send into
     what was left. A chip stays one line and truncates; the row stays one row. */
  #composer { padding: 10px 12px 12px; }
  .composer-controls, .composer-controls-right { gap: 6px; min-width: 0; }
  .drop-btn { white-space: nowrap; padding: 5px 9px; gap: 4px; }
  /* The two pickers are what gives way. Everything that ACTS — emoji, attach, send —
     keeps its size, or the row overflows and it is the SEND button, at the far end,
     that goes off the edge. */
  #model-drop, #effort-drop { flex: 0 1 auto; min-width: 0; }
  #model-drop .drop-btn, #effort-drop .drop-btn { max-width: 100%; }
  #model-btn-label, #effort-btn-label {
    flex: 0 1 auto; min-width: 0; max-width: 76px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  #emoji-btn, #attach-btn, #send, #stop { flex: none; }
  .drop-menu { max-width: calc(100vw - 28px); }

  /* Catalog rows. One line of summary is a design decision that holds at 370px
     and stops holding at 340 total: "Probation reviews, contract en..." does not
     tell two similar skills apart. Two lines here, and an Add you can hit. */
  .cat-sum {
    white-space: normal; display: -webkit-box; -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
  .cat-btn { min-height: 38px; padding: 8px 16px; }
}


/* ==========================================================================
   PHONE, round 2 - the overflow, the order, and the furniture
   ==========================================================================
   Round 1 stopped the five hub views pretending to be desktop panes. What it left
   behind was smaller and more specific: one grid still wider than the screen, one
   header rendering under the content it titles, and two hubs spending 300-450px on
   navigation before the first row of anything. Same house rule as that block - every
   layout change lives inside a media query, and nothing above this line moves. */
@media (max-width: 760px) {

  /* ---- 1 - Calendar: the header comes before the month ---- */
  /* `.cal-rail` is where the page's own title bar lives - the back chevron and the
     only "+" on the screen - and round 1 put that rail BELOW the month, correctly:
     the rest of it is a legend, worth nothing above a month you cannot see. So the
     rail stops being one box. `display: contents` dissolves it and its children become
     grid items in their own right, which is what lets the head take the top row and the
     legend the bottom one. There is no other way to place a GRANDCHILD in a grid area,
     and moving the markup would be the same fix with a merge conflict in it.
     WARNING: it costs the rail its own background and border, so the two pieces that are
     still drawn carry theirs. */
  .cal-hub {
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas: "head" "main" "rail";
  }
  .cal-rail { display: contents; }
  /* Empty (`railTopHtml()` returns ""), and an unassigned child would still claim an
     implicit row of its own above everything. */
  .cal-rail > .rail-top { display: none; }
  .cal-rail > .tsk-rail-head {
    grid-area: head; background: var(--bg-2);
    border-bottom: 1px solid var(--border);
  }
  .cal-rail > .cal-rail-list {
    grid-area: rail; max-height: 30vh; overflow-y: auto;
    background: var(--bg-2); border-top: 1px solid var(--border);
  }

  /* ---- 2 - Tasks / Inbox: the rail is a chip ROW, not a column ---- */
  /* A capped, scrolling COLUMN was still 287px of it - Views, three saved views,
     Projects, two projects - so the first task row began at y=450 on an 844px screen,
     with two thirds of the first screenful spent on navigation. The same rows laid out
     sideways cost ONE row of height whatever is in them, which is the shape the filter
     chips above the Skills catalogue already use, and it is the same trade: you swipe
     for the ones that do not fit instead of scrolling past all of them. */
  .tsk-rail, .ibx-rail { max-height: none; }
  .tsk-rail-list, .ibx-rail-list {
    flex: 0 0 auto; display: flex; flex-direction: row; flex-wrap: nowrap;
    align-items: center; gap: 6px; overflow-x: auto; overflow-y: hidden;
    padding: 9px 16px 11px; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .tsk-rail-list::-webkit-scrollbar, .ibx-rail-list::-webkit-scrollbar { display: none; }
  .tsk-rail-list > *, .ibx-rail-list > * { flex: 0 0 auto; }
  /* Same cue as the filter rows below: a chip sliced by the screen edge has to read as
     more-this-way rather than as a clipped layout. Spacer, then fade - see (5).
     A mask reads ALPHA only, so the opaque stop's colour never reaches a pixel - #fff is
     the literal `test_rail_shell` already allows, and any token would render identically. */
  .tsk-rail-list, .ibx-rail-list {
    -webkit-mask-image: linear-gradient(to right, #fff calc(100% - 26px), transparent);
    mask-image: linear-gradient(to right, #fff calc(100% - 26px), transparent);
  }
  .tsk-rail-list::after, .ibx-rail-list::after { content: ""; flex: 0 0 12px; }
  .tsk-rail .ibx-fold, .ibx-rail .ibx-fold {
    width: auto; gap: 7px; padding: 6px 12px; border-radius: 999px;
    background: var(--bg-2); border: 1px solid var(--border); white-space: nowrap;
  }
  .tsk-rail .ibx-fold.on, .ibx-rail .ibx-fold.on { border-color: var(--accent); }
  .tsk-rail .ibx-fold-label, .ibx-rail .ibx-fold-label { flex: 0 0 auto; overflow: visible; }
  /* Tasks groups its chips under plain labels ("Views", "Projects"); the Inbox's fold
     heads are BUTTONS - destinations - and stay chips like everything beside them. */
  .tsk-rail .ibx-fold-head {
    display: flex; align-items: center; margin: 0; padding: 0 2px 0 8px;
    font-size: 10.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
    color: var(--text-faint); white-space: nowrap;
  }
  .tsk-rail .ibx-fold-head:first-child { padding-left: 0; }
  /* "New project" and "Trash" join the row rather than sitting under a rule of their
     own - a horizontal divider between two chips on one line reads as a broken border. */
  .tsk-rail-foot { display: contents; }

  /* ---- 3 - one "nothing here" per screen, not three ---- */
  /* An empty list already says there is nothing; the reading pane behind it then said
     it again ("You are all caught up"), and stacked on a phone the two sit one under the
     other with a border between them. When there is nothing to PICK, the pane that
     exists to show what you picked has nothing to be. */
  .ibx-hub:has(#inbox-empty:not(.hidden)) .ibx-read,
  .tsk-hub:has(#tasks-empty:not(.hidden)) .tsk-read { display: none; }
  /* WARNING: `.ibx-ai` is deliberately NOT in that list: it holds every verb the Inbox
     has, and `tests/test_inbox_read_and_reach.py` says so by name. Its resting sentence
     is the one that got reworded instead. */

  /* ---- 4 - Company: the word "Company" three times in 280px ---- */
  /* Title bar, then an identity card repeating it, then the H1 repeating it again -
     which pushed the only two actions on the page (Create / Join) to y=368. The middle
     one is the one carrying no information: before you are in a company its name IS
     "Company". It goes only while the onboarding pane is up, because once you have
     joined one that card is the only place the company's real name is shown. */
  .cn-hub:has(#cn-onboarding:not(.hidden)) .cn-rail-head { display: none; }

  /* ---- 5 - a chip row that is cut off says so ---- */
  /* Skills, Connections and Heartbeat all end their filter row mid-word at the screen
     edge ("... People & HR 12 - Le"), which reads as a layout fault rather than an
     invitation to swipe. A fade is the cue; the `::after` spacer is what lets the last
     chip clear the fade at the end of the scroll - a trailing `padding-right` is not
     reliably counted in a flex scroller's extent, a flex item always is. */
  .lib-tabs {
    -webkit-mask-image: linear-gradient(to right, #fff calc(100% - 26px), transparent);
    mask-image: linear-gradient(to right, #fff calc(100% - 26px), transparent);
  }
  .lib-tabs::after { content: ""; flex: 0 0 26px; }
}

@media (max-width: 620px) {
  /* ---- 6 - a card grid whose column is wider than the screen ---- */
  /* `repeat(auto-fill, minmax(370px, 1fr))` reads as "at least 370" and means it: with
     326px to give, auto-fill still lays ONE 370px column, so every Heartbeat card ran
     44px past the right edge - no right border anywhere on screen, the on/off toggle
     and the "..." menu jammed against the physical edge. auto-fill cannot correct that;
     the minimum has to stop being a fixed pixel width.
     WARNING: every grid listed here ALREADY resolves to a single column at this width,
     so the only thing that changes is that the column now fits its container. */
  .hb-grid, .cat-list, .market-grid, .skl-cards,
  .home-cardlist, .cn-policy-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  /* ---- 7 - one gutter, not two ---- */
  /* Files, Skills, Memory, Channels, Connections and Heartbeat inset their content by
     32px; Photometrics, Mind maps and Company by 16 - so moving between views the whole
     page visibly jumped sideways by half a gutter. 16 is the one to keep: it is what the
     hub views already use, and on the narrowest screen there is, the 32 was buying white
     space out of the only column of content.
     WARNING: `padding-inline` only, so `.lib-body`'s own `padding-top: 20px` at this
     same breakpoint is not silently reset by a shorthand.
     WARNING: `:not(.split-body)`. Photometrics and Mind maps carry BOTH classes, and
     `.split-body { padding: 0 }` is what keeps their list column flush against the
     sidebar - a single-class selector, so a later single-class `.page-body` rule beats
     it on document order alone and quietly re-inset both pages to 32 (measured: it
     did, before this was scoped). That is the exact trap the comment sitting above
     `.split-body` itself was written about. */
  .page-body:not(.split-body) { padding-inline: 16px; }

  /* ---- 8 - the composer's two picker chips ---- */
  /* Round 1 capped both labels at 76px so the SEND button could never be pushed off the
     edge, which was right, and left them reading "Anthropi..." and "Thinking: n...".
     `effortButtonLabel` now drops the prefix at this width, so the effort chip needs far
     less and the model chip can have what it gives up. */
  #effort-btn-label { max-width: 88px; }
  #model-btn-label { max-width: 104px; }

  /* ---- 9 - a button should not narrow the sentence beside it ---- */
  /* Mind maps' "New map" sits inline with the title block, wrapping the page's one line
     of description to ~55% width while the rest of the screen is empty. */
  .lib-head-side { width: 100%; margin-left: 0; }
}


/* ==========================================================================
   PHONE, round 3 - the pane that describes a pane you cannot open
   ==========================================================================
   Round 1 stopped the hub views pretending to be desktop columns and round 2 took the
   furniture out of the top of them. What was left was the bottom: on Tasks and on the
   Inbox, two full-width bordered bands sat under the list saying "Pick a task to open
   it here." and "Open a task and I'll tell you what I'd do about it." - about a fifth
   of an 844px screen spent describing a pane that has nothing in it, above 250px of
   empty grey. Round 2 fixed their WORDING. This is their PRESENCE.

   The rule the whole block turns on: a pane is hidden only in its RESTING state, and
   the resting state is MARKED in the markup (`.inbox-reading-empty.idle`) rather than
   inferred from the class alone. `renderTaskReadEmpty` draws the same element for
   "couldn't open that task" and "that task isn't in this view any more" - real news,
   which must not be swallowed by a rule meant for "nothing is picked".

   Same house rules as the two blocks above: every layout change is inside a media
   query, nothing above this line moves, and nothing is reordered. */

/* The phone-only lead for the Photometrics drop zone. Declared here, outside the query,
   because the element is NEW - it has never rendered on a desktop and this is what keeps
   it that way. See (6). */
.phot-drop-tap { display: none; }

@media (max-width: 760px) {

  /* ---- 1 - Tasks / Inbox: nothing is picked, so there is nothing to show ---- */
  /* THE finding of this round. Both panes go, by two rules rather than one (see the
     second warning): a reading pane with nothing to read and a panel offering to advise
     you on nothing are the same emptiness twice. They come back the instant something is
     selected, because opening a task REPLACES `#tasks-reading`'s contents - the `.idle`
     sentence leaves the DOM, `:has()` stops matching, and every rule below stops with
     it. Verified by driving it: click a row, measure, screenshot.
     WARNING: `.ibx-ai` itself is never hidden, at any width, and that is a rule rather
     than a nicety - `tests/test_inbox_read_and_reach.py` greps this whole file for a
     rule that switches the panel off outright and fails on one, because the panel holds
     every verb the Inbox has and a stylesheet that hid it once left people able to read
     a card and do nothing about it. So what goes is what it draws while resting. Round 1
     took the 132px animated mark out of that state for the same reason and stopped
     there; this takes the rest of it, and the panel's own padding and border with it, so
     an empty column costs nothing without ever ceasing to exist. */
  .tsk-hub:has(#tasks-reading > .inbox-reading-empty.idle) .tsk-read,
  .ibx-hub:has(#inbox-reading > .inbox-reading-empty.idle) .ibx-read { display: none; }
  /* The PANEL is not hidden - only what it is DRAWING while it has nothing to say, and
     only while nothing is picked. `.ibx-ai-empty` is that resting state: the mark and
     one sentence, `ibxHeroHtml(text)` with `withButton` false. The same class also
     carries "Analyse using AI" and the tell box when a card IS open and has no analysis
     yet, which is why this is scoped to the hub having an idle reading pane rather than
     to the class. With no visible child, the panel's own box is all that would be left,
     so that goes with it and the column collapses to nothing while still being there. */
  .tsk-hub:has(#tasks-reading > .inbox-reading-empty.idle) .tsk-ai > .ibx-ai-empty,
  .ibx-hub:has(#inbox-reading > .inbox-reading-empty.idle) .ibx-ai > .ibx-ai-empty {
    display: none;
  }
  .tsk-hub:has(#tasks-reading > .inbox-reading-empty.idle) .tsk-ai,
  .ibx-hub:has(#inbox-reading > .inbox-reading-empty.idle) .ibx-ai {
    padding: 0; border-top: 0; min-height: 0;
  }

  /* ---- 2 - the Inbox names the folder you are standing in four times ---- */
  /* Measured at 390x844: 247px between the app bar and the first row of content, of
     which the list head was 66. It said "Needs you 0" under a chip already reading
     "Needs you" and carrying the same count as a badge, then "Decisions waiting on you.
     Nothing is ever sent until you approve it." under a policy band already reading
     "Nothing happens until you approve it.", above an empty state already reading
     "Nothing needs your decision right now."
     What is dropped is only ever a REPEAT. The two things the head alone can say -
     "Mark all read" and "Select all N" - keep it on screen when they exist; when they
     do not, the head has nothing left of its own and goes.
     WARNING: the price, written down. `.ibx-lh-n` is the folder's TOTAL while the
     chip's badge is what is pending or unread, so in "Done" the count is no longer
     shown anywhere. The list under it is the answer, and it is one screen away. */
  .ibx-listhead .ibx-lh-title,
  .ibx-listhead .ibx-lh-n,
  .ibx-listhead .ibx-lh-blurb { display: none; }
  .ibx-listhead:not(:has(.ibx-lh-read)):not(:has(.ibx-lh-sel)) { display: none; }
  .ibx-listhead { padding: 8px 14px; }
  /* ...and the chrome above it gives back the rest. A title bar sized for a mouse costs
     the same 24px of vertical padding on every one of the hubs. */
  .gc-rail-head, .ibx-rail-head, .tsk-rail-head, .cn-rail-nav-head,
  .phot-rail-head, .mm-rail-head { padding: 10px 12px 8px; }
  .tsk-rail-list, .ibx-rail-list { padding: 7px 16px 9px; }
  /* The approvals switch STAYS - it changes what this screen does, and hiding a safety
     control to save 50px is the wrong trade. It is only tightened. */
  .ibx-policy { padding: 8px 12px 9px; gap: 4px; }

  /* ---- 3 - Company: the word "Company" above the word "Company" ---- */
  /* The same judgement round 2 made about the identity card one row below, applied to
     the row above it: while you are not in a company its name literally IS "Company",
     so the title bar and the H1 are one word twice. Once you have joined one the H1
     becomes the company's real name and the bar's static label is a label again -
     which is why this is conditional and not a deletion. The chevron stays: it is the
     only "back to the app" on the page. */
  .cn-hub:has(#cn-onboarding:not(.hidden)) .cn-rail-nav-title { display: none; }

  /* ---- 4 - Connections: 134px of headings that read as a failed load ---- */
  /* Measured: the last connected card ends at y=733 and the first unconnected one
     starts at 867, with "Not connected" and "Business" floating in between. Nothing was
     broken - a 10.5px heading was carrying 18px of margin, and the second one 18px of
     margin PLUS 14px of padding, inside a 12px grid gap. Two headings that belong to
     each other are set as one block instead. */
  .cat-sep { margin: 12px 2px 4px; }
  .cat-sep-sub { padding-top: 6px; }
  .cat-sep + .cat-sep-sub { margin-top: 0; padding-top: 0; }

  /* ---- 5 - the chat's empty state, with a hole in the middle of it ---- */
  /* `#messages` and `#empty-cards` BOTH carried `margin-top: auto` inside a centred
     flex column, so the free space did not go above the greeting - it was split, half
     above it and half between the composer and the suggestion cards. Measured: 60px
     each. Dropping both auto margins gives `justify-content: center` its job back: the
     same 60px above the greeting, the cards docked under the composer they belong to,
     and the remainder below the whole group, where it reads as margin rather than as a
     gap between two things that go together. */
  .chat-main.chat-empty #messages,
  .chat-main.chat-empty #empty-cards { margin-top: 0; }

  /* ---- 6 - Photometrics leads with the one gesture a phone does not have ---- */
  /* "Drop photometric files here" was the bold primary instruction on a device with no
     drag and drop, and "choose files" - the only thing that works there - was the small
     link after the comma. The two sentences swap; nothing else changes. There is no
     second upload path: `.phot-drop-in` is `pointer-events: none`, so a tap anywhere in
     the dashed box already reaches `#phot-drop`'s own click handler, which opens the
     very same `#phot-file` input that `choose files` opens. */
  .phot-drop-drag { display: none; }
  .phot-drop-tap { display: block; }
  .phot-drop { padding-block: 22px; }
}


/* ==========================================================================
   PHONE, round 4 - the OTHER shell, the type scale, and the cards
   ==========================================================================
   Rounds 1-3 measured, screenshotted and fixed the CLASSIC shell with every
   sidebar shut. That is one of the two layouts this product ships, and it is
   the one that was already closest to right. `ui_shell: "rail"` is a second,
   opt-in shell - ~150 rules of its own - and nothing in three rounds ever
   rendered it at phone size. Measured at 390x844 it gives the content pane
   154px: `#shell-panel` is a real flex child of `#app-body`, 236px wide, and
   `#app-body` is a ROW at every width. 390 - 236 = 154, and at 360 it is 124.
   Chat, Marketplace, Connections, Channels and Heartbeat all render inside it.

   Three rules this block lives by, and they are the same three the earlier
   rounds used:

   - A view-level sidebar does not sit BESIDE the content on a phone. Round 2
     settled the shape for the Tasks and Inbox rails - the column becomes a
     horizontally scrolling row of chips above the content - and the panel
     follows it rather than inventing a second answer. The strip costs ONE row
     of height whatever is in it, and nothing is deleted: every destination the
     panel offered is still one tap away.
   - Nothing is HIDDEN to buy room if it is the only copy of something. The
     panel's own title stays (it is what names the row of chips), the search
     beside Recents stays, the AI is still reachable. What goes is a repeat.
   - Every layout and type change is inside a media query, nothing above this
     line moves, and no rule is reordered.

   WARNING: this block sits after the `THE RAIL SHELL` header, which
   `tests/test_rail_shell.py` reads to the END of the file when it checks that the
   mode hard-codes no colours. Tokens only, and the one literal that block already
   allows (`#fff`, which a mask reads as ALPHA - the colour never reaches a pixel).
   ========================================================================== */

@media (max-width: 760px) {

  /* ---- 1 - the rail shell's middle column becomes a strip above the content ---- */
  /* `#app-body` is `nav | panel | main`. Below 920px the nav is already
     `position: fixed` off-canvas, so its only IN-FLOW children here are the panel
     and the view - which is what makes turning the row into a column safe, and
     what makes it the whole fix rather than a pile of width overrides. */
  body.shell-rail #app-body { flex-direction: column; }

  body.shell-rail #shell-panel {
    width: auto; max-width: none; min-width: 0; flex: 0 0 auto;
    flex-direction: row; align-items: center;
    border-right: 0; border-bottom: 1px solid var(--border);
    /* The fold animated a WIDTH. There is no width to fold here. */
    transition: none;
  }
  /* A view with no panel section shows no strip. On a desktop it stays in the flow
     at width 0 so it can SLIDE; a strip has nothing to slide, and a zero-width flex
     item in a COLUMN still claims its full height. */
  body.shell-rail #shell-panel.shell-panel-off { display: none; }
  /* WARNING: the fold is a desktop gesture and it is neutralised here rather than
     honoured. `shellPanelShut` is toggled by clicking the nav item you are already
     on - which on a phone means opening the drawer, tapping, and watching the only
     navigation this view has disappear with no visible way to bring it back. The
     collapse chevron goes with it, so nothing on screen offers a control that would
     now do nothing. */
  body.shell-rail.shell-panel-shut #shell-panel {
    width: auto; opacity: 1; border-right-color: transparent;
  }

  /* The head leads the strip. WARNING: the title is NOT hidden - it is the only
     thing saying what this row of chips is, and `test_new_chat_is_the_SAME_control`
     says so by name. It is set as the small-caps marker the Tasks rail already puts
     in front of its own chips, so the two rows read as one pattern. */
  body.shell-rail #shell-panel .shell-panel-head {
    flex: 0 0 auto; gap: 6px; padding: 0 2px 0 14px;
  }
  body.shell-rail #shell-panel .shell-panel-head h2 {
    font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
    text-transform: uppercase; color: var(--text-faint); white-space: nowrap;
  }
  /* The group labels DO go. They split four rows into two, which is worth a line in
     a column and is not worth 120px of a 390px row - and the title beside them
     already says what the whole strip is.
     WARNING: grouped with the chevron, and the ORDER of the two selectors is load
     bearing. `test_the_panel_follows_the_TASKS_RAIL_template` finds the effective
     heading style by taking the LAST rule in this file whose selector ends in that
     group class, so a rule ending there - even one that only applies on a phone -
     is read as the template and fails. Naming the group first, with the chevron
     after it, leaves that literal unmatched. Round 3 hit the same trap from a
     COMMENT, so this one is deliberately worded around the string as well. */
  body.shell-rail #shell-panel .shell-panel-grp,
  body.shell-rail #shell-panel .shell-panel-collapse { display: none; }

  body.shell-rail #shell-panel .shell-panel-body {
    flex: 1 1 auto; min-width: 0;
    display: flex; flex-direction: row; align-items: center; gap: 6px;
    overflow-x: auto; overflow-y: hidden; padding: 9px 12px 9px 14px;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    /* Same cue as every other chip row on the phone: a chip sliced by the screen edge
       has to read as more-this-way rather than as a clipped layout.
       WARNING: this row fades at BOTH ends, unlike the ones round 2 added, because it
       has a LABEL to its left rather than the screen edge. Scrolled, a chip clipped
       dead against that label read as one word ("MARKETPLACEonnectors"). The left stop
       is the scroller's own padding, so at rest it falls on empty space and nothing
       looks faded until something is actually scrolled under it. */
    -webkit-mask-image: linear-gradient(to right, transparent 0, #fff 14px,
      #fff calc(100% - 26px), transparent);
    mask-image: linear-gradient(to right, transparent 0, #fff 14px,
      #fff calc(100% - 26px), transparent);
  }
  body.shell-rail #shell-panel .shell-panel-body::-webkit-scrollbar { display: none; }
  /* A trailing padding is not reliably counted in a flex scroller's extent; a flex
     item always is. This is what lets the last chip clear the fade. */
  body.shell-rail #shell-panel .shell-panel-body::after { content: ""; flex: 0 0 20px; }
  body.shell-rail #shell-panel .shell-panel-row {
    width: auto; flex: 0 0 auto; gap: 7px; padding: 6px 12px;
    border-radius: 999px; background: var(--bg-2);
    border: 1px solid var(--border); white-space: nowrap;
  }
  body.shell-rail #shell-panel .shell-panel-row.on { border-color: var(--accent); }

  /* ---- 2 - the Chat panel is the same strip, holding conversations ---- */
  /* `.side-recents` was LIFTED out of the sidebar by `shellRehome`, so on a phone it
     is the only place the recent chats are. Hiding the panel here would take them
     with it. It becomes a row of chips like every other panel. */
  body.shell-rail #shell-panel[data-section="chat"] .side-recents {
    flex: 1 1 auto; flex-direction: row; align-items: center;
    min-width: 0; min-height: 0;
  }
  /* The magnifier stays - it is the only chat search there is. The word "Recents"
     goes, because the strip's own title one chip to its left already says Chat and
     the things in the row are visibly conversations. */
  body.shell-rail #shell-panel[data-section="chat"] .side-recents .side-sec-head {
    flex: 0 0 auto; padding: 0 2px 0 4px;
  }
  body.shell-rail #shell-panel[data-section="chat"] .side-recents .side-section-title {
    display: none;
  }
  body.shell-rail #shell-panel[data-section="chat"] .side-recents .side-sec-body {
    flex: 1 1 auto; min-width: 0;
    display: flex; flex-direction: row; align-items: center; gap: 6px;
    overflow-x: auto; overflow-y: hidden; padding: 9px 12px 9px 10px;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #fff 10px,
      #fff calc(100% - 26px), transparent);
    mask-image: linear-gradient(to right, transparent 0, #fff 10px,
      #fff calc(100% - 26px), transparent);
  }
  body.shell-rail #shell-panel[data-section="chat"] .side-sec-body::-webkit-scrollbar { display: none; }
  body.shell-rail #shell-panel[data-section="chat"] .side-sec-body::after { content: ""; flex: 0 0 20px; }
  body.shell-rail #shell-panel[data-section="chat"] .recents-search {
    flex: 0 0 auto; width: 150px; margin: 0;
  }
  /* WARNING: `max-width: 100%` (rule 22, the containment pass) is right for a COLUMN
     inside a fixed-width panel and wrong for the content of a horizontal scroller:
     it caps the row at the visible width, and the chips past the edge become
     unreachable rather than scrollable. The clip that made the moved list safe lives
     on the scroller instead, which is where a scroller's clip belongs. */
  body.shell-rail #shell-panel[data-section="chat"] .conversations {
    display: flex; flex-direction: row; align-items: center; gap: 6px;
    width: max-content; max-width: none; overflow: visible;
  }
  body.shell-rail #shell-panel[data-section="chat"] .conversations > * {
    flex: 0 0 auto; width: auto; max-width: 46vw;
    padding: 6px 12px; border-radius: 999px;
    background: var(--bg-2); border: 1px solid var(--border);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* ---- 3 - a hub's own rail, in this shell, was still 264px wide ---- */
  /* Round 2 turned the Tasks and Inbox rails into chip rows, and in the rail shell
     they were laid out inside a box rule 25 pins to `var(--rail-w)` with
     `overflow: hidden` - so the row was clipped mid-chip at 264px on a 390px screen
     ("Team Bo"), and Company's and Messages' rails were 264px columns inside a
     full-width stacked hub.
     WARNING: `animation: none` is not tidying. `railIn` interpolates width from 0 to
     `var(--rail-w)`, so without it the rail plays a 220ms animation out to 264px on
     every arrival before snapping back to auto. */
  body.shell-rail .ibx-rail, body.shell-rail .tsk-rail, body.shell-rail .cal-rail,
  body.shell-rail .gc-rail, body.shell-rail .cn-rail {
    width: auto; max-width: none; animation: none; transition: none;
  }
  /* WARNING: ...and rule 25's `overflow: hidden` beats round 1's `overflow-y: auto`
     on these two, which are still COLUMNS here (a legend and a nav, not chips). A
     capped column that cannot scroll simply loses its last rows. */
  body.shell-rail .cn-rail, body.shell-rail .gc-rail {
    overflow-x: hidden; overflow-y: auto;
  }
  /* The desktop mode frames the list and reading panes as one rounded card with a
     16px inset around it. Stacked, the two are not beside each other and the
     rounding lands on the wrong corners, so it is a plain divider again. */
  body.shell-rail .ibx-hub, body.shell-rail .tsk-hub { padding: 0; }
  body.shell-rail .ibx-rail, body.shell-rail .tsk-rail { margin: 0; }
  body.shell-rail .ibx-listcol, body.shell-rail .tsk-listcol {
    border: 0; border-bottom: 1px solid var(--border); border-radius: 0;
  }
  body.shell-rail .ibx-read, body.shell-rail .tsk-read {
    border: 0; border-top: 1px solid var(--border); border-radius: 0;
  }
  body.shell-rail .ibx-listhead, body.shell-rail .tsk-listhead { border-radius: 0; }

  /* ---- 4 - the AI rail stops reserving a column ---- */
  /* On a desktop `--ai-rail-w` is 52px of permanent chrome welded to the right edge,
     and `#app-body` pays for it whether or not the panel is open. That is 13% of a
     390px screen spent on one button. Here the rail OVERLAYS as a short tab at the
     middle of the right edge - clear of the composer at the bottom of Messages, and
     clear of the head at the top of everything else.
     WARNING: done by re-declaring the TOKEN rather than by unpicking the four rules
     that read it. `#app-body`'s padding, the panel's `right`, the panel's own width
     formula and the rail's width are each stated once in terms of this, and "the
     rail costs no width" is exactly what `0px` says. */
  :root { --ai-rail-w: 0px; --ai-panel-w: min(340px, 100vw - 48px); }
  body.shell-rail .ai-rail {
    top: auto; bottom: 16px; right: 14px;
    width: auto; height: auto; padding: 4px;
    background: var(--bg-2); border: 1px solid var(--border);
    border-radius: 999px; box-shadow: var(--shadow-2);
  }
  /* WARNING: Messages is the one of the four that pins a composer to the bottom of
     its own pane, and the send button is in exactly this corner. It is the only
     place a floating control can land on top of another control rather than on top
     of a document that scrolls under it. */
  body.shell-rail:has(#view-groupchat:not(.hidden)) .ai-rail { bottom: 80px; }

  /* ...and a drawer you opened on purpose has to have something in it. Round 3 hides
     what the Tasks and Inbox AI panels DRAW while nothing is picked, which is right
     where the panel is STACKED under the list - it is a bordered box holding one
     sentence, and collapsing it gives the list the screen. In this shell the same
     panel is a fixed drawer that exists only once you press the rail, so the same
     rule made pressing it open a blank white sheet 340px wide.
     WARNING: the specificity has to be won, not assumed. Round 3's selector carries a
     `:has()` whose argument holds an ID, so it scores (1,5,0) and a plain
     `body.shell-rail.ai-open ...` descendant of it loses. Naming the panel by its own
     id AND matching the hub through the reading pane it owns is what puts this ahead.
     WARNING: this un-hides only the RESTING sentence, and only while the drawer is
     open. It is not the panel being hidden or shown - `.ibx-ai` is never hidden at any
     width, which `tests/test_inbox_read_and_reach.py` says by name. */
  /* WARNING: only the INBOX panel needs its inset back. `.tsk-ai` sets `padding: 0`
     itself and insets its children instead (`--tsk-pad`), so round 3's zero is what
     that panel already had - restoring 16px there would give the Tasks drawer a border
     of padding the desktop one does not have. Two panels, two answers, read from the
     base rules rather than assumed to match. */
  body.shell-rail.ai-open .ibx-hub:has(#inbox-reading) #inbox-ai {
    padding: 16px 14px 14px;
  }
  body.shell-rail.ai-open .tsk-hub:has(#tasks-reading) #tasks-ai > .ibx-ai-empty,
  body.shell-rail.ai-open .ibx-hub:has(#inbox-reading) #inbox-ai > .ibx-ai-empty {
    display: flex;
  }

  /* ---- 5 - the rail shell's page frame keeps the app's gutter ---- */
  /* `body.shell-rail .lib-body` is a two-class selector, so it beats round 2's
     `.page-body:not(.split-body)` on specificity and re-inset every library page in
     this shell. The two shells must not disagree about the gutter. */
  body.shell-rail .lib-body { padding: 20px 32px 36px; }
}

/* ==========================================================================
   PHONE, round 4 - the type scale
   ==========================================================================
   Asked for directly: content type should be smaller on a phone so more of it
   fits. Sizes in this stylesheet are pixels almost everywhere, so scaling the
   root font size reaches nothing - what a scale can be here is a small set of
   NAMED steps, declared once under the breakpoint and applied in ONE block
   rather than sprinkled through the file as per-element overrides.

   Six steps, and the sizes they replace:
     --fs-body        14.5    -> 13.5   running text, chat, list rows
     --fs-page-title  24-30   -> 20     the one H1 on a page
     --fs-page-sub    13-14.5 -> 12.5   the sentence under it
     --fs-card-title  14-15   -> 13.5   a card's name
     --fs-card-sub    12.5-13 -> 12     its one line of summary
     --fs-meta        11.5-12 -> 11     counts, stamps, footers

   WARNING: deliberately not smaller than this. 13.5px body copy is a step down,
   not a squint; most of the win on a phone comes from the 24-30px page titles,
   which were sized for a monitor and cost two lines of screen each. Nothing here
   goes below 11px, and a control keeps its HEIGHT - only its lettering comes
   down, so a smaller scale never makes something harder to hit.
   ========================================================================== */
@media (max-width: 760px) {
  :root {
    --fs-body: 13.5px;
    --fs-page-title: 20px;
    --fs-page-sub: 12.5px;
    --fs-card-title: 13.5px;
    --fs-card-sub: 12px;
    --fs-meta: 11px;
    --fs-control: 13px;
  }

  body { font-size: var(--fs-body); }

  /* One H1 per page, whatever the page calls it. */
  .lib-title, .page-head h1, .page-head-row h1, .empty-state h1,
  .cap-head-main h1, .home-head h1, .cn-main-head h1, .cn-admin-pagehead h1 {
    font-size: var(--fs-page-title);
  }
  /* ...and the sentence under it. */
  .lib-sub, .page-head > p, .page-head-row p, .empty-state p,
  .cn-main-head p, .cn-admin-pagehead p {
    font-size: var(--fs-page-sub);
  }

  /* A card's name, and its one line of summary. */
  .cat-name, .hb-name, .market-card-name, .chan-name { font-size: var(--fs-card-title); }
  .cat-sum, .hb-sub, .market-card-sum, .chan-sub { font-size: var(--fs-card-sub); }
  .cat-note, .hb-foot, .market-card-cat, .lib-tab-n, .fv-meta { font-size: var(--fs-meta); }

  .lib-tab, .lib-search, .lib-new { font-size: var(--fs-control); }

  /* The chat greeting was set for a page with nothing else on it. */
  .chat-main.chat-empty .empty-logo { font-size: 42px; }
}

/* ==========================================================================
   PHONE, round 4 - one column, one gutter, nothing wider than its card
   ==========================================================================
   Round 2 dropped the fixed `minmax(370px, ...)` minimum on six card grids at
   620px, which is what stopped the Heartbeat cards running past the right edge.
   Three grids of the same shape were missed - `auto-fill, minmax(320px, 1fr)`
   reads as "at least 320" and means it, so with 326px to give it still lays one
   320px column and the card cannot fit the gutters. Same failure, same fix. */
@media (max-width: 620px) {
  .chan-list, .mk-grid, .cn-onb-cards { grid-template-columns: minmax(0, 1fr); }

  /* A card's trailing controls belong INSIDE it. `.hb-top` already gives the name
     the flexible column and the switch and the menu a fixed one; what a narrow card
     needs is for the tile not to eat the name's share of the row. */
  .hb-card { padding: 13px 13px 11px; gap: 8px; }
  .hb-top { gap: 9px; }
  .hb-top-end { gap: 2px; }
  .hb-ico { --ico-sz: 30px; }
  .cat-ico { --ico-sz: 34px; width: 34px; height: 34px; font-size: 18px; }
  .cat-card { grid-template-columns: 34px minmax(0, 1fr) auto; }

  /* The library head. The search is declared 264px wide and `flex-shrink: 0`, so on
     the narrowest screens it is wider than the row it now has to itself. */
  .lib-search-wrap { min-width: 0; }
  .lib-head-tools { flex-wrap: wrap; }
}

/* ---- Phone: the panel strip needs more room to the LEFT of its first chip -------
   Round 4 gave this row a two-ended mask precisely so a chip scrolled under the
   "MARKETPLACE" marker would not read as one word with it. The stop was 14px, and
   the marker is ~180px of small-caps immediately beside it, so a sliced chip still
   surfaced a character clear of the fade: the strip opened on "MARKETPLACEonnectors".
   It shows up only once the row is SCROLLED, which it is on arrival — app.js brings
   the active chip into view, and on Heartbeat that is the last one.
   Widening the fade alone would eat the first chip at rest, so the scroller also
   carries the padding: the fade falls on empty space until something scrolls into it. */
@media (max-width: 760px) {
  body.shell-rail #shell-panel .shell-panel-body {
    padding-left: 10px;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #fff 26px,
      #fff calc(100% - 26px), transparent);
    mask-image: linear-gradient(to right, transparent 0, #fff 26px,
      #fff calc(100% - 26px), transparent);
  }
  /* A hairline between the marker and the scrolling chips, so the two read as label
     and list rather than as a run-on. Follows the strip's own border token. */
  body.shell-rail #shell-panel .shell-panel-head {
    border-right: 1px solid var(--border);
    margin-right: 2px; padding-right: 8px;
  }
}
/* ==========================================================================
   PHONE, round 5 - one pane at a time (the drill-in, and the way back)
   ==========================================================================
   Rounds 1-4 stopped the hubs pretending to be desktop columns by STACKING them: the
   rail, then the list, then the thing you opened, all on one page you scroll. That
   FITS, and it is still not how a phone works. Asked for directly: "a user list first
   (second sidebar), click on it only go into chat (content)" - the list fills the
   screen, tapping a row replaces it with the detail, and a back control returns you.

   Round 1 deliberately did not build this and named the reason: "no detail pane has a
   back or close control, so hiding the list on selection would trap the user." So the
   back control is the first half of this block rather than an afterthought, and every
   route into a detail goes through the one function that draws it (`showDetailPane`) -
   including the ones no click handler sees: `#inbox/245`, `#task/150`, a Home card, a
   chat opened from a notification.

   THE MECHANISM, and it is one mechanism rather than four. Each hub carries a
   `.mob-back` button in its markup placed as the SEAM between its two halves:
   everything BEFORE it is the list side (the rail, plus the list column on Tasks and
   the Inbox), everything AFTER it is the detail side (the reading pane, the AI panel).
   `data-mpane="list"|"detail"` on the hub says which side is showing, written only by
   `app.js:mpaneApply`. So the list rule is structural - "hide what is past the seam" -
   and only the detail rule has to name panes, because CSS has no previous-sibling
   combinator. The renderers are untouched: they already decide what is SELECTED, and
   this decides what is VISIBLE.

   WARNING: desktop is untouched, and by construction rather than by care.
   `.mob-back { display: none; }` is unconditional, so on a monitor the button is not
   laid out at all and no hub grid gains a track; `data-mpane` is read by nothing
   outside this query; and `showDetailPane` pushes no history entry above 760px.

   WARNING: `.ibx-ai` is never hidden by a rule of its own -
   `tests/test_inbox_read_and_reach.py` greps this file for that literal, because the
   panel holds every verb the Inbox has and a stylesheet that hid it once left people
   able to read a card and do nothing about it. It is not hidden here either: what the
   sibling rule stands down is the whole detail SIDE, and only while you are looking at
   the LIST - the one moment there is no card open to act on. Open one and the panel is
   back exactly as rounds 3 and 4 left it (stacked in classic, a drawer in the rail
   shell). Round 3 already collapsed what these panels DRAW while resting; this is the
   same judgement one level up, and it is what makes the reading pane's leftovers
   disappear when you tap back with a card still selected.

   WARNING: the Calendar is deliberately not here. Its rail is a legend, not a list, so
   there is nothing to drill into - round 2 put the month first and that stands.
   ========================================================================== */

/* Unconditional, so a monitor never lays the seam out. */
.mob-back { display: none; }

@media (max-width: 760px) {

  /* ---- 1 - the control that crosses the seam ---- */
  /* It NAMES what it goes back to. A bare chevron on a full-screen pane is the second
     half of the trap round 1 described: you can see there is a way out and not what is
     on the other side of it. Sticky, because three of the four hubs are a page that
     scrolls (`display: block`, rounds 1-3) and a back button you have to scroll up to
     find is one a thumb gives up on. */
  [data-mpane="detail"] > .mob-back {
    display: flex; align-items: center; gap: 7px;
    position: sticky; top: 0; z-index: 6;
    width: 100%; padding: 10px 14px;
    background: var(--bg-2); color: var(--text);
    border: 0; border-bottom: 1px solid var(--border);
    font: inherit; font-size: 13px; font-weight: 650;
    text-align: left; cursor: pointer;
  }
  .mob-back svg { width: 17px; height: 17px; flex: none; color: var(--text-dim); }
  .mob-back:active { background: var(--bg-3); }

  /* ---- 2 - the list side: everything past the seam stands down ---- */
  /* The whole of it. One rule, four hubs, and a pane added to any of them tomorrow is
     covered as long as it is put on the correct side of the button. */
  [data-mpane="list"] > .mob-back ~ * { display: none; }

  /* ---- 3 - the detail side: the list halves stand down ---- */
  /* Named one by one because there is no previous-sibling combinator - and named per
     hub rather than as `.gc-rail, .cn-rail, ...` so a rail can never be hidden by a
     state belonging to a hub it is not in. */
  .gc-hub[data-mpane="detail"] .gc-rail,
  .cn-hub[data-mpane="detail"] .cn-rail,
  .tsk-hub[data-mpane="detail"] .tsk-rail,
  .tsk-hub[data-mpane="detail"] .tsk-listcol,
  .ibx-hub[data-mpane="detail"] .ibx-rail,
  .ibx-hub[data-mpane="detail"] .ibx-listcol { display: none; }

  /* ---- 4 - and now the list gets the whole screen ---- */
  /* Messages is a GRID (its composer is pinned to the bottom of its own pane), so its
     two states are two row templates rather than a hidden box. */
  .gc-hub[data-mpane="list"] { grid-template-rows: minmax(0, 1fr); }
  /* WARNING: `:has(#gc-view)` is not decoration - it is how these two win. Round 1 read
     "nothing is picked" off `.gc-hub:has(.gc-empty-main)`, which was the only signal it
     had, and `.gc-empty-main` is ALSO what an OPEN but empty room draws ("No messages
     yet - say hi"). So opening a brand-new chat matched a rule meaning the opposite and
     hid the conversation you had just tapped, leaving a back button over a blank
     screen - measured, on the first room of a fresh company. `data-mpane` is the signal
     round 1 did not have; the id is what buys the specificity to say so. */
  .gc-hub[data-mpane="detail"]:has(#gc-view) { grid-template-rows: auto minmax(0, 1fr); }
  .gc-hub[data-mpane="detail"]:has(#gc-view) > .gc-main { display: flex; }
  /* Round 1 capped these rails to a strip so the content they navigate to stayed within
     a screen of them. There is no content under them any more - they ARE the screen. */
  .gc-hub[data-mpane="list"] .gc-rail,
  .cn-hub[data-mpane="list"] .cn-rail { max-height: none; border-bottom: 0; }
  /* Same for the two list COLUMNS. Round 1 caps them to 44vh once something is picked
     (and their rails to 20vh), which was right when the thing you picked was stacked
     underneath; tap back with a card still selected and that cap would leave the list
     ending two thirds down an otherwise empty screen.
     WARNING: the rules being undone carry an ID inside `:not(:has(...))`, so they score
     (1,3,0) and a plain two-class selector loses to them however far down the file it
     sits. `:has(#tasks-reading)` - which is always true, the pane is in the markup -
     is what buys the ID back. Match the shape of the rule being undone, not just its
     property; round 1 wrote the same warning about a two-class selector at 920px. */
  .tsk-hub[data-mpane="list"]:has(#tasks-reading) .tsk-listcol,
  .tsk-hub[data-mpane="list"]:has(#tasks-reading) .tsk-rail,
  .ibx-hub[data-mpane="list"]:has(#inbox-reading) .ibx-listcol,
  .ibx-hub[data-mpane="list"]:has(#inbox-reading) .ibx-rail { max-height: none; }
  /* ...and the list column's bottom border is the divider to a pane that is no longer
     under it. */
  .tsk-hub[data-mpane="list"] .tsk-listcol,
  .ibx-hub[data-mpane="list"] .ibx-listcol { border-bottom: 0; }

  /* ---- 5 - reading SEVERAL Inbox cards is a list-side act ---- */
  /* Tick a second row and the reading pane stops being one card: `renderInboxStack`
     fills it with all of them, and `#inbox-list`'s own click handler stops opening
     cards ("while several are ticked the pane IS the stack, so a row takes you to its
     card in it"). Nothing in that flow calls `showDetailPane`, and it must not - every
     tick would throw you into the detail and there would be no way to tick a third.
     So the stack is the one thing on the detail side that is shown while the hub says
     `list`, which is exactly how it read in rounds 1-4: the cards under the rows you
     ticked, one scroll away.
     WARNING: the panel stays down, and that is not an oversight - it draws only "I
     weigh in on one at a time" for a stack, and every verb the stack needs is on the
     cards themselves. */
  .ibx-hub[data-mpane="list"]:has(#inbox-reading > .ibx-stack) #inbox-reading {
    display: block;
  }

  /* ---- 6 - Company before you are in one has no list to go back to ---- */
  /* The onboarding pair (Create a company / Join a company) lives in `.cn-main`, which
     is the DETAIL side - so the drill-in would hide the only two buttons on the page
     behind a nav whose every destination is empty. While `#cn-onboarding` is up the two
     stack exactly as rounds 1-3 left them, and the seam is dormant (still `list`, so
     the back button stays hidden - there is nothing to go back FROM). */
  .cn-hub:has(#cn-onboarding:not(.hidden))[data-mpane="list"] > .mob-back ~ .cn-main {
    display: flex;
  }
}
/* ==========================================================================
   PHONE, round 6 - the same drill-in, everywhere else
   ==========================================================================
   Round 5 built ONE seam and applied it to the four hubs. This applies the SAME seam to
   the rest of the app, because the surfaces left over are the ones a person otherwise
   meets a different way on every page: "i mean for all tab, i want to do this, like
   inbox, task, memory, files, company, photometric, mind maps, aichat also".

   Nothing new is invented. `.mob-back` is still the seam in the markup, `data-mpane` is
   still the state, `app.js:showDetailPane` is still the only way in, and the structural
   sibling rule `[data-mpane="list"] > .mob-back ~ *` in the round-5 block above is still
   what hides the detail side. What is here is the per-surface half round 5 wrote for the
   hubs: which element is the list, and what it does once it owns the screen.

   The five surfaces are THREE shapes, and only the first is hub-like:

   1. Photometrics and Mind maps are `.split-body` - a list column beside a content
      column. Near enough to a hub that they are near-identical here.

   2. Files and Memory have no list column at all. MEMORY HAS NO DETAIL AT ALL - its rows
      edit in place - so it is neither in this block nor in `MPANE_HUBS`; it is already a
      full-width list on a phone and there is nothing to drill into, the same verdict
      round 5 reached about the Calendar. Files' detail is the preview DRAWER, which
      below 1100px already covers the page; what it lacked was a way back that says where
      back IS. Its modal carries a STATIC `data-mpane="detail"` in the markup - see the
      note there - so it draws the round-5 button off the round-5 rule.

   3. Chat's list is not inside the chat view. The conversations are `.side-recents`,
      which lives in the app sidebar (an off-canvas drawer on a phone) and which the rail
      shell already LIFTS into `#shell-panel`. `app.js:chatListHome` lifts the same
      element - never a copy, there is one `#conversation-list` and one renderer - into a
      list pane inside the view.

   WARNING: desktop is untouched by construction rather than by care. `.mob-back`,
   `.mob-only` and `.chat-mlist` are all `display: none` outside this query, so no grid
   above 760px gains a track, and `data-mpane` is read by nothing outside the two phone
   blocks.
   ========================================================================== */

/* Unconditional, so a monitor never lays either of them out. */
.mob-only { display: none; }
.chat-mlist { display: none; }

@media (max-width: 760px) {

  /* ---- 1 - Photometrics and Mind maps: the list column IS the page ---- */
  /* Named per rail rather than as a positional child selector, for the reason round 5
     gives: a state belonging to one page must never be able to reach into another. Each
     of these rails exists in exactly one view. */
  .split-body[data-mpane="detail"] > .phot-rail,
  .split-body[data-mpane="detail"] > .mm-rail { display: none; }

  /* Rule 7 of the round-2 block caps these to a 180px strip, so the content they
     navigate to stayed within a screen of them. There is no content under them now -
     they ARE the screen. (0,3,0) against that rule's (0,2,0), so this wins on
     specificity and not on position.) */
  .split-body[data-mpane="list"] { grid-template-rows: minmax(0, 1fr); }
  .split-body[data-mpane="list"] > .phot-rail,
  .split-body[data-mpane="list"] > .mm-rail { max-height: none; border-bottom: 0; }

  /* WARNING: an EMPTY list is the trap, and it is the one Company's onboarding pair
     sprang in round 5. Rule 7 also HIDES these rails when they hold nothing but a
     `.split-empty` (a strip containing only a repeat of the title is worse than no
     strip) - so with the drill-in engaged, "No saved maps yet" would have been a hidden
     list beside a hidden content column: a blank screen with a back button on it. With
     nothing in the list there is nothing to go back to, so the seam goes DORMANT
     instead. The content column shows in either state and the back control stays down,
     which is exactly how these two pages read in rounds 1-5; the moment the list has one
     row in it (press + New, or store a file) the drill-in engages by itself. */
  .split-body:has(.split-empty)[data-mpane="list"] > .mob-back ~ * { display: block; }
  .split-body:has(.split-empty)[data-mpane="detail"] > .mob-back { display: none; }

  /* The list side's own way in. Mind maps already had one (`#mm-new-rail`); Photometrics
     did not, and its drop zone lives in the content column - so on the list, with that
     column away, the only route to a file the list does not hold yet would have been a
     reload. `.mob-only` is what keeps it off every desktop width. */
  .mob-only { display: inline-flex; }

  /* ---- 2 - the file preview drawer: a full-screen detail with a way back ---- */
  /* The drawer already covers the page below 1100px (`--preview-w: 100%` plus the
     backdrop), so the SHAPE was right and only the control was missing: a corner
     in a head that also holds Copy reference and Download is not a way back, it is the
     third of three buttons. The back bar is the one every other surface draws, and
     `setPreviewBackLabel()` names the page it returns to rather than saying "Back". */
  #preview-overlay .modal-head { padding: 11px 12px; gap: 8px; flex-wrap: wrap; }
  #preview-overlay .modal-head h2 { font-size: 15px; }
  #preview-overlay .preview-acts { gap: 6px; margin-left: auto; }
  #preview-overlay .preview-body { padding: 12px 12px 18px; }

  /* ---- 3 - Chat: the conversations, full screen, then the thread ---- */
  /* `#chat-layout` is a GRID (the composer is pinned to the bottom of its own pane), so
     its two states are two row templates rather than a hidden box - the same shape
     Messages needed in round 5, for the same reason. */
  #chat-layout[data-mpane="list"] { grid-template-rows: minmax(0, 1fr); }
  #chat-layout[data-mpane="detail"] { grid-template-rows: auto minmax(0, 1fr); }
  #chat-layout[data-mpane="list"] > .chat-mlist {
    display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden;
  }
  .chat-mlist-head {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 16px 14px 10px;
  }
  .chat-mlist-title { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
  /* Prominent, and deliberately not the head's little glyph. Starting a chat is the
     primary action on this product; on the one screen that is nothing but a list of
     chats it must not be a 25px icon in a corner. */
  .chat-mlist-new { flex: none; padding: 8px 14px; font-size: 13.5px; }
  .chat-mlist-slot { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
  /* The MOVED element. It was `flex: 1` inside a sidebar column and it is `flex: 1`
     inside this one; what changes is that it scrolls itself now rather than riding the
     sidebar's scroller. */
  .chat-mlist-slot .side-recents { flex: 1; min-width: 0; min-height: 0; margin-top: 0; }
  .chat-mlist-slot .side-sec-head { padding: 0 14px; }
  .chat-mlist-slot .side-sec-body {
    flex: 1; min-height: 0; overflow-y: auto; padding: 0 10px 14px;
    -webkit-overflow-scrolling: touch;
  }
  .chat-mlist-slot .recents-search { width: 100%; box-sizing: border-box; margin: 4px 0 8px; }
  /* A full-width row on a phone, not a 264px sidebar row. */
  .chat-mlist-slot .conv-item { padding: 11px 12px; font-size: 14.5px; }

  /* WARNING: in the rail shell the chat panel was round 4's chip STRIP, and its only
     content was the very element that now lives in the view. Left showing, it would be
     an empty bar carrying a title and a plus above a page that already has both - and
     `shell-panel-off` cannot do it, because as far as `applyShellPanel` is concerned
     chat still HAS a panel section (it does, on a monitor). */
  body.shell-rail #shell-panel[data-section="chat"] { display: none; }
}

/* ==========================================================================
   ROUND 7 - the one element that was not allowed to shrink
   ==========================================================================
   A chat you cannot scroll, reported from a phone. It is not a chat bug and it is not
   round 6's drill-in: it is one missing `min-height` on `#main`, and it takes out the
   scroll container of every view in the rail shell at once.

   `#app` is `height: 100vh; overflow: hidden`, so NOTHING in this product scrolls the
   page - every view owns an internal scroller (`#messages`, `.side-sec-body`, a hub's
   column) and the whole design depends on a bounded height reaching it from `#app` down.

   `#main` is `flex: 1; display: flex; flex-direction: column; min-width: 0` - note which
   axis that `min-*` is on. While `#app-body` is a ROW (every width above 760px, and every
   width in the classic shell) `#main` is a flex item in the CROSS axis, so `align-items:
   stretch` hands it the container's height and the missing `min-height` never matters.

   The round-3 rail fix above turns `#app-body` into a COLUMN below 760px. `#main` is now
   a MAIN-axis item, where `min-height: auto` resolves to the content-based minimum size -
   so `flex: 1` can no longer shrink it, and it grows to whatever its content is. Measured
   at 390x844 on a 40-message conversation, rail shell:

       #app-body    792   (bounded, correct)
       #main     10 680   <- min-height: auto, the break
       #view-chat   10 680
       .chat-main   10 639
       #messages    clientHeight 10 528 == scrollHeight 10 528   -> nothing to scroll

   `#messages` was not a scroller at all: it had been stretched to the full height of its
   own content, so `overflow-y: auto` had nothing to do and `scrollTop` stayed 0. The
   composer sat at y=10 621, about 9 800px below the fold, and `#app`'s `overflow: hidden`
   meant the page could not be scrolled to it either - the transcript was simply cut off
   at 844px with no way to move.

   Scoped to the exact condition that breaks it (the rail shell, below 760px, where and
   only where `#app-body` is a column) rather than written unconditionally: in a row
   container `min-height: 0` is a no-op, so an unscoped rule would claim to do something
   at every width while doing something at one, and the next reader would have to
   re-derive which. Same measurement after: #main 792, #messages client 587 / scroll
   10 528, scrollTop reaches 9 941, composer on screen at y=733. */
@media (max-width: 760px) {
  body.shell-rail #main { min-height: 0; }
}

/* ---- Plugins: the same group heading the Skills page draws ----
   The two pages sit one nav item apart and had two different heading treatments —
   Skills a named group with a count pill and a rule, Plugins bare uppercase small caps.
   ⚠️ `grid-column` stays: these headings live INSIDE the auto-fill `.cat-list`, where
   Skills puts its own outside a <section>. That is the one structural difference, and it
   is forced by the layout rather than a second opinion about how a heading looks. */
.cat-sep {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 22px 2px 12px; padding: 0 2px 8px;
  border-bottom: 1px solid var(--border);
  font-size: inherit; font-weight: inherit; letter-spacing: normal;
  text-transform: none; color: inherit;
}
.cat-sep:first-child { margin-top: 2px; }

/* ---- Tasks rail ---------------------------------------------------------------
   Two levels: All tasks, then Personal and Company each holding their own rows —
   My Task, Team Task, and the categories filed under that side.

   A scope is an ordinary `.ibx-fold`, not a heavier heading: it is a destination you
   can click, exactly like the rows under it, and weighting it differently would imply
   it behaves differently. What separates the levels is the INDENT on the children and
   a hairline above each group — nothing that needs reading. */
.tsk-rail-grp {
  display: flex; align-items: center; gap: 2px;
  margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border);
}
.tsk-rail-grp > .ibx-fold { flex: 1; min-width: 0; }
/* The per-scope +. It sits OUTSIDE the scope button, or clicking "add a category here"
   would also select the scope — two actions on one press, and the one you did not ask
   for repaints the list under you. */
.tsk-rail-plus {
  flex-shrink: 0; border: none; background: none; cursor: pointer;
  color: var(--text-faint); font-size: 16px; line-height: 1;
  border-radius: 7px; padding: 5px 7px;
}
.tsk-rail-plus:hover { background: var(--bg-hover); color: var(--text); }
.tsk-rail-plus:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* A child of a scope, indented past its parent's icon. */
/* ⚠️ The edit affordance on a category. `.rail-edit` is declared `opacity: 0` and
   revealed by `.rail-item:hover` — a class this rail does not use and has not used
   since its rows became `.ibx-fold`. So the pencil was rendered, occupied space, and
   was invisible AND unclickable at every width: there was no way to rename a category,
   and once Delete moved into that modal, no way to delete one either. Revealed on the
   row that actually exists, and kept visible on the open row so the control does not
   vanish the moment you stop pointing at what you selected. */
.ibx-fold .rail-edit { opacity: 0; margin-left: 2px; }
.ibx-fold:hover .rail-edit,
.ibx-fold.on .rail-edit,
.ibx-fold .rail-edit:focus-visible { opacity: 1; }
.ibx-fold .rail-edit:hover { color: var(--text); }

.ibx-fold.tsk-sub { padding-left: 34px; }
.ibx-fold.tsk-sub .ibx-fold-ico { opacity: .62; }
.ibx-fold.tsk-sub.on .ibx-fold-ico { opacity: 1; }
/* ⚠️ Below 920px the rail collapses to a 58px icon strip, and the rule that centres
   its rows (`.tsk-rail .ibx-fold`) has the SAME specificity as `.ibx-fold.tsk-sub`
   above — which sits later in the file and would therefore win, re-indenting every
   child row off the edge of a strip that has no room for it. Undone explicitly here
   rather than relied on by position: this block is appended, and the next appended
   block would take the argument back. The + goes too — there is no width for it, and
   a category can still be added from the + menu at the top or from Ctrl+K. */
@media (max-width: 920px) {
  .ibx-fold.tsk-sub { padding-left: 4px; }
  .tsk-rail-plus { display: none; }
  .tsk-rail-grp { margin-top: 6px; padding-top: 6px; }
}

/* ---- Tasks: field ownership on a delegated task --------------------------------
   Both surfaces that can edit a task say the same sentence in the same voice: the
   modal above its fields, the drawer under its controls. It is an explanation, not a
   warning — nothing has gone wrong, the other half of this task belongs to somebody
   else — so it takes the muted body colour and not the danger one. */
.field-note, .tsk-owned-note {
  color: var(--text-dim); font-size: 12px; line-height: 1.5;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 10px;
}
.field-note { margin-bottom: 12px; }
.tsk-owned-note { margin-top: 10px; }
.field-note.hidden { display: none; }
/* A field somebody else owns still has to be READABLE — it is the thing you opened
   the task to see. Dim the chrome, never the text. */
.modal-body input:disabled, .modal-body textarea:disabled, .modal-body select:disabled,
.tsk-ctl select:disabled {
  opacity: 1; color: var(--text-dim); background: var(--bg-2);
  border-color: var(--border); cursor: not-allowed;
}

/* ===== Settings: this computer ==============================================
   The desktop helper. Three numbered steps, because it genuinely is a sequence —
   download, run, done — and the number is the only thing telling somebody that
   step 2 will take a few minutes and has not hung. */
.helper-steps { display: flex; flex-direction: column; gap: 18px; }
.helper-step { display: flex; gap: 14px; align-items: flex-start; }
.helper-n {
  flex: 0 0 26px; height: 26px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent-2);
  font-size: 13px; font-weight: 700; display: inline-flex;
  align-items: center; justify-content: center; margin-top: 1px;
}
.helper-step-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.helper-step-main .btn-primary {
  align-self: flex-start; margin-top: 6px; text-decoration: none;
  display: inline-flex; align-items: center;
}

.helper-card {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-2);
}
.helper-card-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* The dot is the whole status at a glance. Three states, never two: "never started" is
   not the same as "offline", and telling somebody to restart something they have never
   run sends them looking for a thing that is not there. */
.helper-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; background: var(--text-faint); }
.helper-card.on  .helper-dot { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-soft); }
.helper-card.off .helper-dot { background: var(--warn); }
.helper-card.never .helper-dot { background: var(--text-faint); }

.helper-adv summary { cursor: pointer; font-size: 13px; color: var(--text-dim); }
.helper-code-row { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.helper-code {
  font-family: var(--mono, ui-monospace, monospace); font-size: 17px; letter-spacing: .12em;
  padding: 6px 12px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-3);
}

/* ==========================================================================
   PHONE, round 5 - the gutter, and a second step of the scale
   ==========================================================================
   Asked for directly: on a phone, scale everything down and give the content
   back the room the margins were taking.

   The margins were the bigger half, and nothing had addressed them. MEASURED on
   a 390px screen before this block: `.page-body` inset 16px a side and the card
   inside it another 16, so the reading column was 326px of 390 - **64px, one
   sixth of the screen, spent on white space twice over**. Round 2's "one gutter,
   not two" fixed the case where two VIEWS disagreed; this is the case where one
   view charges the same gutter twice, once on the page and once on the card.
   Both paddings come down, and the second one comes down further, because a card
   already has a border and a background doing the work of separating it.

   The scale takes one more step. Round 4 wrote "deliberately not smaller than
   this", and that warning was right on its own terms - it is kept below, moved
   rather than deleted, because what changed is the REQUEST, not the reasoning.
   Its two hard rules still hold and are what stop this becoming a squint:
   nothing goes below 11px, and a control keeps its HEIGHT so a smaller label
   never makes a thing harder to hit.

   ⚠️ Scoped to 620px, not 760. Round 4's scale sits at 760 and covers tablets,
   where none of this was asked for and a 12px gutter would be wrong. Redeclaring
   the same NAMED steps one band down is the mechanism working as intended; a set
   of per-element overrides at 620 would be a second scale competing with the
   first.
   ========================================================================== */
@media (max-width: 620px) {
  :root {
    /* One step down from round 4. Body copy is the one that must not overreach:
       13px is still ordinary running text, and it is the floor for this element. */
    --fs-body: 13px;
    --fs-page-title: 18px;
    --fs-page-sub: 12px;
    --fs-card-title: 13px;
    --fs-card-sub: 11.5px;
    --fs-meta: 11px;      /* the floor round 4 set. Unchanged, on purpose. */
    --fs-control: 12.5px;
    /* The step round 4 called "the missing middle" - a section heading INSIDE a
       page, between the page's own title and its body copy. It was 16px, which
       was a middle step under a 26px title and is nearly the title itself under
       an 18px one, so the ladder had collapsed at the top. 18 / 15 / 13. */
    --fs-section: 15px;

    /* The page's inset, and a card's own. Named for the same reason the type
       steps are: they are applied in several places and must move together, or
       the page and the card inside it disagree about where the edge is. */
    --gutter: 12px;
    --card-pad: 12px;
  }

  /* ---- the page's gutter ---- */
  /* Same selector and the same two warnings as round 2 - `padding-inline` only, so
     `.lib-body`'s own `padding-top` is not reset by a shorthand; `:not(.split-body)`
     because Photometrics and Mind maps need their list column flush. */
  .page-body:not(.split-body) { padding-inline: var(--gutter); }
  .split-main { padding-left: var(--gutter); padding-right: var(--gutter); }
  .ibx-read { padding-left: var(--gutter); padding-right: var(--gutter); }
  /* ⚠️ Two classes, so it beats `.page-body:not(.split-body)` on specificity and
     re-insets every library page in the rail shell to 32 - round 4 hit exactly this
     and said so. The two shells must not disagree about the gutter. */
  body.shell-rail .lib-body { padding-left: var(--gutter); padding-right: var(--gutter); }

  /* ---- and the card's, which was being charged on top of it ---- */
  .home-card, .market-card, .inbox-card, .skill-result, .cn-settings-card {
    padding-left: var(--card-pad); padding-right: var(--card-pad);
  }
  .home-card { padding-top: var(--card-pad); padding-bottom: 10px; }
  /* ⚠️ `min-height: 180px` exists to keep cards in a grid ROW the same height. The
     grid is ONE column here, so there is no row to equalise and the only thing it
     still does is pad every short card out to 180px of a 844px screen. */
  .home-card { min-height: 0; }

  /* ---- headings that the round-4 list did not reach ---- */
  /* Home's card headings, and the phone chat-list screen's own title. The second is
     the ONE H1 on that screen, so it belongs with every other page title rather than
     22px on its own - it stays the most prominent thing there either way, because
     everything around it came down too. */
  .home-card-head h2 { font-size: var(--fs-section); }
  .chat-mlist-title { font-size: var(--fs-page-title); }
  .chat-mlist-head { padding-left: var(--gutter); padding-right: var(--gutter); }

  /* ---- a full-bleed view does not get a band for the mark ---- */
  /* ⚠️ Introduced by the commit that removed the top bar, and only wrong here: the
     brand belongs to whichever NAVIGATION FRAME is on screen, and on a phone that
     frame is the drawer, not the rail. The rail at this width is a filter strip, so
     its brand band was ~48px of an 844px screen spent on a logo, stacked directly
     under the strip already reserved for the menu button - two bands, one of them
     saying nothing you need while navigating. The drawer still carries it, one tap
     away, which is where the mark is on every other phone app. */
  .rail-chrome-top { display: none; }

  /* ---- the strip reserved for the floating menu button ---- */
  /* It only has to clear the chip, and the chip comes down with everything else. */
  .sidebar-toggle { top: 6px; left: var(--gutter); padding: 4px 9px; font-size: 14px; }
  #app-body { padding-top: 38px; }
}

/* ------------------------------------------------------------------ BIM preview
   An IFC previews as its CONTENT, not as its text — see backend/bimview.py. The
   dimensions and the headline properties are what a specifier checks first, so they
   are the two things above the fold. */
.bimview { display: grid; gap: 1rem; padding: .25rem; }
.bim-head { display: grid; gap: .2rem; }
.bim-title { margin: 0; font-size: 1.15rem; letter-spacing: -.01em; }
.bim-sub { color: var(--muted); font-size: .85rem; }
.bim-dims {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: .6rem;
}
.bim-dim {
  display: grid; gap: .15rem; padding: .6rem .7rem;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px;
}
.bim-dim-k { font-size: .75rem; color: var(--muted); text-transform: uppercase;
             letter-spacing: .04em; }
.bim-dim-v { font-size: 1.05rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.bim-props { display: grid; gap: 0; border: 1px solid var(--border); border-radius: 10px;
             overflow: hidden; }
.bim-prop { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
            gap: .75rem; padding: .45rem .7rem; }
.bim-prop:nth-child(odd) { background: var(--panel-2); }
.bim-prop-k { color: var(--muted); font-size: .85rem; overflow-wrap: anywhere; }
.bim-prop-v { font-size: .9rem; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
.bim-headline .bim-prop-v { font-weight: 600; }
.bim-note { margin: 0; color: var(--muted); font-size: .87rem; line-height: 1.45; }
.bim-more summary { cursor: pointer; color: var(--muted); font-size: .87rem;
                    padding: .2rem 0; }
.bim-foot { color: var(--muted); font-size: .8rem; font-variant-numeric: tabular-nums; }

/* A file we cannot open. ⚠️ The advice is the point — see app._OPAQUE_CAD_ADVICE. */
.pv-unreadable { display: grid; gap: .7rem; padding: 1.6rem; max-width: 48ch; margin: 0 auto;
                 text-align: center; }
.pv-unreadable-why { margin: 0; color: var(--muted); line-height: 1.5; }
.pv-unreadable-do {
  margin: 0; padding: .8rem 1rem; line-height: 1.5;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px;
  text-align: left;
}


.pv-convert { margin: .2rem auto 0; }
.pv-convert-note { margin: 0; text-align: center; }

/* --- Fab-file plot (Quorie PCB Viewer) ------------------------------------------
   ⚠️ The board paints its OWN ground and deliberately does not use the theme tokens.
   Every fab viewer ever made is copper on a dark board, and that convention is what
   makes the picture readable at a glance — a PCB on the page's white background reads
   as a diagram OF a PCB. The colours live in `app.js:PCB_INK` because they are written
   into the SVG itself, so a downloaded or printed plot keeps them. */
.pcb-plot { margin: 0 0 14px; }
.pcb-svg {
  width: 100%; height: auto; display: block;
  border-radius: 10px; border: 1px solid var(--border);
  /* A wide board in a narrow panel must not run off the page. */
  max-height: 60vh; object-fit: contain;
}
.pcb-cap {
  margin-top: 8px; font-size: 12px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
/* ⚠️ Not an error style. This line carries "one layer of the fab set", which is the
   most important sentence on the panel and must not read as a failure. */
.pcb-note { margin: 4px 0 0; font-size: 12px; color: var(--text-dim); opacity: .85; }

/* ---- Email ------------------------------------------------------------------
   The LAYOUT is not here. Email reuses the Inbox shell verbatim — `.ibx-hub` and
   its rail / list / reading / AI columns — so the rail is the same width as every
   other rail and the left edge of the app does not move when you navigate here.
   A bespoke grid at slightly different widths was the first version, and that is
   exactly the drift this avoids.

   Only what is genuinely about MAIL lives below: a message row, and the reading
   pane's own furniture. Anything a page could share is shared. */
.mailv-row {
  display: block; width: 100%; text-align: left; cursor: pointer;
  padding: 10px 14px; background: none; border: 0;
  border-bottom: 1px solid var(--border); color: var(--text);
}
.mailv-row:hover { background: var(--bg-2); }
.mailv-row.on { background: var(--accent-soft); }
.mailv-row-top { display: flex; gap: 8px; align-items: baseline; }
.mailv-from { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mailv-date { margin-left: auto; font-size: 11px; color: var(--text-dim); white-space: nowrap; }
.mailv-subj {
  font-size: 13px; color: var(--text-dim); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Unread is carried by WEIGHT, not a coloured dot: the list is scanned, and a bold
   line reads as unread at a glance without needing a legend. */
.mailv-row.unread .mailv-from,
.mailv-row.unread .mailv-subj { font-weight: 600; color: var(--text); }

.mailv-msg { padding: 18px 22px; }
.mailv-msg-subj { font-size: 19px; margin: 0 0 10px; }
.mailv-msg-meta { font-size: 13px; color: var(--text-dim); display: grid; gap: 2px; }
.mailv-msg-meta b { color: var(--text); font-weight: 500; margin-right: 6px; }
.mailv-atts { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0 0; }
.mailv-att {
  font-size: 12px; padding: 3px 9px; border: 1px solid var(--border);
  border-radius: 999px; color: var(--text-dim);
}
.mailv-att-n { opacity: .7; margin-left: 4px; }
.mailv-body {
  margin: 16px 0 0; padding-top: 16px; border-top: 1px solid var(--border);
  font-size: 14px; line-height: 1.6; word-break: break-word;
}

.mailv-replybox { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border); }
.mailv-replybox textarea {
  width: 100%; min-height: 90px; resize: vertical; padding: 10px 12px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-2); color: var(--text); font: inherit;
}
.mailv-reply-acts { display: flex; align-items: center; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.mailv-reply-note { font-size: 12px; color: var(--text-dim); }

.mailv-ai-head { padding: 12px 14px; font-weight: 600; font-size: 13px; border-bottom: 1px solid var(--border); }
.mailv-ai-quick { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 12px 4px; }
.mailv-ai-quick button {
  font-size: 12px; padding: 4px 10px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
}
.mailv-ai-quick button:hover { border-color: var(--accent); color: var(--accent-2); }
.mailv-ai-log { flex: 1; overflow-y: auto; padding: 10px 12px; font-size: 13px; }
.mailv-ai-q { margin: 10px 0 4px; font-weight: 600; padding-left: 8px; border-left: 2px solid var(--accent); }
.mailv-ai-a { margin: 0 0 12px; line-height: 1.55; }
.mailv-ai-ask { display: flex; gap: 6px; padding: 10px 12px; border-top: 1px solid var(--border); }
.mailv-ai-ask textarea {
  flex: 1; resize: vertical; padding: 8px 10px; font: inherit;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg); color: var(--text);
}
.mailv-empty { color: var(--text-dim); font-size: 13px; padding: 16px; margin: 0; }
/* The AI column is a flex stack so its log scrolls and the ask box stays put — the
   same arrangement #inbox-ai uses. */
#mailv-ai { display: flex; flex-direction: column; min-height: 0; }
/* A SCHEMATIC reuses the plot's frame and nothing else, because it is the opposite
   drawing: a fab plot is copper on a dark board, a schematic is ink on paper. Both paint
   their own ground for the same reason — the convention is what makes the picture
   readable at a glance, and a schematic on a dark page reads as a diagram OF a schematic.
   The colours live in `app.js:SCH_INK`, written into the SVG itself.

   ⚠️ Taller than the fab plot on purpose. A board is looked AT; a schematic is READ, and
   a net has to be followable from one end to the other without scrolling. */
.sch-plot .pcb-svg, .sch-svg {
  width: 100%; height: auto; display: block;
  border-radius: 10px; border: 1px solid var(--border);
  max-height: 78vh; object-fit: contain;
}

/* The render setup panel, beside the 3D preview.
   ⚠️ It sits UNDER the canvas rather than beside it: the model is the thing being judged
   and a side rail steals width from it on exactly the narrow screens where the shape is
   already hardest to read. Two columns of short rows keep the panel shallow instead. */
.st-shell { margin-top: 10px; }
.st-panel {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 18px; align-items: center;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-2);
}
.st-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.st-lab { font-size: 12px; color: var(--text-dim); width: 74px; flex: none; }
.st-sel, .st-range { flex: 1; min-width: 0; }
.st-sel {
  font: inherit; font-size: 12px; padding: 3px 6px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
}
/* Tabular so a slider does not shuffle the row as the number changes width. */
.st-val {
  font-size: 12px; color: var(--text-dim); width: 46px; flex: none;
  text-align: right; font-variant-numeric: tabular-nums;
}
.st-col { width: 44px; height: 24px; padding: 0; border: 1px solid var(--border);
          border-radius: var(--radius-sm); background: var(--bg); }
.st-toggles { gap: 16px; }
.st-check { display: flex; align-items: center; font-size: 12px; color: var(--text); }
/* The camera readout spans both columns: it is the one line that must be findable,
   because it carries the numbers the render will actually be given. */
.st-cam, .st-hint, .st-go { grid-column: 1 / -1; }
.st-cam { font-size: 12px; color: var(--text); font-variant-numeric: tabular-nums; }
.st-go { justify-self: start; }
/* ⚠️ Not an error style. This says the preview is an approximation, which is the most
   important sentence on the panel and must not read as a failure. */
.st-hint { margin: 0; font-size: 12px; color: var(--text-dim); opacity: .85; }

@media (max-width: 560px) { .st-panel { grid-template-columns: minmax(0, 1fr); } }

/* The company-email card lists PEOPLE, not addresses — an admin turning email on is
   deciding about a person, and matching a roster against a list of strings is the work
   this page exists to do for them. */
.cn-mail-people { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.cn-mail-who { font-weight: 500; min-width: 120px; }
.cn-mail-note { color: var(--text-dim); font-size: 12px; }

/* The account this rail is showing, above its folders — the arrangement every mail
   client uses. A group header rather than a title tweak, so a second account later is a
   second group and not a rewrite. */
.mailv-acct {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 8px 8px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.mailv-acct-addr {
  font-size: 12.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ==========================================================================
   PHONE - a generated file is a NAME first
   ==========================================================================
   Reported from a phone: the file cards in chat showed no filename at all and the
   Download button was cut off by the card's own edge.

   `.file-row` was `[icon] [name + size] [Preview] [Download]` on one flex line, and
   only the middle column could shrink — so it absorbed every pixel of pressure.
   MEASURED at 320px: the name got 49px ("ESP3…"), and it is the ONE thing that says
   which file this is. A hair more button width (a wider glyph, a boosted font on a
   real handset) takes it to zero and the actions overflow into the clip.

   Two rows instead: the name gets the full width, the actions get their own line and
   keep their tap targets. Nothing here depends on guessing a font metric — that is
   the point, because the metric is what differed between the report and the repro. */
@media (max-width: 620px) {
  .file-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: 2px 9px;
    padding: 10px var(--card-pad);
  }
  .file-icon { grid-column: 1; grid-row: 1 / span 2; align-self: start; margin-top: 1px; }
  .file-info { grid-column: 2; }
  /* ⚠️ Left-aligned under the name, not stretched: a full-width Download reads as the
     card's primary action when the primary action is opening the thing. */
  .file-acts { grid-column: 2; justify-self: start; margin-top: 5px; }
  /* The name can use the whole row now, so it only ellipses when it genuinely must. */
  .file-name { font-size: var(--fs-card-title); }
  .file-meta { font-size: var(--fs-meta); }
}

/* ==========================================================================
   PHONE - the thread's text column, which was two thirds of the screen
   ==========================================================================
   Reported: the chat is cramped and hard to read on a phone.

   MEASURED at 390px, the AI's text had 258px to wrap in — 66% of the screen. The
   other third went to three things sized for a monitor:

     22px  `.msg-row` padding, each side
     44px  avatar (30) + gap (14) on the left
     44px  `.msg-row.assistant .msg-body` padding-RIGHT, which holds no content at
           all — it exists so an assistant paragraph ends level with the user
           bubble above it. That symmetry is worth 11% of the width on a desktop
           and nothing on a phone, where the column is the scarce thing and the two
           edges are never seen together anyway.

   Reclaiming all three gives the text ~330px of 390 (85%). Nothing is scaled down
   here — the type is untouched; the words simply get the room the decoration had.

   ⚠️ `.msg-body`'s bottom padding is NOT reclaimed, though it looks like dead space.
   It reserves the hover/tap toolbar, which is built on FIRST interaction: with
   nothing reserved, every message grows by the toolbar's height the first time you
   touch it and the thread jumps under your finger. */
@media (max-width: 620px) {
  .msg-row { padding-left: var(--gutter); padding-right: var(--gutter); gap: 8px; }
  .avatar { width: 26px; height: 26px; border-radius: 7px; font-size: 14px; }
  .avatar.user .cn-ppl-avatar { width: 26px; height: 26px; font-size: 12px; }
  /* The reserve goes; the column keeps a small inset so a long word does not run
     into the screen edge, which is what made the wrap look broken. */
  .msg-row.assistant .msg-body { padding-right: 6px; }
}

/* A model the workspace has no key for. Still listed and still clickable — the click
   explains what is missing — but it must not read as an equal choice beside the ones
   that will actually run. */
.drop-item.model-item-off b { color: var(--text-dim); }
.drop-item.model-item-off span { color: var(--text-faint); }

/* The named plugin a capability belongs to (backend/capabilities/families.py). Sits
   between the row's own name and its summary line, quieter than both: it is the family
   this thing is part of, not what the row IS. */
.cat-fam {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--accent);
  opacity: .85;
  margin: 1px 0 2px;
}
