  /* ══ THE Z-INDEX SCALE ════════════════════════════════════════════════════════════════════
     IB-06 + IB-143 + IB-1440. Read this before you add or change any z-index in this file.

     IB-1440 · every stacking collision this shell has had — the newest one being the kebab
     menu opening under the tab strip and coder cards — came from one build picking a number
     bigger than the numbers it happened to be able to see. 67 raw z-index declarations across
     28 values had accreted this way, with no shared order. That is over: there is now exactly
     ONE scale, defined once below as named custom properties, and every z-index in this file
     (public/dashboard.css) is a var(--z-*) reference to it — enforced by
     tests/dashboardZIndexScale.test.js, which fails the build on any raw numeric z-index outside
     this block. Reach for a bigger raw number and the test catches it before Brandon does.

     A z-index only wins inside its OWN stacking context — a parent with position+z-index,
     transform, filter, opacity<1, or a CSS containment property (contain/container-type) traps
     its children in a sub-context no matter how high their number is. <header> is exactly such a
     trap (position:sticky + z-index): everything inside it, including its dropdowns, is capped
     to compete only against its own header siblings. That is why header-anchored dropdowns
     (the kebab/overflow menu, the avatar menu, search results, the model picker...) get their
     own clearly-separated --z-dropdown tier instead of reusing small numbers that happened to
     already be free — a dropdown fighting over "is 60 bigger than the other thing's 65 or 80"
     inside a trapped context is exactly the bug this scale exists to end.

     TOP TO BOTTOM:

       7 · EMERGENCY — the one layer that must never be covered: the approval takeover.
                                                                     --z-emergency (99999)
           Just below it: the guided-merge completion takeover, a takeover in its own right but
           deliberately one notch under a live decision.       --z-emergency-secondary (99998)

       6 · TOAST / ALERT — informational, self-dismissing, never blocking: the brain toast, the
           quiet alert toast, the card-outcome receipt toast. Always above every modal; always
           below the emergency layer.                                    --z-toast (9000)

       5 · MODAL / PANEL — a takeover that REPLACES the board: every full-screen modal/sheet, the
           merge tray, the focus / right-now / morning / shutdown / scan overlays, the phone's
           full-screen desk chat, the ops side-drawer.                    --z-modal (1000)
           Two panels can be open "on top of" one another (the coder-detail sheet over full-screen
           chat, the image viewer over its opener, the tool-rail drawer over chat, the mobile
           model picker over chat) — those get the raised sub-tier.  --z-modal-raised (1100)
           A scrim/backdrop paired with a raised panel sits between the two.
                                                                       --z-modal-scrim (1050)
           A scrim/backdrop paired with a base-tier panel sits just under it. --z-scrim (950)

       4 · DROPDOWN / MENU — any popover anchored to a control, wherever it lives: the kebab
           menu, the avatar menu, search/command results, the model picker, the brain-desk menu,
           the floating "Pin" button. Above the header and board chrome; below a takeover — this
           is the fix for tonight: the kebab's old z-index:60 lived in the same cramped 50-80
           band as a dozen unrelated things inside the header's trap; --z-dropdown is its own
           clearly-higher number so it can never again lose to a sibling's guess.
                                                                          --z-dropdown (500)

       4.5 · PORTAL MENU (WO-PORTAL-MENU) — a bigger number was never the real fix for tier 4's
           menus, and the kebab kept breaking in new ways (IB-1423 a display:none gap, IB-1434 a
           display:contents ancestor breaking its position, IB-1440 a plain stacking-context trap)
           because each patch addressed one ancestor's misbehaviour instead of removing the
           dependency on the ancestor chain. The kebab/overflow menu, the avatar menu, and the
           More-tab menu are now re-parented to #portal-root — a single layer that is always a
           direct child of <body>, positioned by real screen coordinates from their trigger, not
           by an ancestor's containing block. --z-portal sits above every modal tier (a menu
           opened from inside a panel must still be visible), but below a toast or the emergency
           takeover (those must never be hidden behind a stray open menu).
                                                                          --z-portal (1150)

       3 · THE BOARD'S HEADER — sticky, the top of the board it belongs to.   --z-header (45)

       2 · BOARD CHROME — the AGENTS rail, its resize handle, the scrim behind it: furniture
           that sits BESIDE the board while the board is still the view.
           backdrop --z-chrome (30) · rail --z-chrome-raised (40) · resize handle --z-chrome-top (41)

       1(b) · RAISED CONTENT — small in-panel decorations that only need to clear their own
              immediate siblings (a sticky sub-header inside a scroll pane, a "now" marker, a
              jump-to-latest button): never competes with anything outside its own panel.
                                                                             --z-raised (10)

       1 · THE BOARD — counts, filter bar, task rows.                              auto / 0

     IB-143 (unchanged): every full-screen layer begins BELOW the header using
     top: var(--header-h) — a panel that wants to cover the whole screen covers the area under
     the header, not the header. A new panel does not need a number bigger than the header's; it
     needs to be on the right named tier and to attach at the reserved offset.

     WHY THIS IS WRITTEN DOWN. IB-01 gave the header z-index 200 to keep it clear of the rail,
     which put it above tier 5 as well. Measured in Chrome at 390x844: the header painted over
     the top 234px of the conversation and over the chat's own back button — a tap meant for
     the way out of a chat landed on the hamburger — and it took the ✕ off nine other
     full-screen layers at the same time. IB-01's sticky pinning is untouched; only its tier
     is. */
  :root { color-scheme: dark; --rail-w: 130px; --tool-rail-w: 240px; --chat-drawer-w: 420px; --header-h: 56px;
          --z-raised: 10;
          --z-chrome: 30;
          --z-chrome-raised: calc(var(--z-chrome) + 10);   /* 40 */
          --z-chrome-top: calc(var(--z-chrome) + 11);      /* 41 — above the rail it resizes */
          /* tier 3: above board chrome (max 41), below the first dropdown (500). */
          --z-header: 45;
          --z-dropdown: 500;
          --z-scrim: calc(var(--z-modal) - 50);       /* 950 — paired with a base-tier panel */
          --z-modal: 1000;
          --z-modal-scrim: calc(var(--z-modal) + 50);  /* 1050 — paired with a raised panel */
          --z-modal-raised: calc(var(--z-modal) + 100); /* 1100 — a panel opened over another panel */
          /* tier 4.5: above every modal tier, below a toast — see WO-PORTAL-MENU above. */
          --z-portal: calc(var(--z-modal-raised) + 50); /* 1150 */
          --z-toast: 9000;
          --z-emergency-secondary: calc(var(--z-emergency) - 1); /* 99998 */
          --z-emergency: 99999; }
  /* WO-PORTAL-MENU: the single shared home every portal-mounted menu re-parents into on open —
     see the JS comment above the overflow-menu wiring in dashboard.html for why. inset:0 with
     pointer-events:none so the empty layer never blocks a click on the real page; each menu
     re-enables its own pointer-events implicitly by being an interactive element inside it. A
     fixed position (not sticky/absolute) so it never joins any ancestor's stacking context or
     containing block — that containment is exactly what this exists to escape. */
  #portal-root { position: fixed; inset: 0; z-index: var(--z-portal); pointer-events: none; overflow: visible; }
  #portal-root > * { pointer-events: auto; }
  * { box-sizing: border-box; }
  body { margin: 0; font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
         background: #0d1117; color: #e6edf3; }
  /* WO-HEADER-REFLOW (task 220): wrap at EVERY width, not just the <=759px phone breakpoint.
     Previously only the phone media query set flex-wrap, so anywhere from ~760px up to full
     desktop the header had nowhere to put content that didn't fit and it clipped off the right
     edge. Wrapping unconditionally means a line that fits stays on one row (unchanged on wide
     desktop); a line that doesn't just grows a second row instead of overflowing. */
  /* WO-HEADER-STREAMLINE-CODER-CAP: two tight rows. Reduced vertical padding + smaller row-gap
     so the sticky header eats less vertical real estate.
     IB-01: sticky and safe-area aware, so the status/context strip stays put while the board
     scrolls and OS chrome never covers it. Both are kept exactly as IB-01 left them.
     IB-06: the z-index is TIER 3 (--z-header), read from the stacking order written at the top
     of this stylesheet — above the rail and the board, below anything that has replaced the
     board. Do not raise it: a layer that needs to be above the header is a takeover, and
     takeovers already are. */
  header { position: sticky; top: 0; z-index: var(--z-header); background: #0d1117;
           padding: calc(7px + env(safe-area-inset-top)) calc(20px + env(safe-area-inset-right)) 7px calc(20px + env(safe-area-inset-left));
           border-bottom: 1px solid #21262d; display: flex; align-items: center;
           gap: 8px; flex-wrap: wrap; row-gap: 5px; }
  header h1 { font-size: 16px; margin: 0; font-weight: 600; }
  /* IB-UX-1 · fleet health control lives in the always-visible action strip, top right. Green for
     healthy, amber for warnings, red for real failures. Clicking reveals one sentence why. */
  .fleet-health { position: relative; display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto;
                  margin-left: auto; padding: 4px 8px; border-radius: 6px; background: #161b22;
                  border: 1px solid #30363d; cursor: pointer; white-space: nowrap; user-select: none; }
  .fleet-health:hover { background: #1c2129; }
  .fleet-health-dot { width: 9px; height: 9px; border-radius: 50%; background: #3fb950; box-shadow: 0 0 8px #3fb950; }
  .fleet-health-dot.warn { background: #d29922; box-shadow: 0 0 8px #d29922; }
  .fleet-health-dot.down { background: #f85149; box-shadow: 0 0 8px #f85149; }
  .fleet-health-label { font-size: 11px; font-weight: 600; color: #c9d1d9; }
  .fleet-health-popover { display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: var(--z-dropdown);
                          min-width: 220px; max-width: 320px; background: #161b22; border: 1px solid #30363d;
                          border-radius: 8px; padding: 10px 12px; box-shadow: 0 10px 28px rgba(0,0,0,.55);
                          font-size: 12px; font-weight: 500; color: #e6edf3; white-space: normal;
                          text-align: left; line-height: 1.4; }
  .fleet-health-popover.on { display: block; }
  /* .meta is now an ERROR-ONLY strip (unauthorized / unreachable) — the benign "updated … / N
     worker(s)" noise it used to carry is gone (WO-HEADER-INSANE-BRAIN, req 7). Empty → invisible. */
  header .meta { color: #f85149; font-size: 11px; font-weight: 600; }
  header .meta:empty { display: none; }
  header button { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 6px;
                  padding: 4px 10px; cursor: pointer; }
  /* Header "All / full board" (task 26, item 4): lit blue while the board is unfiltered so it
     reads as the active view — replaces the old sidebar "All" row's selected highlight. */
  .hdr-all.active { background: #1f6feb; color: #fff; border-color: #1f6feb; font-weight: 600; }
  /* WO-HEADER-STREAMLINE-CODER-CAP: icon-only header buttons (Dashboard, Manager chat). Square,
     compact, centered, with the same active blue tint as the old text pill. */
  .hdr-ico { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px;
             padding: 0; flex: 0 0 auto; }
  .hdr-ico-sym { font-size: 15px; line-height: 1; }
  /* hamburger: visible on mobile only (mobile-first) */
  .hamb { display: inline-flex; align-items: center; justify-content: center; font-size: 16px; line-height: 1;
          padding: 4px 9px; }

  /* ── brand mark + wordmark (WO-HEADER-BRAIN-LOGO / IB-06) ───────────────────
     IB-151 extracted the mark's own markup, CSS, animation and health states into
     public/brand-mark.js — the ONE file both this page and public/board-tv.html load, so the two
     can no longer drift the way earlier hand-copy attempts did (see that file for the .brand /
     .ib-logo / .wordmark rules). What's left here, keyed off #brand rather than the shared .brand
     CLASS so it can never collide with the shared rule, is this page's own affordance around the
     mark: it's a clickable control (jumps to the full board) only on the dashboard, never on the
     read-only TV board, so `cursor`/`user-select` stay local rather than shared. */
  #brand { flex: 0 0 auto; cursor: pointer; user-select: none; }

  /* ── live vitals pills (WO-HEADER-STREAMLINE-CODER-CAP + IB-174): tight, monospace
     run/queued/blocked/done readout, all from one source when the coding board is in view. */
  .vitals { display: flex; gap: 4px; flex: 0 0 auto; flex-wrap: wrap;
            font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
  .vpill { display: flex; align-items: baseline; gap: 3px; padding: 4px 7px; border: 1px solid #30363d;
           border-radius: 6px; background: #161b22; white-space: nowrap; }
  .vpill .vn { font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums; }
  .vpill .vl { font-size: 9px; text-transform: uppercase; letter-spacing: .04em; color: #8b949e; }
  .vpill.running .vn { color: #3fb950; }
  .vpill.queued .vn { color: #d29922; }
  .vpill.blocked .vn { color: #f85149; }
  .vpill.done .vn { color: #58a6ff; }
  /* IB-UX-1 · the top search/command box is hidden from the default view. The markup and handlers
     stay in the DOM for back-compat; the box is simply not shown. */

  /* ── search / command box (kept hidden from the default view by IB-UX-1) */
  .hdr-search { display: none; position: relative; flex: 1 1 170px; min-width: 110px; max-width: 320px; }
  .cmdbox { width: 100%; background: #0d1117; border: 1px solid #30363d; border-radius: 6px;
            color: #e6edf3; padding: 6px 10px; font-size: 13px; }
  .cmdbox:focus { outline: none; border-color: #1f6feb; }
  .cmdresults { display: none; position: absolute; top: calc(100% + 5px); left: 0; right: 0; z-index: var(--z-dropdown);
                background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow-y: auto;
                max-height: 340px; box-shadow: 0 10px 28px rgba(0,0,0,.55); }
  .cmdresults.on { display: block; }
  .cmdresult { padding: 8px 12px; cursor: pointer; display: flex; align-items: center; justify-content: space-between;
               gap: 10px; font-size: 13px; border-bottom: 1px solid #1b2129; }
  .cmdresult:last-child { border-bottom: none; }
  .cmdresult:hover, .cmdresult.sel { background: #1f6feb26; }
  .cmdresult .cr-k { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: #8b949e;
                      flex: 0 0 auto; }
  .cmdresult .cr-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* ── what's-running peek (req 5): a compact icon toggle that expands into the live task list.
     WO-HEADER-SIDEBAR-UX (task 452, req 1): the running COUNT used to live here too ("N running"),
     duplicating the vitals "Running" pill — Brandon flagged the double-up. The count is gone; this
     is now just the expand-to-see-the-list affordance (the dot pulses green while something builds,
     the chevron hints it opens). The vitals pill is the single running-count source of truth. */
  .running-peek { position: relative; flex: 0 0 auto; }
  .rp-btn { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
            min-height: 28px; padding: 4px 8px; border-radius: 6px; background: #161b22;
            color: #c9d1d9; border: 1px solid #30363d; cursor: pointer; }
  .rp-btn:hover { background: #1c2129; }
  .rp-chev { font-size: 10px; line-height: 1; color: #8b949e; }
  .rp-dot { width: 7px; height: 7px; border-radius: 50%; background: #484f58; flex: 0 0 auto; }
  .rp-dot.live { background: #3fb950; box-shadow: 0 0 6px #3fb950; animation: um-pulse 1.6s ease-in-out infinite; }
  .rp-panel { display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: var(--z-dropdown); width: 300px;
              max-height: 360px; overflow-y: auto; background: #161b22; border: 1px solid #30363d;
              border-radius: 8px; box-shadow: 0 10px 28px rgba(0,0,0,.55); padding: 6px; }
  .rp-panel.on { display: block; }
  .rp-item { padding: 7px 8px; border-bottom: 1px solid #1b2129; }
  .rp-item:last-child { border-bottom: none; }
  .rp-item .rp-title { font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .rp-item .rp-sub { font-size: 10px; color: #8b949e; margin-top: 1px; }

  /* ── avatar menu (req 7): replaces the "Brandon Bozarth / Owner" inline block */
  .avatar-wrap { position: relative; flex: 0 0 auto; }
  .avatar-btn { display: flex; align-items: center; justify-content: center; width: 30px; height: 30px;
                padding: 0; border-radius: 50%; overflow: hidden; background: #21262d; font-size: 12px;
                font-weight: 700; }
  .avatar-btn img { width: 100%; height: 100%; object-fit: cover; }
  .avatar-menu { display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: var(--z-dropdown); min-width: 190px;
                 background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 10px 12px;
                 box-shadow: 0 10px 28px rgba(0,0,0,.55); }
  .avatar-menu.on { display: block; }
  .avatar-menu .am-name { font-size: 13px; color: #e6edf3; font-weight: 600; }
  .avatar-menu .am-role { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: #8b949e; }
  .avatar-menu .am-role.owner { color: #f0883e; }
  .avatar-menu button { width: 100%; margin-top: 8px; }

  /* ── WO-MERGE-TRAY (task 453): badge + cascade tray — replaces the old "Merge all ready now"
     mass-submit button. Green/tested branches land hands-off (autonomous mergeCycle); this tray
     surfaces ONLY branches that hit a real issue (gate-failed/conflict/escalated/error/reverted),
     so the badge only ever lights up when something needs Brandon's attention. Fixed position
     (not anchored to any one of the 3 header trigger buttons) so it works the same whether opened
     from the desktop action-strip, the phone overflow menu, or the legacy inline copy. */
  .mt-badge { display: none; align-items: center; justify-content: center; min-width: 15px; height: 15px;
              padding: 0 4px; margin-left: 3px; border-radius: 20px; background: #f85149; color: #fff;
              font-size: 10px; font-weight: 700; line-height: 15px; }
  /* T94 (merge-tray-popup-redesign): the tray is a CENTERED POPUP over a dim backdrop, not a
     full-page takeover. #merge-tray-panel IS the backdrop (clicking it closes); .mt-modal is the
     card, width-capped and self-scrolling so the header/status strip/footer stay put while only
     the card stack moves. Mobile-first: the cap collapses to the viewport under 560px. */
  .merge-tray { display: none; position: fixed; inset: 0; z-index: var(--z-modal);
                background: rgba(1,4,9,.72); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
                align-items: flex-start; justify-content: center;
                /* IB-143: the tray is already offset below the header by the structural guard;
                   keep only a small breathing-room pad so the modal card clears the reserved strip. */
                padding: 8px 10px 12px; }
  .merge-tray.on { display: flex; }
  .mt-modal { width: 100%; max-width: 520px; max-height: 100%; display: flex; flex-direction: column;
              background: #161b22; border: 1px solid #30363d; border-radius: 14px; overflow: hidden;
              box-shadow: 0 18px 48px rgba(0,0,0,.7); }
  .mt-modal-body { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; }
  .mt-head { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
             gap: 8px; padding: 10px 12px; border-bottom: 1px solid #21262d; background: #161b22; z-index: var(--z-raised); }
  .mt-head h3 { margin: 0; font-size: 13px; color: #e6edf3; }
  .mt-head .sub { font-size: 10px; color: #8b949e; margin-top: 1px; }
  .mt-mergeall { flex: 0 0 auto; background: #238636; border: 1px solid #2ea043; color: #fff; font-size: 11px;
                 font-weight: 600; padding: 6px 10px; border-radius: 6px; cursor: pointer; white-space: nowrap; }
  .mt-mergeall:disabled { background: #21262d; border-color: #30363d; color: #6e7681; cursor: default; }
  .mt-empty { padding: 20px 14px; text-align: center; color: #6e7681; font-size: 12px; }
  .mt-section { border-bottom: 1px solid #21262d; }
  .mt-section:last-child { border-bottom: none; }
  .mt-section-head { display: flex; align-items: center; justify-content: space-between; gap: 8px;
                     padding: 8px 12px; background: #0d1117; border-bottom: 1px solid #1b2129; }
  .mt-section-head strong { font-size: 11px; color: #e6edf3; }
  .mt-section-head .sub { font-size: 10px; color: #8b949e; }
  .mt-row { padding: 9px 12px; border-bottom: 1px solid #1b2129; }
  .mt-row:last-child { border-bottom: none; }
  .mt-rtop { display: flex; align-items: center; gap: 7px; }
  .mt-branch { font-size: 12px; font-weight: 600; color: #e6edf3; overflow: hidden; text-overflow: ellipsis;
               white-space: nowrap; flex: 1 1 auto; min-width: 0; }
  .mt-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
  .mt-dot.red { background: #f85149; }
  .mt-dot.amber { background: #d29922; }
  .mt-dot.green { background: #3fb950; }
  .mt-dot.gray { background: #6e7681; }
  .mt-meta { margin-top: 3px; font-size: 10px; color: #8b949e; }
  .mt-btn { flex: 0 0 auto; font-size: 11px; padding: 4px 9px; border-radius: 6px; cursor: pointer;
            background: #21262d; border: 1px solid #30363d; color: #c9d1d9; }
  .mt-btn:hover { background: #262c36; }
  .mt-btn:disabled { opacity: .5; cursor: default; }
  .mt-auto { padding: 8px 12px; font-size: 10px; color: #8b949e; border-top: 1px solid #21262d; }

  /* ── T49 (WO-MERGE-TRAY-DETAIL): the row detail drawer. A tray row is now CLICKABLE and opens an
     inline drawer that REPLACES the list inside the same fixed panel — not a second floating layer.
     On a phone that matters: the tray is already width-capped to the viewport and scrolls its own
     body, so the drawer inherits exactly that behaviour and needs no separate mobile treatment.
     Same dark palette and same 10-12px type scale as the list it slides over. */
  .mt-row.clickable { cursor: pointer; }
  .mt-row.clickable:hover { background: #1a2029; }
  .mt-row.held { opacity: .72; }
  .mt-chip { flex: 0 0 auto; font-size: 9px; font-weight: 700; letter-spacing: .3px; text-transform: uppercase;
             padding: 2px 5px; border-radius: 4px; background: #21262d; color: #8b949e; border: 1px solid #30363d; }
  .mt-chip.hold { background: #3a2d0c; border-color: #6b5116; color: #e3b341; }
  .mt-chip.red { background: #3d1518; border-color: #6e2427; color: #ff7b72; }
  .mt-chip.green { background: #12261a; border-color: #1f462b; color: #56d364; }
  .mt-dhead { position: sticky; top: 0; z-index: var(--z-raised); display: flex; align-items: flex-start; gap: 8px;
              padding: 10px 12px; background: #161b22; border-bottom: 1px solid #21262d; }
  .mt-dback { flex: 0 0 auto; background: #21262d; border: 1px solid #30363d; color: #c9d1d9; border-radius: 6px;
              font-size: 11px; padding: 4px 8px; min-height: 44px; cursor: pointer; }
  .mt-dback:active { background: #2a3240; }
  .mt-dtitle { flex: 1 1 auto; min-width: 0; }
  .mt-dtitle h3 { margin: 0; font-size: 12.5px; color: #e6edf3; line-height: 1.35; }
  .mt-dtitle .sub { font-size: 10px; color: #8b949e; margin-top: 2px; word-break: break-all; }
  .mt-dsec { padding: 9px 12px; border-bottom: 1px solid #1b2129; }
  .mt-dsec:last-child { border-bottom: none; }
  .mt-dlabel { font-size: 9px; text-transform: uppercase; letter-spacing: .4px; color: #6e7681; margin-bottom: 4px; }
  .mt-dtext { font-size: 11px; color: #c9d1d9; line-height: 1.45; }
  .mt-badges { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
  .mt-files { margin-top: 5px; max-height: 210px; overflow-y: auto; }
  .mt-file { display: flex; align-items: center; gap: 8px; font-size: 10.5px; padding: 2px 0; }
  .mt-fpath { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
              color: #c9d1d9; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; direction: rtl; text-align: left; }
  .mt-fadd { flex: 0 0 auto; color: #3fb950; }
  .mt-fdel { flex: 0 0 auto; color: #f85149; }
  .mt-unavail { font-size: 10px; color: #6e7681; font-style: italic; }
  .mt-actions { display: flex; flex-wrap: wrap; gap: 6px; padding: 10px 12px; border-bottom: 1px solid #1b2129;
                background: #0d1117; }
  .mt-act { flex: 0 0 auto; font-size: 11px; font-weight: 600; padding: 6px 10px; min-height: 44px; border-radius: 6px;
            cursor: pointer; background: #21262d; border: 1px solid #30363d; color: #c9d1d9; }
  .mt-act:hover:not(:disabled) { background: #262c36; }
  .mt-act:active:not(:disabled) { background: #171b21; }
  .mt-act:disabled { opacity: .45; cursor: not-allowed; }
  .mt-act.go { background: #238636; border-color: #2ea043; color: #fff; }
  .mt-act.go:hover:not(:disabled) { background: #2ea043; }
  .mt-act.go:active:not(:disabled) { background: #1c6e2c; }
  .mt-act.danger { background: #21262d; border-color: #6e2427; color: #ff7b72; }
  .mt-act.danger:hover:not(:disabled) { background: #3d1518; }
  .mt-act.danger:active:not(:disabled) { background: #2a0f11; }
  /* The inline confirm — a Discard NEVER fires on the first click. */
  .mt-confirm { padding: 9px 12px; background: #241416; border-top: 1px solid #6e2427; border-bottom: 1px solid #6e2427; }
  .mt-confirm .q { font-size: 11px; color: #ffb3ad; line-height: 1.45; }
  .mt-confirm .btns { display: flex; gap: 6px; margin-top: 7px; }
  .mt-confirm .yes { background: #da3633; border: 1px solid #f85149; color: #fff; font-size: 11px; font-weight: 700;
                     padding: 6px 10px; border-radius: 6px; cursor: pointer; }
  .mt-diff { margin-top: 6px; max-height: 320px; overflow: auto; background: #0d1117; border: 1px solid #21262d;
             border-radius: 6px; padding: 8px; font-size: 10px; line-height: 1.4; white-space: pre;
             font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: #c9d1d9; }
  .mt-err { padding: 9px 12px; font-size: 11px; color: #ff7b72; }

  /* ── T71 (merge-tray-truth) ────────────────────────────────────────────────
     Two additions, both about saying what git actually says:
       .mt-red*     — a genuinely red branch NAMES the tests it broke and offers "Re-run gate".
                      The old row put a Merge button on it that the server would refuse.
       .mt-gitline  — the base the tray judged everything against, so "why is this row here"
                      always has a visible answer (and a stale remote-tracking ref says so). */
  .mt-red { margin-top: 6px; padding: 7px 8px; background: #241416; border: 1px solid #6e2427; border-radius: 6px; }
  .mt-redhead { font-size: 10.5px; font-weight: 700; color: #ff7b72; }
  .mt-redtest { margin-top: 3px; font-size: 10px; color: #ffb3ad; line-height: 1.4;
                font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mt-redfile { color: #d98a86; opacity: .8; }
  .mt-redmore { margin-top: 3px; font-size: 9.5px; color: #d98a86; font-style: italic; }
  .mt-rerun { flex: 0 0 auto; font-size: 11px; font-weight: 600; padding: 4px 9px; min-height: 44px; border-radius: 6px;
              cursor: pointer; background: #3a2d0c; border: 1px solid #6b5116; color: #e3b341; }
  .mt-rerun:hover:not(:disabled) { background: #4a3a10; }
  .mt-rerun:active:not(:disabled) { background: #2c2209; }
  .mt-gitline { padding: 6px 12px; font-size: 9.5px; color: #6e7681; background: #0d1117;
                border-bottom: 1px solid #21262d; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  .mt-gitline.warn { color: #e3b341; }
  /* T104: git truth IS present (via the GitHub API) — a quiet positive, not a shout. */
  .mt-gitline.ok { color: #3fb950; }
  .mt-age { flex: 0 0 auto; font-size: 9.5px; color: #6e7681; }

  /* T79 (WO-guided-merge-flow): recommended row, global lock, and running-state styling. */
  .mt-row.recommended { background: #12261a; border-left: 3px solid #3fb950; }
  .mt-row.recommended:hover { background: #1a3322; }
  .mt-row.locked { opacity: .55; }
  .mt-reason { font-size: 10px; color: #56d364; margin-top: 4px; }
  .mt-waits { font-size: 10px; color: #e3b341; margin-top: 2px; }
  .mt-merging { font-size: 10px; color: #58a6ff; margin-top: 2px; }
  .mt-head-actions { display: flex; gap: 6px; }
  .mt-doall { flex: 0 0 auto; background: #1f6feb; border: 1px solid #388bfd; color: #fff; font-size: 11px;
              font-weight: 600; padding: 6px 10px; border-radius: 6px; cursor: pointer; white-space: nowrap; }
  .mt-doall:disabled { background: #21262d; border-color: #30363d; color: #6e7681; cursor: default; }

  /* ── T94 (merge-tray-popup-redesign) ───────────────────────────────────────
     The tray was the #1 bottleneck: a full-screen wall of rows with grey buttons, and clicking
     Merge while another merge ran threw HTTP 423 and jammed the board. This is the popup rebuild:
       .mt-strip  — an ALWAYS-VISIBLE status row. Idle: "N branches ready". Busy: "Merging X…
                    please wait" + a progress bar. It is the one place the lock state is stated.
       .mt-card   — one branch, one plain-words state, one obvious action. Gate-green cards get a
                    SOLID GREEN Merge; a card whose gate is still running gets a spinner and NO
                    button at all; an empty build gets Dismiss.
       .mt-card.top — the recommended card. 2px accent border + a "Start here" badge; when it
                    lands the badge cascades to whatever is on top next.
       [disabled] Merge buttons read "Waiting…" — while the REAL backend lane is busy every other
                    card's action is physically unclickable, so a second submit (and its 423) is
                    impossible from this UI. */
  .mt-x { flex: 0 0 auto; min-width: 44px; min-height: 44px; border-radius: 8px; background: #21262d;
          border: 1px solid #30363d; color: #c9d1d9; font-size: 14px; line-height: 1; cursor: pointer; }
  .mt-x:hover { background: #262c36; color: #e6edf3; }
  .mt-x:active { background: #171b21; }
  .mt-strip { flex: 0 0 auto; padding: 9px 12px; background: #0d1117; border-bottom: 1px solid #21262d; }
  .mt-strip-line { display: flex; align-items: center; gap: 7px; font-size: 11.5px; color: #c9d1d9; line-height: 1.35; }
  .mt-strip.busy .mt-strip-line { color: #79c0ff; font-weight: 600; }
  .mt-bar { margin-top: 7px; height: 4px; border-radius: 3px; background: #21262d; overflow: hidden; }
  .mt-bar > i { display: block; height: 100%; width: 34%; border-radius: 3px;
                background: linear-gradient(90deg, #1f6feb, #79c0ff);
                animation: mt-slide 1.3s ease-in-out infinite; }
  @keyframes mt-slide { 0% { transform: translateX(-110%); } 100% { transform: translateX(330%); } }
  .mt-spin { flex: 0 0 auto; display: inline-block; width: 11px; height: 11px; border-radius: 50%;
             border: 2px solid #30363d; border-top-color: #58a6ff; animation: mt-spin 0.8s linear infinite; }
  @keyframes mt-spin { to { transform: rotate(360deg); } }
  .mt-card { margin: 8px 10px; padding: 10px 11px; min-height: 44px; border-radius: 10px; background: #0d1117;
             border: 1px solid #21262d; cursor: pointer; }
  .mt-card:hover { background: #11171f; }
  .mt-card:active { background: #161c25; }
  .mt-card.top { border: 2px solid #2ea043; background: #0f1c14; padding: 9px 10px; }
  .mt-card.top:hover { background: #122418; }
  .mt-card.top:active { background: #0d1e15; }
  .mt-card.held { opacity: .72; }
  .mt-card.waiting { opacity: .62; }
  .mt-ctop { display: flex; align-items: center; gap: 7px; }
  .mt-cname { flex: 1 1 auto; min-width: 0; font-size: 12.5px; font-weight: 700; color: #e6edf3;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mt-start { flex: 0 0 auto; font-size: 9px; font-weight: 800; letter-spacing: .4px; text-transform: uppercase;
              padding: 2px 7px; border-radius: 20px; background: #238636; color: #fff; white-space: nowrap; }
  .mt-csub { margin-top: 3px; font-size: 10px; color: #8b949e; overflow: hidden; text-overflow: ellipsis;
             white-space: nowrap; }
  .mt-cstate { margin-top: 6px; display: flex; align-items: center; gap: 6px; font-size: 11px; color: #8b949e; }
  .mt-cstate.ready { color: #56d364; }
  .mt-cstate.testing { color: #79c0ff; }
  .mt-cstate.rerun { color: #e3b341; }
  .mt-cstate.empty { color: #6e7681; }
  .mt-cstate.unknown { color: #e3b341; }
  .mt-cact { margin-top: 8px; display: flex; gap: 6px; }
  .mt-go { flex: 1 1 auto; min-height: 44px; background: #238636; border: 1px solid #2ea043; color: #fff;
           font-size: 12.5px; font-weight: 700; padding: 9px 12px; border-radius: 8px; cursor: pointer; }
  .mt-go:hover:not(:disabled) { background: #2ea043; }
  .mt-go:active:not(:disabled) { background: #1c6e2c; }
  .mt-go:disabled { background: #21262d; border-color: #30363d; color: #6e7681; cursor: not-allowed; }
  .mt-cact .mt-rerun { flex: 1 1 auto; min-height: 44px; font-size: 12.5px; padding: 9px 12px; border-radius: 8px; }
  .mt-cact .mt-rerun:disabled { background: #21262d; border-color: #30363d; color: #6e7681; cursor: not-allowed; }
  .mt-dismiss { flex: 1 1 auto; min-height: 44px; background: #21262d; border: 1px solid #30363d; color: #c9d1d9;
                font-size: 12.5px; font-weight: 600; padding: 9px 12px; border-radius: 8px; cursor: pointer; }
  .mt-dismiss:hover { background: #262c36; }
  .mt-dismiss:active { background: #171b21; }
  .mt-dismiss.yes { background: #da3633; border-color: #f85149; color: #fff; font-weight: 700; }
  .mt-dismiss.yes:active { background: #a92825; }
  .mt-cancel { flex: 0 0 auto; min-height: 44px; background: #21262d; border: 1px solid #30363d; color: #c9d1d9;
               font-size: 12.5px; padding: 9px 12px; border-radius: 8px; cursor: pointer; }
  .mt-cancel:active { background: #171b21; }
  .mt-ask { margin-top: 7px; font-size: 10.5px; color: #ffb3ad; line-height: 1.4; }
  .mt-foot { flex: 0 0 auto; padding: 10px 12px; border-top: 1px solid #21262d; background: #161b22; }
  .mt-allbtn { width: 100%; min-height: 40px; background: #1f6feb; border: 1px solid #388bfd; color: #fff;
               font-size: 12.5px; font-weight: 700; padding: 10px; border-radius: 8px; cursor: pointer; }
  .mt-allbtn:hover:not(:disabled) { background: #388bfd; }
  .mt-allbtn:disabled { background: #21262d; border-color: #30363d; color: #6e7681; cursor: not-allowed; }
  .mt-foothint { margin-top: 6px; font-size: 9.5px; color: #6e7681; text-align: center; line-height: 1.4; }
  .mt-grouphead { padding: 9px 12px 2px; font-size: 10px; font-weight: 700; letter-spacing: .4px;
                  text-transform: uppercase; color: #6e7681; }
  @media (max-width: 560px) {
    /* IB-143: the structural guard already offsets the tray; keep only the small breathing pad. */
    .merge-tray { padding: 6px 6px 8px; }
    .mt-modal { border-radius: 12px; }
    .mt-card { margin: 7px 8px; }
  }

  /* T79 (WO-guided-merge-flow): full-screen completion takeover. Green for success, red for failure.
     IB-130 (green-means-it-landed): a THIRD tone, `.partial` (amber) — some of the batch landed and
     some did not, which is neither green ("it worked") nor red ("nothing did"). The colour now
     follows the real per-branch outcome record (src/lib/mergeSignalRecord.js), never the fact that
     the signal reached a terminal status — see mergeSignalVerdict() below. */
  #guided-merge-takeover {
    display: none; position: fixed; inset: 0; z-index: var(--z-emergency-secondary);
    background: rgba(10, 14, 10, 0.92); backdrop-filter: blur(4px);
    align-items: center; justify-content: center; padding: 20px;
  }
  #guided-merge-takeover.show { display: flex; }
  .gmt-card {
    width: 100%; max-width: 520px; background: #161b22; border: 2px solid #30363d; border-radius: 14px;
    box-shadow: 0 14px 40px rgba(0,0,0,.55); padding: 22px 20px; color: #e6edf3;
  }
  .gmt-card.success { border-color: #2ea043; }
  .gmt-card.partial { border-color: #e3b341; }
  .gmt-card.error { border-color: #f85149; }
  .gmt-kicker { font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: 8px; }
  .gmt-card.success .gmt-kicker { color: #3fb950; }
  .gmt-card.partial .gmt-kicker { color: #e3b341; }
  .gmt-card.error .gmt-kicker { color: #f85149; }
  .gmt-title { font-size: 20px; font-weight: 700; line-height: 1.35; margin-bottom: 10px; }
  .gmt-detail { font-size: 14px; line-height: 1.5; color: #c9d1d9; margin-bottom: 16px; }
  .gmt-card.partial .gmt-detail.reason { color: #e3b341; }
  .gmt-card.error .gmt-detail.reason { color: #f85149; }
  .gmt-next { background: #0d1117; border: 1px solid #21262d; border-radius: 10px; padding: 14px; margin-bottom: 18px; }
  .gmt-next-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: #8b949e; margin-bottom: 6px; }
  .gmt-next-title { font-size: 15px; font-weight: 600; color: #e6edf3; margin-bottom: 4px; }
  .gmt-next-reason { font-size: 12px; color: #56d364; }
  .gmt-acts { display: flex; gap: 8px; flex-wrap: wrap; }
  .gmt-acts button { flex: 1 1 auto; min-height: 46px; font-size: 14px; font-weight: 600; border-radius: 8px; border: none; cursor: pointer; }
  .gmt-merge { background: #2ea043; color: #fff; }
  .gmt-retry { background: #e3b341; color: #1a1200; }
  .gmt-dismiss { background: transparent; color: #8b949e; border: 1px solid #30363d !important; }

  /* ── WO-HEADER-V2 (task 230, req 2): thin color-coded quick-action strip — row 2 of the header.
     flex-basis:100% (see `header` flex-wrap) always drops this onto its own full-width line under
     row 1, at every width, not just mobile. Small ~30px buttons (icon + tiny label), horizontally
     scrollable so a crowded strip never wraps/clips — it just scrolls, same as it does on a
     phone (req 4). Colour-coded by category via a left accent bar: green=build/go, amber=ops
     (careful), purple=AI/brains, blue=navigate; the legend dots + each button's title tooltip
     both explain the coding (req 2 — "subtly or on hover"). */
  /* TIB-037 (B2/B4) · the strip WRAPS on desktop instead of scrolling sideways. With the coder
     cards carrying real visual weight the chips + fleet no longer fit one line on a laptop, and
     the old `nowrap; overflow-x:auto` answer was to push the printer tile off the right edge —
     which B4 forbids by name. Wrapping means the fleet claims its own line exactly when it needs
     one and folds back beside the chips when nothing is running, which is B2's "scale with what
     is running" for free. The ≤430px block still forces nowrap: one scrolling row is right on a
     phone, where the coder tiles are hidden behind #hud-mobile-status anyway. */
  .action-strip { flex-basis: 100%; display: flex; align-items: center; gap: 6px; overflow-x: auto;
                   -webkit-overflow-scrolling: touch; padding: 2px 0 1px; scrollbar-width: thin;
                   flex-wrap: wrap; row-gap: 6px; }
  .action-strip::-webkit-scrollbar { height: 4px; }
  .astrip-btn { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; white-space: nowrap;
                min-height: 28px; padding: 4px 9px; border-radius: 6px; font-size: 11px; font-weight: 600;
                cursor: pointer; background: #161b22; color: #c9d1d9; border: 1px solid #30363d;
                border-left: 3px solid transparent; }
  .astrip-btn:hover { background: #1c2129; }
  .astrip-ic { font-size: 13px; line-height: 1; }
  .astrip-lb { line-height: 1; }
  .astrip-green  { border-left-color: #3fb950; }
  .astrip-amber  { border-left-color: #d29922; }
  .astrip-purple { border-left-color: #a371f7; }
  .astrip-blue   { border-left-color: #58a6ff; }
  /* WO-HEADER-STREAMLINE-CODER-CAP: accent-glow active state for HUD chips. */
  .astrip-btn.active { background: #1c2129; border-color: var(--astrip-glow, #58a6ff);
                       box-shadow: 0 0 10px var(--astrip-glow, rgba(88,166,255,.55)); }
  .astrip-green.active  { --astrip-glow: rgba(63,185,80,.65); }
  .astrip-amber.active  { --astrip-glow: rgba(210,153,34,.65); }
  .astrip-purple.active { --astrip-glow: rgba(163,113,247,.65); }
  .astrip-blue.active   { --astrip-glow: rgba(88,166,255,.65); }
  .astrip-spacer { flex: 1 1 auto; min-width: 6px; }
  /* Provider pills live inside the action strip now, pushed right by .astrip-spacer. */
  .action-strip .usage-bars { flex: 0 1 auto; flex-basis: auto; justify-content: flex-end; flex-wrap: nowrap;
                              gap: 8px; padding: 0; overflow-x: visible; }
  /* Icon-only fallback when space is tight (req 2) — merged into the single ≤430px block further
     down (search "phone top bar") rather than a second standalone query here, so code that slices
     the doc between the 759px and 430px media markers (see mobile-sweep-task197.test.js) still
     finds the ONE intended 430px block, not this one first. The strip stays real-tap-friendly
     (28px+) even with labels hidden. */

  /* WO-HEADER-USAGE-AND-KEBAB (task 237, req 1): compact per-provider WEEKLY usage bars — small
     horizontal bars, not a widget. Colour comes from --ubar-c, a custom property set per-bar
     inline by renderUsageBars() (Claude=orange, GLM=red, any other subscription provider gets a
     deterministic colour from its id so several future providers stay distinguishable from each
     other, not just from Claude/GLM) -- .ubar-fill/.ubar-pct just inherit it, no per-provider CSS
     classes to maintain as new providers connect (req 2). near-cap/exhausted status layers on as
     an outer-border colour so the provider hue stays legible either way.
     WO-HEADER-SIDEBAR-UX (task 452, req 2): the bars used to sit at the right end of the action
     strip and share ITS single horizontal scroll with ~10 action buttons — on a phone the providers
     got pushed past the right edge and looked permanently cut off. They now own their full-width
     header row (flex-basis:100%, a sibling of the action strip) and scroll WITHIN that row, so they
     always fit the viewport and never ride off-screen (justify-content:flex-end keeps them
     right-aligned on a wide desktop where they don't need to scroll). */
  .usage-bars { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; flex-basis: 100%;
                justify-content: flex-end; overflow-x: auto; flex-wrap: wrap; -webkit-overflow-scrolling: touch;
                padding: 3px 0 2px; scrollbar-width: thin; }
  .usage-bars::-webkit-scrollbar { height: 4px; }
  .ubar { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; white-space: nowrap;
          min-height: 28px; padding: 4px 8px; border-radius: 6px; font: inherit; font-size: 11px;
          font-weight: 600; cursor: pointer; background: #161b22; color: #c9d1d9; border: 1px solid #30363d; }
  .ubar:hover { background: #1c2129; }
  .ubar-lb { line-height: 1; opacity: .85; }
  .ubar-track { width: 30px; height: 5px; border-radius: 3px; background: #21262d; overflow: hidden; display: inline-block; }
  .ubar-fill { display: block; height: 100%; border-radius: 3px; background: var(--ubar-c, #58a6ff); }
  .ubar-pct { font-variant-numeric: tabular-nums; min-width: 24px; text-align: right; line-height: 1; color: var(--ubar-c, #c9d1d9); }
  .ubar-near      { border-color: #d29922; }
  .ubar-exhausted { border-color: #f85149; }
  .ubar-exhausted .ubar-pct { color: #f85149; }

  /* WO-HEADER-STREAMLINE-CODER-CAP: running-coders cluster sits between the left HUD chips and
     the right model-quota cluster inside the single row-2 action strip. It shows only actively
     running primary coders; idle slots and offline fallback slots are hidden. */
  /* TIB-037 (B2) · `flex: 0 0 auto` so the fleet WRAPS to its own line rather than being squeezed.
     The old `0 1 auto` + `min-width: 0` let it shrink to nothing, which is how six coders and a
     printer ended up compressed into a 200px sliver beside the chips. */
  .coder-cluster { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
  .coder-cluster-head { font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
                        color: #8b949e; white-space: nowrap;
                        font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
  .coder-cluster-head b { color: #c9d1d9; font-weight: 700; }
  .coder-cluster-cards { display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
                         scrollbar-width: none; }
  .coder-cluster-cards::-webkit-scrollbar { display: none; }
  /* WO-PRINTER-CARD-DEDUPE req 1: the coder strip is now split into two OWNED slots so the two
     renderers can never clobber each other's nodes. renderCoderCards/renderCoderCluster own
     .strip-coders and rewrite it freely; renderBambuStrip owns .strip-printers and patches its
     cards in place. `display: contents` makes both slots layout-invisible, so every card is still
     a direct flex item of the strip and every existing `.coder-cluster-cards .coder-card` rule
     keeps matching (they're descendant selectors). */
  .strip-coders, .strip-printers { display: contents; }
  /* TIB-037 (B1) · the strip's coder tiles are no longer compact chips — the rules that made them
     28px tall are gone, and their new weight is defined next to the base .coder-card rule further
     down (search "SIZE IS A CLAIM ABOUT IMPORTANCE"), where the card's other dimensions live. */
  /* WO-MOBILE-HUD-STRIP: the compact phone-only status line. Hidden on desktop (the full coder
     tiles + printer card render here); revealed at ≤430px, where it REPLACES them — see the rule
     inside the ≤430px block. */
  .hud-mobile-status { display: none; }
  /* IB-143 → IB-1459 (job 3): printer status is as glanceable as coder status. The cluster mirrors
     the coder cluster and shares the same action-strip row; it is hidden on the smallest phones
     where the compact hud-mobile-status line carries the same facts. It used to be bare text
     beside a card with no shared frame — "reads like something that fell off a shelf" — so it now
     gets its own bordered slot with the same breathing room the coder cluster's cards get, and the
     busy/idle count lives INSIDE that slot rather than floating loose next to it. */
  .printer-cluster { display: flex; align-items: center; gap: 6px; flex: 0 0 auto;
                     padding: 4px 8px; margin: 2px 0; border: 1px solid #21262d; border-radius: 8px;
                     background: #10141b; }
  .printer-cluster-head { font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
                          color: #8b949e; white-space: nowrap;
                          font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
  .printer-cluster-head b { color: #c9d1d9; font-weight: 700; }
  .printer-cluster-cards { display: flex; align-items: flex-start; gap: 8px; flex: 0 0 auto;
                           scrollbar-width: none; }
  .printer-cluster-cards::-webkit-scrollbar { display: none; }

  /* ── IB-1131 · FOUR TABS + MORE ─────────────────────────────────────────────────────────────
     The header action strip is now primary navigation only: Work, Map, My Day, and a
     More control for Photo, Calendar and the Ops detail. The old .astrip-btn IDs are kept in the
     markup (hidden) so existing tests that assert on IDs/wiring keep passing; the visible buttons
     reuse .astrip-btn styling but use .tab-lb labels so the new "Map" label does not collide with
     tests that forbid an .astrip-lb of "Map". */
  .main-tabs { display: contents; }
  .main-tabs .astrip-btn { flex: 1 1 auto; justify-content: center; }
  .tab-lb { line-height: 1; }
  /* IB-1626 (job 4) · #nav-toggle-btn only exists to replace the row above with a dropdown on a
     phone (see the ≤759px block) — desktop keeps the plain row via .main-tabs's display:contents
     above and never shows this trigger. */
  .nav-toggle-btn { display: none; }
  /* IB-1434 · #more-menu's absolute position resolves against the nearest positioned ancestor —
     that has to be .more-tab-wrap (a real box), not .tab-btn-more (display:contents unwraps
     .main-tabs, so a relative .tab-btn-more with #more-menu as its SIBLING, not descendant, did
     nothing; the menu fell back to the sticky <header> as its containing block and rendered off
     in the corner instead of under the button). */
  .main-tabs .more-tab-wrap { flex: 1 1 auto; display: flex; position: relative; }
  /* Spacers were used to center the coder cluster; the cluster is now in the Work tab. */
  .astrip-spacer { display: none !important; }
  .more-menu { display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: var(--z-dropdown);
               background: #161b22; border: 1px solid #30363d; border-radius: 8px; min-width: 170px;
               box-shadow: 0 10px 28px rgba(0,0,0,.55); padding: 6px; }
  .more-menu.on { display: block; }
  .more-menu button { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
                      background: none; border: none; color: #c9d1d9; padding: 8px 10px;
                      border-radius: 6px; font-size: 13px; cursor: pointer; }
  .more-menu button:hover { background: #21262d; }
  /* Hide the legacy action-strip buttons on desktop. They remain in the DOM for test assertions. */
  #astrip-ops, #astrip-production, #astrip-printqueue, #astrip-coding,
  #astrip-photo, #astrip-calendar, #astrip-newtask, #astrip-merge,
  #astrip-map, #astrip-dayshutdown { display: none !important; }
  /* IB-UX-1 · the coder/printer cluster is part of the always-visible top strip. The Work tab keeps
     its own detailed views; this header mirror stays visible on the main board shell. */

  /* Tab content panes live inside <main> and replace the default board/chat view when active. */
  .tab-content { display: none; flex: 1 1 auto; flex-direction: column; min-width: 0; overflow-y: auto; }
  .tab-content.active { display: flex; }
  .tab-content-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px;
                      border-bottom: 1px solid #21262d; flex: 0 0 auto; }
  .tab-content-head h2 { margin: 0; font-size: 16px; flex: 1 1 auto; }
  .tab-content-body { padding: 16px; flex: 1 1 auto; overflow-y: auto; }
  /* When a main tab is active, suppress the default board/chat view inside <main>. */
  .main.tab-active .filterbar,
  .main.tab-active .pausebanner,
  .main.tab-active .opsbanner,
  .main.tab-active .chatpanel-wrapper,
  .main.tab-active .wrap { display: none !important; }

  /* Right Now tab */
  .rn-quiet { color: #8b949e; font-size: 15px; padding: 20px 4px; }
  .rn-groups { display: flex; flex-direction: column; gap: 12px; }

  /* Work tab */
  .work-subtabs { display: flex; gap: 4px; flex: 0 0 auto; }
  .work-subtab { background: #161b22; border: 1px solid #30363d; color: #c9d1d9;
                 padding: 5px 12px; border-radius: 6px; font-size: 12px; font-weight: 600;
                 cursor: pointer; }
  .work-subtab.active { background: #1f6feb; border-color: #1f6feb; color: #fff; }
  .work-pane { display: none; flex: 1 1 auto; flex-direction: column; min-width: 0; overflow-y: auto; }
  .work-pane.active { display: flex; }
  .work-empty { color: #8b949e; padding: 24px 4px; }
  .work-summary { margin-bottom: 12px; }
  .work-summary-line { color: #c9d1d9; font-size: 14px; line-height: 1.5; }
  /* IB-141 · Tasks lives inside the Work screen below the board. The board pane is capped so the
     Tasks panel and the sub-tabs stay reachable without scrolling; the Tasks body is capped so it
     never grows to the height of the whole list. */
  .work-pane { display: none; flex: 0 0 auto; flex-direction: column; min-width: 0; overflow-y: auto; }
  .work-pane.active { display: flex; max-height: 360px; }
  .work-tasks-panel { flex: 0 0 auto; min-width: 0; }
  .work-tasks-panel .panel-tasks .body { max-height: 240px; overflow: auto; overscroll-behavior: contain; }
  .work-tasks-toggle { background: #21262d; border: 1px solid #30363d; color: #58a6ff;
                       border-radius: 6px; padding: 3px 10px; font-size: 12px; cursor: pointer;
                       margin-left: 8px; }
  .work-tasks-toggle:hover { background: #30363d; }
  /* IB-1459 (job 5) · collapse the whole Tasks strip so the kanban pane above it gets its height
     back. Same pill as .work-tasks-toggle, its own control so "show all vs. live-only" and
     "hide the strip entirely" stay two separate choices. */
  .work-tasks-collapse { background: #21262d; border: 1px solid #30363d; color: #8b949e;
                         border-radius: 6px; padding: 3px 10px; font-size: 12px; cursor: pointer;
                         margin-left: 6px; }
  .work-tasks-collapse:hover { background: #30363d; }
  /* Collapsed: only the "Tasks" header (with this control) stays — the strip is never deleted,
     just given up until asked for again — and the board pane above reclaims the height the 360px
     cap was reserving for it. Selectors carry extra specificity on purpose so they win over both
     the base rule above and the ≤759px mobile override further down regardless of source order. */
  body.work-tasks-collapsed .work-tasks-panel .panel-tasks .repo-tabs,
  body.work-tasks-collapsed .work-tasks-panel .panel-tasks .repo-legend,
  body.work-tasks-collapsed .work-tasks-panel .panel-tasks .body { display: none; }
  body.work-tasks-collapsed .work-pane.active { max-height: none; }

  /* Ops detail as a closable side panel, not a viewport takeover. It keeps class="modal" in markup
     so tests that assert on #ops-modal using .modal still pass, but .ops-panel overrides the fixed
     full-screen behaviour to a right-hand drawer. */
  .ops-panel { display: none; position: fixed; inset: 0 auto 0 calc(100% - 420px); width: 420px;
               max-width: 100%; padding: 0; background: rgba(0,0,0,0); justify-content: flex-end;
               align-items: stretch; z-index: var(--z-modal); }
  .ops-panel.on { display: flex; }
  .ops-panel .sheet { width: 100%; max-width: none; max-height: none; border-radius: 0;
                      border-left: 1px solid #30363d; border-top: none; border-bottom: none;
                      border-right: none; display: flex; flex-direction: column; }
  .ops-panel .sheet-head { flex: 0 0 auto; }
  .ops-panel .ops-grid { grid-template-columns: 1fr; overflow-y: auto; }
  .ops-panel-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: var(--z-scrim);
                        display: none; }
  body.ops-open .ops-panel-backdrop { display: block; }

  /* ── TIB-037 (A4) · the live coder output panel ──────────────────────────────────────────────
     A terminal, not a dialog: monospace, near-black, and it opens as a row of the header under the
     cards rather than as a layer over the board. `display:none` collapsed means an unopened panel
     contributes exactly nothing to the header's height, so the strip above it does not move when
     it opens and the printer tile never leaves the viewport (B4). */
  .coder-live { display: none; flex-basis: 100%; flex-direction: column; margin: 4px 0 2px;
                border: 1px solid #30363d; border-radius: 8px; background: #010409; overflow: hidden; }
  .coder-live.on { display: flex; }
  .cl-head { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 6px 10px;
             background: #0d1117; border-bottom: 1px solid #21262d; }
  .cl-dot { width: 8px; height: 8px; border-radius: 50%; background: #6e7681; flex: 0 0 auto; }
  .cl-dot.running { background: #3fb950; box-shadow: 0 0 6px #3fb950; animation: cc-pulse 1.4s ease-in-out infinite; }
  .cl-dot.quiet { background: #3fb950; box-shadow: 0 0 6px #3fb950; opacity: .75; }
  .cl-dot.hung, .cl-dot.blocked { background: #f85149; box-shadow: 0 0 6px #f85149; }
  .cl-dot.finished { background: #58a6ff; }
  .cl-title { font-size: 12px; font-weight: 700; color: #e6edf3; white-space: nowrap; }
  /* The honest state (A5) lives here and is never allowed to be empty — "starting up — no output
     yet", "finished (exit 0)", "no run captured for this coder yet". */
  .cl-sub { font-size: 11px; color: #8b949e; flex: 1 1 auto; min-width: 0; overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }
  .cl-jump { display: none; flex: 0 0 auto; font: inherit; font-size: 10px; font-weight: 700;
             padding: 3px 8px; border-radius: 12px; cursor: pointer;
             background: #1f6feb; color: #fff; border: none; }
  /* Only offered when he has actually scrolled away from the bottom — a permanent "jump to live"
     on a panel that is already live is noise. */
  .coder-live.scrolled-up .cl-jump { display: inline-flex; }
  .cl-x { flex: 0 0 auto; background: none; border: none; color: #8b949e; font-size: 13px;
          cursor: pointer; padding: 2px 4px; line-height: 1; }
  .cl-x:hover { color: #e6edf3; }
  .cl-out { flex: 1 1 auto; margin: 0; height: 260px; max-height: 42vh; overflow-y: auto;
            overflow-x: hidden; padding: 8px 10px 8px 14px; background: #010409; color: #c9d1d9;
            font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
            font-size: 13px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
            -webkit-overflow-scrolling: touch; }
  /* IB-119 · horizontal drag handle to resize the terminal output area. */
  .cl-resize { flex: 0 0 auto; height: 6px; background: #21262d; cursor: ns-resize; border-top: 1px solid #30363d; }
  .cl-resize:hover, body.cl-resizing .cl-resize { background: #1f6feb; }
  /* A5 · never a blank black box. When there is nothing to show, the panel says so in words. */
  .cl-out .cl-empty { color: #8b949e; font-style: italic; }
  .cl-out .cl-note { color: #d29922; }
  /* TIB-051 · CANNOT REACH THE BOX ≠ THE CODER IS BROKEN. Amber, the same vocabulary TIB-031 settled
     on for the printer, and the head is tinted so the state is legible before a word is read. Red is
     reserved for the thing being watched being in trouble. */
  .cl-dot.lost { background: #d29922; box-shadow: 0 0 6px #d29922; }
  .coder-live.cl-stale .cl-head { background: #1c1710; border-bottom-color: #7d5b1a; }
  .coder-live.cl-stale .cl-sub { color: #d29922; }
  /* The moment contact was lost, drawn once, so the scrollback above it is visibly history rather
     than something that merely stopped moving. */
  .cl-out .cl-note.cl-lost { color: #d29922; border-top: 1px dashed #7d5b1a; margin-top: 4px; padding-top: 4px; }

  /* ── TIB-051 (req 5) · IT WORKS ON HIS PHONE ─────────────────────────────────────────────────
     Brandon reads this on a phone as often as a laptop, and three things were wrong there. The
     honest state sentence — the whole point of the panel when there is nothing to show — was clamped
     to ONE ellipsised line, so "can't reach your Mac…" arrived as "can't reach your…". The terminal
     type was sized for a desktop. And a flick that reached the end of the output chained into the
     PAGE, so scrolling the log scrolled the board out from under it (overscroll-behavior: contain
     stops exactly that; -webkit-overflow-scrolling on .cl-out already gives it momentum).
     Placed AFTER the base rules on purpose — these are overrides and the cascade is the mechanism. */
  @media (max-width: 700px) {
    .cl-head { flex-wrap: wrap; padding: 7px 10px; row-gap: 2px; }
    .cl-title { font-size: 13px; }
    /* The sentence wraps and is read in full. It stays bounded (3 lines) so a long one can never
       push the terminal itself off the screen. */
    .cl-sub { flex: 1 1 100%; order: 3; white-space: normal; overflow: visible; text-overflow: clip;
              font-size: 12px; line-height: 1.35; max-height: 3.2em; }
    .cl-jump { order: 2; font-size: 11px; padding: 4px 10px; }
    .cl-out { font-size: 13px; line-height: 1.5; height: 46vh; max-height: 58vh; padding: 8px 10px; }
  }
  /* Applies on every screen, not just phones: the terminal's scroll is its own and never steals the
     page's. */
  .cl-out { overscroll-behavior: contain; }
  /* ── TIB-084 · the coder output reads like a terminal ───────────────────────────────────────── */
  .cl-line { display: block; padding: 1px 0 1px 10px; text-indent: -10px;
              border-left: 3px solid transparent; white-space: pre-wrap; word-break: break-word; }
  .cl-line.cl-pass { border-left-color: #3fb950; }
  .cl-line.cl-fail { border-left-color: #f85149; font-weight: 700; }
  .cl-line.cl-warn { border-left-color: #d29922; }
  .cl-tag { display: inline-block; font-size: 0; position: relative; vertical-align: baseline;
            margin-right: 6px; line-height: 1; }
  .cl-tag::before { content: attr(data-tag); display: inline-block; font-size: 11px; line-height: 1.2;
                    padding: 1px 5px; border-radius: 4px;
                    border: 1px solid var(--tag-color, #58a6ff);
                    background: var(--tag-bg, rgba(88,166,255,.15));
                    color: var(--tag-color, #58a6ff);
                    user-select: none; vertical-align: middle; white-space: nowrap; }
  .cl-time { display: inline-block; font-size: 0; position: relative; vertical-align: baseline;
             margin-left: 4px; line-height: 1; }
  .cl-time::before { content: attr(data-round); display: inline; font-size: 12px; color: #8b949e;
                     user-select: none; }
  .cl-time[title] { cursor: help; }
  .ansi-bold { font-weight: 700; }
  .ansi-dim { opacity: 0.6; }
  .ansi-underline { text-decoration: underline; }
  .cl-filters { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; padding: 6px 10px;
                background: #0d1117; border-bottom: 1px solid #21262d; }
  .cl-filter { font: inherit; font-size: 11px; padding: 3px 8px; border-radius: 10px; cursor: pointer;
               background: #21262d; color: #c9d1d9; border: 1px solid #30363d; }
  .cl-filter.active { background: #1f6feb; color: #fff; border-color: #1f6feb; }
  .cl-filter-input { flex: 1 1 auto; min-width: 0; font: inherit; font-size: 12px; padding: 3px 8px;
                     border-radius: 10px; background: #010409; color: #c9d1d9; border: 1px solid #30363d; }
  .cl-filter-input:focus { outline: none; border-color: #58a6ff; }
  .cl-fail-count { display: none; flex: 0 0 auto; font-size: 11px; font-weight: 700; padding: 2px 6px;
                   border-radius: 8px; background: #f85149; color: #fff; margin-left: auto; }
  .cl-fail-count.show { display: inline-block; }
  .cl-fail-below { display: none; flex: 0 0 auto; font: inherit; font-size: 10px; font-weight: 700;
                   padding: 3px 8px; border-radius: 12px; cursor: pointer; background: #f85149; color: #fff;
                   border: none; margin-right: 6px; }
  .cl-fail-below.show { display: inline-block; }
  .cl-truncated-notice { color: #8b949e; font-style: italic; padding: 2px 0 4px; }
  .cl-dim { opacity: 0.25; }
  .cl-highlight { background: rgba(210,153,34,.35); border-radius: 2px; color: inherit; }
  /* TIB-051 (req 5) · the phone-only HUD line becomes the way into the panel while a build is
     running. Two classes deep so it beats the ≤430px base rule's padding wherever that sits in the
     file, and a ::after chevron rather than extra words — renderHudMobileStatus's TEXT is the coder
     count and the printer, and it stays exactly that. */
  .hud-mobile-status.hud-watchable { color: #58a6ff; cursor: pointer; padding: 7px 10px;
                                     border-radius: 6px; background: #10192b; }
  .hud-mobile-status.hud-watchable::after { content: ' ▸'; font-weight: 700; }
  /* TIB-073 · work that is not happening, on the one line a phone always shows. */
  .hud-mobile-status.hud-not-working { color: #f85149; font-weight: 700; }
  .hud-mobile-status.hud-watchable:active { background: #16233d; }
  /* IB-1459 (job 2) · the quiet line, shrunk to a chip. It was one full sentence taking a wide
     horizontal strip across the middle of the header; it is `display:none` (via .waiting-chip)
     until there is something waiting, so an empty queue leaves no trace of it on the strip at all
     — same rule as before, just on the wrapper now instead of the bare span. */
  .waiting-chip { display: none; align-items: center; flex: 0 0 auto; position: relative;
                  padding: 4px 8px; border-radius: 6px; background: #161b22; border: 1px solid #30363d;
                  cursor: pointer; user-select: none; }
  .waiting-chip:hover { background: #1c2129; }
  .waiting-chip.show { display: inline-flex; }
  .waiting-line { font-size: 11px; color: #8b949e; white-space: nowrap;
                  overflow: hidden; text-overflow: ellipsis; max-width: 22ch; }
  /* IB-1459 (job 2) · the full sentence, on tap or hover — same popover pattern as
     .fleet-health-popover so the header has one consistent way of saying "here's more". */
  .waiting-chip-popover { display: none; position: absolute; top: calc(100% + 6px); left: 0; z-index: var(--z-dropdown);
                           min-width: 220px; max-width: min(320px, calc(100vw - 32px)); background: #161b22; border: 1px solid #30363d;
                           border-radius: 8px; padding: 10px 12px; box-shadow: 0 10px 28px rgba(0,0,0,.55);
                           font-size: 12px; font-weight: 500; color: #e6edf3; white-space: normal;
                           text-align: left; line-height: 1.4; }
  .waiting-chip-popover.on, .waiting-chip:hover .waiting-chip-popover { display: block; }
  /* TIB-051 · the desk-log sheet's pointer at the live panel (the two show different things). */
  .lc-watch { display: block; width: 100%; margin: 0 0 8px; padding: 9px 12px; border: 1px solid #1f6feb;
              border-radius: 8px; background: #10192b; color: #58a6ff; font: inherit; font-size: 13px;
              font-weight: 600; text-align: left; cursor: pointer; }
  .lc-watch:hover { background: #16233d; }

  /* shell = rail + main. Mobile-first: rail is an off-canvas slide-over. */
  .shell { position: relative; }
  .rail { position: fixed; top: var(--header-h); left: 0; bottom: 0; width: var(--rail-w); z-index: var(--z-chrome-raised);
          background: #11161d; border-right: 1px solid #21262d;
          /* WO-LAUNCHPAD-PIN-BOTTOM: the rail is a flex column so the Dashboard button + Agents
             header stay fixed at top, #agent-list scrolls in the middle, and the Launchpad dock is
             pinned flush to the bottom (flex:none). On a tall screen the agents list fills the freed
             space — no black gap, and the launchpad never floats in the middle. */
          display: flex; flex-direction: column; overflow: hidden; overscroll-behavior: contain;
          transform: translateX(-100%); transition: transform .18s ease; }
  body.rail-open .rail { transform: translateX(0); box-shadow: 0 0 40px rgba(0,0,0,.6); }
  .backdrop { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: var(--z-chrome); }
  body.rail-open .backdrop { display: block; }
  /* IB-143: the header's region is reserved. Every full-screen layer that replaces the board
     starts BELOW the header. This is the structural guard — a future panel must attach here,
     not re-invent its own offset and not raise a z-index to beat the header. */
  .modal, .printers-modal, .production-modal, .printqueue-modal, .coding-modal, .map-modal,
  .rightnow-overlay, .focus-overlay, .morning-overlay, .shutdown-overlay, .scan-overlay, .img-viewer-overlay,
  .merge-tray, #guided-merge-takeover,
  .ops-panel, .ops-panel-backdrop, .tool-rail-backdrop {
    top: var(--header-h) !important;
  }
  .rail h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: #8b949e;
             margin: 0; padding: 14px 14px 8px; display: flex; align-items: center; gap: 8px; flex: none; }
  .rail h2 .collapse { margin-left: auto; background: none; border: none; color: #8b949e; cursor: pointer;
                       font-size: 14px; padding: 2px 6px; }
  /* WO-HEADER-SIDEBAR-UX (task 452, req 4): an always-visible "Dashboard" quick-jump pinned to the
     TOP of the sidebar — Brandon shouldn't have to hunt the horizontally-scrolling header strip
     for the board view. It sits above the Agents list (a STATIC child of the rail, NOT inside
     #agent-list, so the 2s renderRail() rebuild never wipes it) and is position:sticky so it stays
     pinned even when a long desk list is scrolled. Its ACTIVE state is driven PURELY by CSS off the
     existing body.desk-chat-open toggle (present while a desk's chat is open) — lit blue on the
     dashboard, dim while a desk is open — so there is zero new JS on the render path (and nothing
     for the rail-never-blank VM test's minimal $() sandbox to trip on). Tapping it runs the same
     selectAgent('') the header Dashboard button (#allbtn) uses. */
  .rail-dash { display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box;
               margin: 0; padding: 11px 14px; border: none; border-bottom: 1px solid #21262d;
               background: #161b22; color: #c9d1d9; font: inherit; font-size: 13px; font-weight: 600;
               text-align: left; cursor: pointer;
               flex: none; z-index: var(--z-raised); }
  .rail-dash:hover { background: #1c2129; }
  .rail-dash-ic { font-size: 15px; line-height: 1; flex: 0 0 auto; }
  .rail-dash-lbl { flex: 1 1 auto; }
  /* WO-LAUNCHPAD-PIN-BOTTOM: the agents list is the rail's single scroll region — it grows (flex:1)
     to fill every pixel between the Agents header and the pinned Launchpad, so a tall screen leaves
     no black gap and a short one never exposes empty rail below the dock. */
  #agent-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
  /* active (on the dashboard — no desk chat open): blue, reading as the current view, matching the
     header Dashboard pill (.hdr-all.active). */
  body:not(.desk-chat-open) .rail-dash { background: #1f6feb; color: #fff; }
  body:not(.desk-chat-open) .rail-dash:hover { background: #388bfd; }
  /* ── Agents rail: flat, frequency-sorted desk list ───────────────────────────────
     One tight row per desk: lane dot (green = US/Claude, red = China/GLM/Kimi), desk name,
     file-count icon+number, and a pin button. No category buckets, no state line, no model pill,
     no "38 files" text, no lock text. Offline desks are dimmed (.off), never struck through. */
  .agent { display: flex; align-items: center; gap: 7px; padding: 5px 10px; cursor: pointer;
           border-left: 3px solid var(--type-accent, transparent); transition: background .1s;
           touch-action: pan-y; -webkit-touch-callout: none; }
  .agent:hover { background: #161b22; }
  .agent.sel { background: var(--type-selbg, #1a2535); border-left-color: var(--type-accent, #58a6ff); }
  .agent.off { opacity: .5; }
  /* Lane dot: green for US/Claude, red for China/GLM/Kimi. */
  .agent .adot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: #6e7681; }
  .agent .adot.lane-us    { background: #3fb950; box-shadow: 0 0 5px #3fb950; }
  .agent .adot.lane-china { background: #f87171; box-shadow: 0 0 5px #f87171; }
  .agent .who { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; }
  .agent .nmrow { display: flex; align-items: center; gap: 6px; min-width: 0; flex: 1 1 auto; }
  .agent .nm { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 13px;
               color: var(--type-name, #e6edf3); flex: 1 1 auto; min-width: 0; }
  .agent .file-chip { display: inline-flex; align-items: center; gap: 2px; font-size: 10px;
                      color: #8b949e; background: transparent; border-radius: 10px; padding: 0; }
  .agent .file-chip.has-files { color: #58a6ff; }
  .agent .rowctrls { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; opacity: 0; transition: opacity .1s; }
  .agent:hover .rowctrls { opacity: 1; }
  .agent .pinbtn { flex: 0 0 auto; width: 20px; height: 20px; line-height: 18px; text-align: center;
                   border: 1px solid #30363d; border-radius: 5px; background: transparent; color: #6e7681;
                   font-size: 11px; cursor: pointer; padding: 0; }
  .agent .pinbtn:hover { color: #adbac7; border-color: #484f58; }
  .agent .pinbtn.pinned { color: #f0883e; border-color: #5a3d1a; }
  .agent .rbtn { flex: 0 0 auto; margin-left: 2px; width: 20px; height: 20px; line-height: 18px;
                 text-align: center; border: 1px solid #30363d; border-radius: 5px; background: transparent;
                 color: #6e7681; font-size: 12px; cursor: pointer; padding: 0; }
  .agent .rbtn:hover { background: #4d2020; border-color: #57391a; color: #f0883e; }
  .agent .wbadge { flex: 0 0 auto; margin-left: 2px; font-size: 11px; cursor: pointer; }
  /* Keep per-desk custom colour overrides (data-color wins over data-type). */
  .agent[data-color="white"] { --type-accent:#e6edf3; --type-name:#ffffff; --type-selbg:#2b3038; }
  .agent[data-color="brown"] { --type-accent:#a9744f; --type-name:#c88a5e; --type-selbg:#2e1e14; }
  .agent[data-color="teal"]  { --type-accent:#2dd4bf; --type-name:#5eead4; --type-selbg:#0d2f2b; }
  .agent[data-color="rose"]  { --type-accent:#f472b6; --type-name:#f9a8d4; --type-selbg:#2e1420; }
  .agent[data-color="slate"] { --type-accent:#94a3b8; --type-name:#cbd5e1; --type-selbg:#1c2330; }
  .agent[data-color="violet"] { --type-accent:#a78bfa; --type-name:#c4b5fd; --type-selbg:#211a33; }
  .agent[data-color="gold"]  { --type-accent:#e3a008; --type-name:#f0c045; --type-selbg:#2a2410; }
  .agent[data-color="emerald"] { --type-accent:#10b981; --type-name:#6ee7b7; --type-selbg:#0c2b22; }
  .agent[data-color="cyan"] { --type-accent:#06b6d4; --type-name:#22d3ee; --type-selbg:#0c2430; }
  .agent[data-color="glm"]    { --type-accent:#dc2626; --type-name:#f87171; --type-selbg:#3a0d0d; }
  .agent[data-color="kimi"]   { --type-accent:#dc2626; --type-name:#f87171; --type-selbg:#3a0d0d; }
  .agent[data-color="claude"] { --type-accent:#f0883e; --type-name:#ffa657; --type-selbg:#3a2412; }
  .agent .nm-edit { width: 100%; font-size: 13px; font-family: inherit; color: #e6edf3; background: #0d1117;
                    border: 1px solid #388bfd; border-radius: 4px; padding: 1px 4px; box-sizing: border-box; }

  /* Search + collapse bar for the flat agent list. */
  .rail-search-row { padding: 6px 10px 4px; position: sticky; top: 0; background: #0d1117; z-index: var(--z-raised); }
  .rail-search { width: 100%; font-size: 13px; color: #e6edf3; background: #161b22; border: 1px solid #30363d;
                 border-radius: 6px; padding: 5px 8px; box-sizing: border-box; }
  .rail-search::placeholder { color: #8b949e; }
  .rail-search:focus { outline: none; border-color: #58a6ff; }
  .rail-more { width: 100%; padding: 7px 10px; border: none; border-top: 1px solid #21262d; background: transparent;
               color: #8b949e; font-size: 12px; text-align: left; cursor: pointer; }
  .rail-more:hover { color: #e6edf3; background: #161b22; }
  /* Agents header add-thread button. */
  .addthreadbtn { width: 18px; height: 16px; line-height: 13px; text-align: center; border: 1px solid #30363d;
                  border-radius: 4px; background: transparent; color: #6e7681; font-size: 12px; font-weight: 700;
                  cursor: pointer; padding: 0; opacity: .7; }
  .addthreadbtn:hover { opacity: 1; color: #56d364; border-color: #2ea043; }
  .add-thread-row { display: flex; align-items: center; gap: 4px; padding: 6px 10px 8px; }
  .add-thread-input { flex: 1 1 auto; min-width: 0; font-size: 13px; font-family: inherit; color: #e6edf3;
                       background: #0d1117; border: 1px solid #388bfd; border-radius: 4px; padding: 5px 8px;
                       box-sizing: border-box; }
  .add-thread-ok, .add-thread-x { flex: 0 0 auto; width: 26px; height: 26px; border: 1px solid #30363d;
                                   border-radius: 4px; background: transparent; color: #8b949e; font-size: 12px;
                                   cursor: pointer; padding: 0; }
  .add-thread-ok:hover { color: #56d364; border-color: #2ea043; }
  .add-thread-x:hover { color: #f85149; border-color: #57391a; }

  /* ── WO-LAUNCHPAD-DOCK: compact app-launch grid at the bottom of the agents rail ───────────────
     Pinned to the bottom of the sidebar so it's always reachable; sticky background keeps it
     readable when the desk list scrolls behind it. Matches the dark theme + accent-glow style. */
  .launchpad { flex: none; left: 0; right: 0; background: #11161d;
               border-top: 1px solid #21262d; padding: 8px 10px 10px; z-index: var(--z-raised); }
  .launchpad-head { display: flex; align-items: center; justify-content: space-between;
                    margin-bottom: 7px; }
  .launchpad-title { font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
                     color: #8b949e; font-weight: 600; }
  .lp-add-btn { width: 20px; height: 20px; line-height: 18px; text-align: center;
                border: 1px solid #30363d; border-radius: 5px; background: transparent;
                color: #6e7681; font-size: 14px; cursor: pointer; padding: 0; }
  .lp-add-btn:hover { color: #56d364; border-color: #2ea043; }
  .launchpad-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
  .lp-tile { display: flex; flex-direction: column; align-items: center; justify-content: center;
             gap: 3px; aspect-ratio: 1; min-height: 52px; padding: 4px 2px;
             background: #161b22; border: 1px solid #21262d; border-radius: 8px;
             color: #c9d1d9; font-size: 10px; text-align: center; cursor: pointer;
             transition: transform .08s, border-color .1s, box-shadow .1s; }
  .lp-tile:hover { transform: translateY(-1px); border-color: #58a6ff;
                   box-shadow: 0 0 10px rgba(88,166,255,.22); }
  .lp-tile:active { transform: translateY(0); }
  .lp-tile .lp-ic { font-size: 17px; line-height: 1; }
  /* WO-LAUNCHPAD-BRAND-ICONS: real brand mark (bundled SVG under /brand/). Falls back to a high-res
     favicon via onerror — never a generic glyph for a known brand. Sized to the emoji .lp-ic slot. */
  .lp-tile .lp-brand { width: 22px; height: 22px; object-fit: contain; display: block; pointer-events: none; }
  .lp-tile .lp-lbl { max-width: 100%; overflow: hidden; text-overflow: ellipsis;
                     white-space: nowrap; font-weight: 500; }
  .lp-tile.lp-add { background: transparent; border-style: dashed; color: #8b949e; }
  .lp-tile.lp-add:hover { color: #e6edf3; border-color: #8b949e;
                          box-shadow: 0 0 8px rgba(139,148,158,.15); }
  .lp-tile.lp-custom { position: relative; }
  .lp-del { position: absolute; top: 1px; right: 2px; font-size: 10px; line-height: 1;
            color: #6e7681; cursor: pointer; padding: 2px; }
  .lp-del:hover { color: #f85149; }
  .lp-tile.lp-web { border-bottom: 2px solid #1f6feb; }
  .lp-tile.lp-native { border-bottom: 2px solid #2ea043; }
  .lp-more { width: 100%; margin-top: 6px; padding: 4px 0; border: none; background: transparent;
             color: #8b949e; font-size: 12px; cursor: pointer; border-radius: 6px; }
  .lp-more:hover { color: #e6edf3; background: #161b22; }
  .lp-status { min-height: 18px; margin-top: 6px; font-size: 11px; text-align: center;
               color: #8b949e; }
  .lp-status.ok { color: #3fb950; }
  .lp-status.err { color: #f85149; }
  .lp-add-sheet { margin-top: 8px; padding-top: 8px; border-top: 1px solid #21262d; }
  .lp-add-input { width: 100%; font-size: 12px; color: #e6edf3; background: #0d1117;
                  border: 1px solid #30363d; border-radius: 6px; padding: 6px 8px;
                  box-sizing: border-box; }
  .lp-add-input:focus { outline: none; border-color: #58a6ff; }
  .lp-add-actions { display: flex; gap: 6px; margin-top: 6px; }
  .lp-add-ok, .lp-add-x { flex: 1 1 auto; padding: 5px 8px; border-radius: 6px; cursor: pointer;
                          font-size: 12px; font-weight: 600; font-family: inherit;
                          border: 1px solid #30363d; background: #161b22; color: #adbac7; }
  .lp-add-ok { border-color: #2ea043; color: #56d364; background: #0f2116; }
  .lp-add-ok:hover { background: #16301f; }
  .lp-add-x:hover { color: #f85149; border-color: #57391a; }
  .lp-add-error { margin-top: 5px; font-size: 11px; color: #f85149; min-height: 13px; }
  /* Collapsed sidebar: shrink the dock to a single icon column so labels don't wrap awkwardly. */
  body.rail-collapsed .launchpad { padding: 8px 6px 10px; }
  body.rail-collapsed .launchpad-head { justify-content: center; }
  body.rail-collapsed .launchpad-title { display: none; }
  body.rail-collapsed .launchpad-grid { grid-template-columns: 1fr; }
  body.rail-collapsed .lp-tile .lp-lbl { display: none; }
  body.rail-collapsed .lp-more { font-size: 10px; }

  /* WO-DESK-DEDUP — all of it lives in the rail, right under the category header it belongs to.
     Two states, same slot: (1) the creation GUARD, shown when a new thread would duplicate a desk
     that already exists ("You already have a Mechanic — Open it, or use a distinct name"), and
     (2) the duplicate NOTICE + its merge confirm, shown while two same-type desks still exist.
     Amber = "this needs a decision", never red (nothing is broken, and nothing is destroyed).
     Buttons are full-height tap targets and the row wraps, so it reads on a phone first. */
  .dup-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 2px 8px 6px;
             padding: 7px 9px; border: 1px solid #57391a; border-radius: 7px; background: #1e1707;
             font-size: 11.5px; line-height: 1.35; color: #e3b341; }
  .dup-msg { flex: 1 1 100%; min-width: 0; color: #e6edf3; }
  .dup-msg b { color: #e3b341; font-weight: 700; }
  .dup-sub { flex: 1 1 100%; color: #8b949e; font-size: 10.5px; }
  .dup-btn { flex: 1 1 auto; min-height: 30px; padding: 5px 10px; border-radius: 6px; cursor: pointer;
             font-size: 11.5px; font-weight: 600; font-family: inherit; border: 1px solid #30363d;
             background: #161b22; color: #adbac7; }
  .dup-btn:hover { border-color: #8b949e; color: #e6edf3; }
  .dup-btn.primary { border-color: #2ea043; color: #56d364; background: #0f2116; }
  .dup-btn.primary:hover { background: #16301f; }
  .dup-btn.warn { border-color: #9e6a03; color: #e3b341; background: #241a08; }
  .dup-btn.warn:hover { background: #322209; }
  .dup-btn[disabled] { opacity: .55; cursor: default; }

  /* Live model pill (Batch 2 Item 1) — the worker's CURRENT Claude model, shown on each
     rail agent + Desks·workers row. Degrades to nothing when the field is absent (ships
     before the backend heartbeat carries current-model). Family-tinted for a fast read. */
  .mpill { display: inline-block; font-size: 10px; padding: 1px 6px; border-radius: 20px;
           font-weight: 600; background: #21262d; color: #adbac7; border: 1px solid #30363d;
           max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
           vertical-align: middle; }
  .mpill-opus { background: #3a2a12; color: #f0883e; border-color: #57391a; }
  .mpill-sonnet { background: #1c2d4a; color: #58a6ff; border-color: #2a3f63; }
  .mpill-haiku { background: #133a1f; color: #3fb950; border-color: #1c5330; }
  /* WO-LABELS: the hosted-cheap/open-weight family (GLM/Llama/DeepSeek/Qwen/MiniMax) — a
     distinct tint from the Claude tiers so a glance tells cheap-lane from frontier. */
  .mpill-cheap { background: #2a1c3a; color: #d2a8ff; border-color: #3f2a57; }
  /* WO-LABELS: the RESTING pill (no live per-call model yet — this is the desk's DEFAULT
     router resolution, not what a specific reply used) — dashed border marks it as "at rest". */
  .mpill-resting { border-style: dashed; opacity: .85; }
  .agent .mpill { font-size: 9px; max-width: 100%; margin-top: 2px; }

  /* WO-MODEL-SELECTOR (task 138): per-agent model picker dropdown/pill */
  .model-picker { position: relative; display: inline-block; flex: 0 0 auto; }
  .model-picker-btn {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: 9px; padding: 1px 6px; border-radius: 20px;
    font-weight: 600; background: #161b22; color: #adbac7;
    border: 1px solid #30363d; cursor: pointer;
    max-width: 110px; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; transition: background .15s, border-color .15s;
  }
  .model-picker-btn:hover { background: #1c2128; border-color: #484f58; }
  .model-picker-btn.disabled { opacity: .5; cursor: not-allowed; }
  /* WO-CODER-BRAIN-DISPLAY (task 199): the button's label IS the live brain ("GLM"/"Claude"),
     fully tinted (text + background + dot) — same colours as the read-only .provbadge above and
     the coder row accents: red = GLM, orange = Claude. This is now the ONE indicator per row;
     it used to read a generic "Default" label next to a separate, sometimes-stale GLM/Claude
     badge (task 159/190) — the two could disagree once GLM capped out and a coder failed over
     to Claude (task 188). Keep in sync with .provbadge.glm/.claude and
     .agent[data-color="glm"/"claude"] above. */
  .model-picker-btn .mp-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
  .model-picker-btn.prov-glm .mp-dot { background: #f87171; }
  .model-picker-btn.prov-glm { color: #f87171; background: #3a0d0d; border-color: #5f1a1a; }
  .model-picker-btn.prov-glm:hover { background: #4a1212; border-color: #8f2b2b; }
  /* WO-KIMI-CHINA-BOUNDARY (task 464): second China-boundary lane — identical red to prov-glm. */
  .model-picker-btn.prov-kimi .mp-dot { background: #f87171; }
  .model-picker-btn.prov-kimi { color: #f87171; background: #3a0d0d; border-color: #5f1a1a; }
  .model-picker-btn.prov-kimi:hover { background: #4a1212; border-color: #8f2b2b; }
  .model-picker-btn.prov-claude .mp-dot { background: #ffa657; }
  .model-picker-btn.prov-claude { color: #ffa657; background: #3a2412; border-color: #5a3d1a; }
  .model-picker-btn.prov-claude:hover { background: #46301a; border-color: #8a5f2a; }
  .model-picker-btn.disabled:hover { background: #161b22; border-color: #30363d; }
  .model-picker-menu {
    display: none; position: absolute; right: 0; top: calc(100% + 4px); z-index: var(--z-dropdown);
    flex-direction: column; gap: 2px; min-width: 180px; padding: 4px;
    background: #161b22; border: 1px solid #30363d; border-radius: 8px;
    box-shadow: 0 8px 28px rgba(0,0,0,.55);
  }
  .model-picker-menu.on { display: flex; }
  .model-picker-option {
    width: 100%; text-align: left; background: #161b22; color: #e6edf3;
    border: 1px solid transparent; border-radius: 6px; padding: 8px 10px;
    font-size: 12px; cursor: pointer; display: flex; align-items: center; gap: 8px;
  }
  .model-picker-option:hover { background: #21262d; border-color: #30363d; }
  .model-picker-option.selected { background: #0d2847; border-color: #1f6feb; }
  .model-picker-option.disabled { opacity: .45; cursor: not-allowed; }
  .model-picker-option.disabled:hover { background: #161b22; border-color: transparent; }
  .model-opt-label { flex: 1; min-width: 0; }
  .model-opt-tag { font-size: 9px; padding: 1px 5px; border-radius: 10px; font-weight: 600; flex-shrink: 0; }
  .model-opt-tag.cost-sub { background: #12261a; color: #56d364; }
  .model-opt-tag.cost-metered { background: #3a2a12; color: #f0883e; }
  .model-opt-tag.warning { background: #3a1a1a; color: #f85149; }
  .model-opt-lock { font-size: 10px; margin-left: auto; }
  /* WO-MODEL-SELECTOR (task 140): explicit host-region badge on each option + a lock badge on
     the picker button itself for crown-jewel/pinned agents (server-computed modelLocked). */
  .model-opt-tag.host-us { background: #0f2a1c; color: #3fb950; }
  .model-opt-tag.host-china { background: #3a1a1a; color: #f85149; }
  .model-lock-badge { font-size: 9px; margin-left: 3px; color: #d29922; flex-shrink: 0; }
  /* IB-124 · polished mobile model picker chrome: header + close X + scrollable options list.
     The sheet structure is always in the DOM; the head/X only appear on mobile, desktop keeps the
     compact dropdown. */
  .model-picker-head { display: none; }
  .model-picker-title { font-size: 13px; font-weight: 700; color: #e6edf3; }
  .model-picker-x { background: none; border: none; color: #8b949e; font-size: 20px; cursor: pointer;
                    min-width: 44px; min-height: 44px; display: inline-flex; align-items: center;
                    justify-content: center; }
  .model-picker-x:hover { color: #e6edf3; }
  .model-picker-options { display: flex; flex-direction: column; gap: 2px; }
  .model-picker-backdrop { display: none; }
  .model-picker-sheet { display: contents; }

  .main { transition: padding-left .18s ease; }
  /* IB-1418 · the phone glance. Desktop/tablet never see it — display:none is the base rule, and
     the ONLY override lives in the ≤759px block below, so the desktop board is untouched. */
  .phone-glance { display: none; }
  .counts { display: flex; gap: 10px; padding: 14px 20px; flex-wrap: wrap; }
  .count { background: #161b22; border: 1px solid #21262d; border-radius: 8px; padding: 10px 16px; min-width: 92px; }
  .count .n { font-size: 22px; font-weight: 700; }
  .count .l { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: #8b949e; }
  .filterbar { padding: 0 20px; min-height: 0; }
  .chip { display: inline-flex; align-items: center; gap: 8px; background: #1c2d4a; color: #58a6ff;
          border: 1px solid #2a3f63; border-radius: 20px; padding: 4px 12px; font-size: 12px; }
  .chip button { background: none; border: none; color: #58a6ff; cursor: pointer; font-size: 13px; padding: 0; }
  /* WO-CARD-SCROLL-PADDING: wider gap between the two-per-row cards (and around the grid) so
     there's real breathing room and an easy strip to wheel-scroll the page without landing on
     a card body. */
  .wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; padding: 16px 24px 28px; }
  @media (max-width: 860px) { .wrap { grid-template-columns: 1fr; gap: 20px; } }
  /* TIB-075 §4 · the two columns are a function of the BOARD's width, not the window's. The board
     is what is left over after both rails, so a wide sidebar on a 1000px window left two ~250px
     columns whose cards had to stack their words one per line — and a viewport-width query cannot
     see that, because the viewport never changed. Set from the board's OWN measured width by
     syncBoardColumns(); a card that cannot be two columns wide enough to read is one column. */
  .wrap.board-narrow { grid-template-columns: 1fr; }
  /* IB-119 · the workbench stays visible while Brandon talks to the Manager. The board body no
     longer hides when a desk chat opens; instead the chat panel becomes a slim side drawer beside
     the work on desktop (see the @media (min-width: 760px) block below). The mobile full-screen
     chat stays as-is. */
  .panel { background: #161b22; border: 1px solid #21262d; border-radius: 10px; overflow: hidden; }
  .panel h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: #8b949e;
              margin: 0; padding: 12px 16px; border-bottom: 1px solid #21262d;
              display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
  /* overscroll-behavior: contain keeps the elastic bounce/rubber-band effect local to the card
     instead of visually bleeding into the page behind it. Actual hand-back-to-page-at-the-edge
     behavior is guaranteed by the wheel listener below (wireScrollHandoff), not by this alone. */
  .panel .body { max-height: 360px; overflow: auto; overscroll-behavior: contain; }
  /* Tasks spans the full grid width and gets a taller body — the space reclaimed from the
     deleted Desks·workers card goes to making tasks bigger/readable (task 20, req 2). */
  .panel-tasks { grid-column: 1 / -1; }
  .panel-tasks .body { max-height: 480px; }
  /* Two labelled sub-sections inside the single Model-routing card (task 20, req 5), with a
     slight rule + gap between them. */
  .subhd { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: #6e7681;
           padding: 9px 14px 3px; font-weight: 700; }
  .subsec + .subsec { border-top: 1px solid #21262d; margin-top: 6px; }
  /* WO-32 · System Health panel rows. A status dot (ok/warn/err) + desk name + real live state. */
  .hitem { display: flex; gap: 8px; padding: 10px 16px; border-bottom: 1px solid #1b2129; align-items: center; }
  .hitem:last-child { border-bottom: none; }
  .hdot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
  .hdot.ok { background: #3fb950; box-shadow: 0 0 6px #3fb95066; }
  .hdot.warn { background: #d29922; box-shadow: 0 0 6px #d2992266; }
  .hdot.err { background: #f85149; box-shadow: 0 0 6px #f8514966; }
  .hname { font-weight: 600; flex: 0 0 auto; }
  .hstate { color: #8b949e; font-size: 12px; flex: 1 1 auto; min-width: 0; overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }
  .hage { color: #6e7681; font-size: 11px; font-variant-numeric: tabular-nums; flex: 0 0 auto; }
  .rlog { padding: 8px 16px; border-bottom: 1px solid #1b2129; font-size: 12px; color: #8b949e; }
  .rlog:last-child { border-bottom: none; }
  .rlog .rw { color: #c9d1d9; font-weight: 600; }
  .health-banner { padding: 8px 14px; font-size: 12px; font-weight: 600; }
  .health-banner.ok { color: #3fb950; }
  .health-banner.bad { color: #f85149; }
  /* TIB-054 · Running code. The commit the checkout holds and the commit each PROCESS booted on —
     two different facts, and for an hour the second one had no reading anywhere. Placed directly
     under the fleet banner because a desk running code that is not on main is the HEADLINE. */
  .rcsec { border-top: 1px solid #21262d; border-bottom: 1px solid #21262d; }
  .rchead { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: #6e7681;
            padding: 9px 14px 3px; font-weight: 700; }
  .rcbanner { padding: 4px 14px 8px; font-size: 12px; font-weight: 600; color: #f85149; }
  .rcbanner.ok { color: #3fb950; }
  .rcbanner.unknown { color: #8b949e; }
  .rcrow { display: flex; gap: 8px; padding: 6px 16px; align-items: center; font-size: 12px; }
  .rcrow .rcname { font-weight: 600; flex: 0 0 auto; color: #c9d1d9; }
  .rcrow .rcsha { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: #8b949e; }
  .rcrow.behind .rcsha { color: #f85149; }
  .rcrow .rcnote { color: #6e7681; flex: 1 1 auto; min-width: 0; overflow: hidden;
                   text-overflow: ellipsis; white-space: nowrap; }
  .rcrow .rcage { color: #6e7681; font-variant-numeric: tabular-nums; flex: 0 0 auto; }
  /* TIB-073 · Working. One line per thing the fleet exists to DO, and whether it is happening. It
     sits ABOVE the running-code rows and above the desk rows for the reason the whole spec exists:
     a process that is up, current and configured with a dead bridge behind it is not green, and the
     answer to "is the shop working" must not be below two rows of answers to narrower questions.
     Deliberately readable at 390px — the reason each duty says what it is in words rather than a
     code, and why the broken ones are the only ones that get a full-width reason line. */
  .wksec { border-bottom: 1px solid #21262d; }
  .wkbanner { padding: 8px 14px; font-size: 13px; font-weight: 700; color: #f85149; line-height: 1.35; }
  .wkbanner.ok { color: #3fb950; font-size: 12px; font-weight: 600; }
  .wkbanner.unknown { color: #8b949e; font-size: 12px; font-weight: 600; }
  .wkrow { display: flex; gap: 8px; padding: 7px 16px; align-items: baseline; font-size: 12px;
           flex-wrap: wrap; }
  .wkrow .wkname { font-weight: 600; color: #c9d1d9; flex: 0 0 auto; }
  .wkrow .wkstate { flex: 0 0 auto; font-size: 11px; font-weight: 700; text-transform: uppercase;
                    letter-spacing: .04em; color: #3fb950; }
  .wkrow.bad .wkstate { color: #f85149; }
  .wkrow.unknown .wkstate { color: #8b949e; }
  /* TIB-082 · how long it has been behind, next to the name. Monospace so a column of them can be
     compared down the panel, and never truncated — the number IS the difference between a blip and
     something worth looking at. */
  .wkrow .wkfor { flex: 0 0 auto; color: #d29922; font-size: 11px; font-weight: 600;
                  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  .wkrow .wkwhat { color: #6e7681; flex: 1 1 200px; min-width: 0; overflow: hidden;
                   text-overflow: ellipsis; white-space: nowrap; }
  /* IB-11 · what the duty did on its last pass. Full width so it is never truncated: a sweep that
     deletes folders has to be able to say how many, and a number behind an ellipsis is not a
     report. Neutral, never alarm-coloured — this line is what NORMAL looks like. */
  .wkrow .wkdetail { flex: 1 1 100%; color: #8b949e; font-size: 12px; line-height: 1.4; }
  /* The reason a thing is broken is never truncated and never behind a tap. */
  .wkrow .wkwhy { flex: 1 1 100%; color: #f0b3ae; font-size: 12px; line-height: 1.4; }
  .wkrow .wkwhy .wktried { color: #8b949e; display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                           font-size: 11px; word-break: break-word; }
  .wkboot { padding: 6px 16px 10px; font-size: 12px; color: #f85149; font-weight: 600; }
  .wkcount { color: #8b949e; }
  .wkcount .wkstate { color: #3fb950; }
  /* TIB-073 · the glance. Full width, under the header, and only ever present when something the
     fleet exists to do is not happening. Wraps rather than ellipsizes: the names of the dead
     machines are the message, and a truncated one reads as a vague warning.
     TIB-082 · AMBER, NOT RED. This is now the ONLY place a late duty appears, and it is somewhere
     he chooses to look rather than something that arrives on his phone. Emergency red on a
     status line he passes every day is how a status line stops being read — and the line itself
     leads with what is working, so the colour has to agree with the words.
     TIB-082/TIB-083 reconcile · there is no dismiss X here and no room reserved for one. TIB-083
     built a 44×44 hit target on the right of a RED bar so the bar could be pushed away for four
     hours; a line that clears itself the moment the duty proves itself again has nothing to push
     away. The right-hand padding that made space for that X is gone with it. */
  .nwbar { display: flex; align-items: center; gap: 10px; background: #2b2410;
           border-bottom: 1px solid #d29922; color: #f0c674; cursor: pointer;
           padding: 10px 14px; font-size: 13px; font-weight: 700; line-height: 1.35; }
  .nwbar:active { background: #3a3016; }
  .nwbar-text { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden;
                text-overflow: ellipsis; }
  .nwbar-text .nwcue { font-weight: 500; color: #d3bb85; }
  .nwbar-close { flex: 0 0 auto; width: 44px; height: 44px; margin: -10px -14px -10px 0;
                 background: transparent; border: none; color: #d3bb85; font-size: 18px;
                 cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
  .nwbar-close:hover, .nwbar-close:focus { color: #f0c674; background: rgba(240,198,116,.1); outline: none; }
  /* IB-1448 · THE FLEET IS BEHIND MAIN, AND THAT IS A FACT NOT A FAULT. Same full-width,
     direct-child-of-body placement as .nwbar (unmissable, never nested in a hidden panel) but a
     distinct BLUE-GRAY tone, never the amber "not working" colour and never red — a landed fix
     that has not reached the running fleet yet is information, not breakage. Clears itself the
     instant the fleet catches up; the dismiss control only silences the CURRENT gap (same
     fingerprint-keyed close as .nwbar), so a later gap still announces itself. */
  .rmbar { display: flex; align-items: center; gap: 10px; background: #16232e;
           border-bottom: 1px solid #3d6a8f; color: #9fc6e6; cursor: pointer;
           padding: 10px 14px; font-size: 13px; font-weight: 700; line-height: 1.35; }
  .rmbar:active { background: #1c2e3b; }
  .rmbar-text { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden;
                text-overflow: ellipsis; }
  .rmbar-text .rmcue { font-weight: 500; color: #7ea8c9; }
  .rmbar-close { flex: 0 0 auto; width: 44px; height: 44px; margin: -10px -14px -10px 0;
                 background: transparent; border: none; color: #7ea8c9; font-size: 18px;
                 cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
  .rmbar-close:hover, .rmbar-close:focus { color: #9fc6e6; background: rgba(159,198,230,.1); outline: none; }
  /* IB-1463 · AUTO-LAND PAUSED, AND THAT IS ALWAYS UNMISSABLE. Same full-width, direct-child-of-body
     placement as .nwbar/.rmbar, but its own RED tone — unlike a fleet running old code (.rmbar,
     informational), nothing merging to main is the exact kill-switch state that sat quiet for 25
     minutes and cost the fleet nothing said about it. No -close variant on purpose: this bar has no
     dismiss button, only Resume (see #autoland-pause-bar's markup) — the only way to make it go away
     is to actually turn auto-land back on. */
  .alpbar { display: flex; align-items: center; gap: 10px; background: #2b1216;
            border-bottom: 1px solid #d2454f; color: #f0a3a9; cursor: default;
            padding: 10px 14px; font-size: 13px; font-weight: 700; line-height: 1.35; }
  .alpbar-text { flex: 1 1 auto; min-width: 0; }
  .alpbar-resume { flex: 0 0 auto; background: #d2454f; border: none; color: #fff;
                    font-size: 13px; font-weight: 700; padding: 8px 16px; border-radius: 6px;
                    cursor: pointer; }
  .alpbar-resume:hover, .alpbar-resume:focus { background: #e8636c; outline: none; }
  /* WO-TASK-ROW-EXPAND (task 203): each row is a compact .trow summary (collapsed by
     default) that clicks to reveal a .tdetail panel below it — the row wrapper is now a
     column so the detail panel can sit full-width under the summary line. */
  .task { border-bottom: 1px solid #1b2129; display: flex; flex-direction: column; }
  .task:last-child { border-bottom: none; }
  .trow { padding: 12px 16px; display: flex; gap: 10px; align-items: baseline; cursor: pointer;
          -webkit-tap-highlight-color: transparent; }
  .trow:hover { background: #161b22; }
  /* IB-138 · the id badge is JS-clamped to an allocated spec code (see specCode() in the script),
     but this is the backstop: no field, however garbage, ever grows this box past its row. */
  .id { color: #8b949e; font-variant-numeric: tabular-nums; min-width: 34px; max-width: 45%;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .title { flex: 1; }
  .tbody { flex: 1; min-width: 0; }
  /* Collapsed summary is a single ellipsized line — the full text lives in .tdetail once expanded. */
  .tclamp { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .sub { color: #8b949e; font-size: 12px; }
  .chev { color: #6e7681; font-size: 11px; align-self: center; transition: transform .2s ease; flex: 0 0 auto; }
  .task.exp .chev { transform: rotate(180deg); }
  /* Smooth expand/collapse via max-height (grid-template-rows would be cleaner but this reads
     everywhere, incl. older mobile Safari, with no JS height measurement needed). */
  .tdetail { max-height: 0; overflow: hidden; transition: max-height .25s ease; }
  .task.exp .tdetail { max-height: 1200px; }
  .tdetail-in { padding: 2px 14px 14px; border-top: 1px dashed #21262d; }
  .drow { font-size: 12px; padding: 5px 0; border-bottom: 1px solid #161b22; display: flex; gap: 8px; }
  .drow:last-of-type { border-bottom: none; }
  .drow .dk { color: #6e7681; min-width: 88px; flex: 0 0 auto; }
  .drow .dv { color: #c9d1d9; overflow-wrap: anywhere; flex: 1 1 auto; }
  .doutcome-ok { color: #3fb950; font-weight: 600; }
  .doutcome-fail { color: #f85149; font-weight: 600; }
  .doutcome-open { color: #8b949e; font-weight: 600; }
  .doutcome-decision { color: #d29922; font-weight: 600; }
  /* WO-TASK-CARD-UPGRADE (chunk 1): Routing story, color-coded commit/deploy status */
  .droute { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
  .droute-start, .droute-end { font-weight: 600; }
  .droute-start { color: #58a6ff; }
  .droute-end { color: #3fb950; }
  .droute-single { color: #c9d1d9; font-weight: 500; }
  .dstatus { font-size: 11px; padding: 2px 8px; border-radius: 12px; font-weight: 600; display: inline-block; }
  .dstatus-green { background: #1a3f2e; color: #3fb950; border: 1px solid #238636; }
  .dstatus-amber { background: #3a2a1a; color: #d29922; border: 1px solid #9a6700; }
  .dstatus-red { background: #3a1a1a; color: #f85149; border: 1px solid #f85149; }
  .tactions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
  .taction { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; border-radius: 5px;
             padding: 5px 10px; font-size: 12px; cursor: pointer; }
  .taction:hover { background: #30363d; }
  .taction:disabled { opacity: .5; cursor: default; }
  .tfulllog { margin-top: 8px; max-height: 260px; overflow: auto; background: #0d1117;
              border: 1px solid #21262d; border-radius: 6px; padding: 8px 10px; font-size: 11px;
              color: #8b949e; white-space: pre-wrap; overflow-wrap: anywhere; }
  /* IB-138 · one pill shape at one size for all four statuses — a border on every one (transparent
     unless the status needs it) so the box never changes size when a status needs to stand out.
     Colour is never the only signal: the word itself (queued/running/done/blocked) reads in
     greyscale, and blocked keeps its own visible ring + mark for a second non-colour cue. */
  .badge { font-size: 11px; padding: 2px 8px; border-radius: 20px; font-weight: 600;
    border: 1px solid transparent; box-sizing: border-box; display: inline-flex;
    align-items: center; gap: 3px; line-height: 1.4; }
  .b-queued { background: #1f2937; color: #9ca3af; }
  .b-running { background: #1c2d4a; color: #58a6ff; }
  .b-done { background: #133a1f; color: #3fb950; }
  .b-blocked { background: #3a1a1a; color: #f85149; border-color: #f85149; }
  /* WO-BLOCKED-TAP (task 257): the blocked pill stays a real tap target (opens the blocked panel)
     but IB-138 dropped the min-height/oversized padding that made it ~3x its neighbours and grew
     its row taller than the others — the whole row is already a ≥44px tap target (see .trow). */
  .badge.b-blocked.tap { cursor: pointer; touch-action: manipulation; }
  .badge.b-blocked.tap:active { background: #4a2020; }
  /* task #1793 · a decision_hold row is its own category, not a shade of blocked — it never means
     "a robot has to fix this", it always means "only a person can". Distinct colour (amber, not
     red) so it never reads as a machine failure at a glance. Same tap target as blocked, reusing
     the same panel — it already names the hold and the question. */
  .b-decision { background: #3a2a10; color: #d29922; border-color: #d29922; }
  .badge.b-decision.tap { cursor: pointer; touch-action: manipulation; }
  .badge.b-decision.tap:active { background: #4a3714; }
  /* Same tappable-badge treatment for a wedged/hung/blocked WORKER row (rail + System Health
     panel) — visually distinct label so it's clear this taps a DESK, not a task. */
  .wbadge { cursor: pointer; border: 1px solid #f85149; background: #3a1a1a; color: #f85149;
    border-radius: 20px; font-size: 10px; font-weight: 700; letter-spacing: .02em;
    min-height: 26px; display: inline-flex; align-items: center; padding: 4px 10px;
    touch-action: manipulation; white-space: nowrap; }
  .wbadge:active { background: #4a2020; }
  /* WO-185: per-task deploy badge stacks under the status badge — mobile-first (narrow task
     rows never overflow two side-by-side pills). */
  .tbadges { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; align-self: center; }
  /* WO-REPO-CHIPS (task 260): registry-driven repo chip — colour + short label come from the
     /repos endpoint (src/worker/repoRegistry.js), never a hardcoded per-repo class. A legacy/
     missing repo always falls back to .repochip-unassigned (req 3) so this never renders blank. */
  .repochip { font-size: 10px; padding: 2px 7px; border-radius: 20px; font-weight: 700;
    letter-spacing: .02em; border: 1px solid transparent; white-space: nowrap; }
  .repochip-unassigned { background: #1f2937; color: #8b949e; border-color: #30363d; }
  /* WO-TASK-RESOLVED-BRAIN: small lane pill on the task row — Kimi/Claude/GLM, matching the
     colour language the coder cards already use. Failover gets a subtle dashed border hint. */
  .tbrain { font-size: 10px; padding: 2px 7px; border-radius: 20px; font-weight: 700;
    letter-spacing: .02em; border: 1px solid transparent; white-space: nowrap; }
  .tbrain-claude { background: #3a2412; color: #ffa657; border-color: #f0883e66; }
  .tbrain-glm { background: #1c2d4a; color: #58a6ff; border-color: #58a6ff66; }
  .tbrain-kimi { background: #3a0d0d; color: #f87171; border-color: #f8717166; }
  .tbrain.failover { border-style: dashed; }
  .repo-tabs { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 16px 0; }
  .repo-tab { font-size: 11px; padding: 4px 11px; border-radius: 20px; font-weight: 600;
    background: #1f2937; color: #8b949e; border: 1px solid #30363d; cursor: pointer;
    touch-action: manipulation; min-height: 28px; }
  /* selected tab's colour comes from an inline style (JS sets background/color/border-color
     straight from the registry entry) — this class only removes the neutral default so that
     inline style shows through. */
  .repo-tab.sel { font-weight: 700; }
  /* IB-138 (BUILD 5): what IB/FM/SD/CB each mean, spelled out once under the chips — no tap
     required, so it reads on a phone where a title= tooltip never fires. */
  .repo-legend { font-size: 10.5px; color: #6e7681; padding: 4px 16px 0; line-height: 1.5; }
  .repo-legend:empty { display: none; }
  .b-dep-deployed { background: #133a1f; color: #3fb950; }
  .b-dep-merged { background: #3a2f0d; color: #d29922; }
  .b-dep-pending { background: #1f2937; color: #8b949e; }
  .b-dep-reverted { background: #3a1a1a; color: #f85149; }
  .b-dep-escalated { background: #3a1a1a; color: #f85149; }
  .msg { padding: 9px 16px; border-bottom: 1px solid #1b2129; font-size: 13px; }
  .msg:last-child { border-bottom: none; }
  .msg .who { color: #58a6ff; }
  .msg.load-more { padding: 8px 16px; text-align: center; }
  .msg.load-more button { background: transparent; border: none; color: #58a6ff; font: inherit; font-size: 12px; cursor: pointer; }
  .msg.load-more button:hover { text-decoration: underline; }
  .empty { padding: 16px; color: #6e7681; }
  .pri-badge { font-size: 10px; padding: 1px 7px; border-radius: 20px; font-weight: 700; margin-left: 6px; text-transform: uppercase; letter-spacing: .03em; }
  .pri-high { background: #3a1a1a; color: #f85149; }
  .pri-medium { background: #1c2d4a; color: #58a6ff; }
  .pri-low { background: #1f2937; color: #9ca3af; }
  .row { display: flex; gap: 10px; align-items: baseline; padding: 10px 16px; border-bottom: 1px solid #1b2129; }
  .row:last-child { border-bottom: none; }
  .row .k { flex: 1; }
  .row .v { color: #8b949e; font-variant-numeric: tabular-nums; }
  .prov { font-size: 11px; padding: 1px 7px; border-radius: 20px; font-weight: 600; }
  .prov-local { background: #133a1f; color: #3fb950; }
  .prov-hosted-cheap { background: #2a2140; color: #b392f0; }
  .prov-claude { background: #3a2a12; color: #f0883e; }
  .prov-glm { background: #5f1a1a; color: #f87171; }
  /* WO-KIMI-CHINA-BOUNDARY (task 464): second China-boundary lane — identical red to prov-glm. */
  .prov-kimi { background: #5f1a1a; color: #f87171; }
  /* WO-NEMOTRON-PROVIDER (task 388) / WO-COSMOS-VISION-PROVIDER (task 397): the meta-pill route
     already renders `prov-nemotron`/`prov-cosmos` generically (routedMetaHtml), but had no
     dedicated color rule (unstyled/default) — give both the same distinct-color treatment as
     every other provider pill, matching the header usage bar's deterministic fallback palette. */
  .prov-nemotron { background: #163a3d; color: #39c5cf; }
  .prov-cosmos { background: #3a1630; color: #f778ba; }
  /* IB-125 · "a second US brain, on a leash": xAI (Grok) — deliberately NOT sharing
     .prov-hosted-cheap's purple (that pill is labelled "Hosted-cheap (Groq/Together/Fireworks)"
     and is the one this must never be mistaken for) or any color already in use above. A gold/
     amber distinct from every other pill in this list, at a glance. */
  .prov-xai { background: #3a2f0a; color: #d4a72c; }

  /* deploy · Railway panel (task 4) */
  .plat { font-size: 11px; padding: 1px 7px; border-radius: 20px; font-weight: 600; }
  .plat-railway { background: #2a1f40; color: #b392f0; }
  .plat-local { background: #1f2937; color: #9ca3af; }
  .row .v a { color: #58a6ff; text-decoration: none; }
  .row .v a:hover { text-decoration: underline; }
  .row .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  /* Deploy rows: let a long value (esp. the commit message at the bottom, task 22 req 1) wrap
     rather than force horizontal scroll on a phone (task 22, req 4). */
  .panel-deploy .row { align-items: baseline; }
  .panel-deploy .row .v { min-width: 0; text-align: right; overflow-wrap: anywhere; }
  .panel-deploy .row .v .dmsg { display: inline-block; text-align: left; }
  .deploy-logs { border-top: 1px solid #21262d; }
  .deploy-logs .lh { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
                     color: #8b949e; padding: 8px 14px 4px; display: flex; align-items: center; gap: 8px; }
  .logs-refresh { margin-left: auto; background: #21262d; color: #adbac7; border: 1px solid #30363d;
                  border-radius: 6px; padding: 1px 8px; font-size: 12px; line-height: 1.6; cursor: pointer; }
  .logs-refresh:hover { color: #e6edf3; border-color: #3d444d; }
  .logline { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px;
             line-height: 1.5; padding: 1px 14px; white-space: pre-wrap; word-break: break-word;
             border-left: 2px solid transparent; }
  .logline .lt { color: #6e7681; }
  .logline.sev-error, .logline.sev-fatal, .logline.sev-critical { border-left-color: #f85149; color: #f0a9a5; }
  .logline.sev-warn, .logline.sev-warning { border-left-color: #d29922; color: #e2c08d; }
  .logline.sev-info { color: #c9d1d9; }
  .logline.sev-debug { color: #8b949e; }

  /* provider-accounts console (spec 613) */
  /* WO-MODAL-CLOSE-BTN (task 471): padding-top accounts for env(safe-area-inset-top) so the
     .sheet-head X button (positioned in normal flow, never pinned to the raw viewport top)
     always clears the iOS status bar / notch / Dynamic Island — see the matching @media
     (max-width:430px) override below where this mattered most (X landing under the clock). */
  .modal { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: rgba(0,0,0,.55);
           padding: 24px; padding-top: calc(24px + env(safe-area-inset-top)); overflow: auto; }
  .modal.on { display: block; }
  /* ══ IB-1443 · THE DETAIL DRAWER ═══════════════════════════════════════════════════════════
     A click on a coding card, a printer, or a production job opens this SAME drawer, INSTANTLY —
     every populate function (openCoderCardDetail/openPrinterCardDetail/openProductionCardDetail,
     dashboard.html) writes into it synchronously from data the page already holds, never behind a
     fetch. It must still open from every screen the always-on HUD lives on, including a phone's
     full-screen chat panel (.chatpanel.on, z-index 100) — same reasoning WO-HEADER-HUD-POLISH gave
     the old coder-detail sheet, so this keeps that raised tier. Backdrop pairs one tier below, per
     the z-index scale's own "raised panel + paired scrim" rule.
     Desktop: a right-edge panel, dragged wider/narrower via .dd-resize and remembered (dd-resize.js
     below + GET/POST /settings/drawer-width) — same "his bench" idiom as the panel layout (IB-1435)
     and the rail/tool-rail width drags. Phone (≤759px): full-screen, no resize handle — see the
     media block near the other ≤759px overrides. */
  .detail-drawer { display: none; position: fixed; top: var(--header-h); right: 0; bottom: 0;
                    width: var(--dd-w, 420px); max-width: 100vw; z-index: var(--z-modal-raised);
                    background: #161b22; border-left: 1px solid #30363d;
                    box-shadow: -12px 0 28px rgba(0,0,0,.4); flex-direction: column;
                    padding: 16px 18px calc(16px + env(safe-area-inset-bottom));
                    padding-top: calc(16px + env(safe-area-inset-top)); overflow: hidden; }
  .detail-drawer.on { display: flex; }
  .dd-resize { position: absolute; top: 0; bottom: 0; left: -4px; width: 8px; cursor: col-resize;
               background: transparent; touch-action: none; }
  .dd-resize:hover, body.dd-resizing .dd-resize { background: rgba(56,139,253,.35); }
  .dd-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
             flex: none; }
  .dd-title { font-size: 15px; margin: 0; }
  .dd-close { background: none; border: none; color: #8b949e; font-size: 18px; cursor: pointer;
              min-width: 44px; min-height: 44px; display: inline-flex; align-items: center;
              justify-content: center; flex: none; }
  .dd-meta, .dd-actions { overflow: auto; }
  .dd-backdrop { display: none; position: fixed; top: var(--header-h); left: 0; right: 0; bottom: 0;
                 background: rgba(0,0,0,.35); z-index: var(--z-modal-scrim); }
  .dd-backdrop.on { display: block; }
  @media (max-width: 759px) {
    .detail-drawer { top: 0; left: 0; width: 100%; max-width: 100%; border-left: none; }
    .dd-resize { display: none; }
    .dd-backdrop { top: 0; }
  }
  .sheet { max-width: 720px; margin: 0 auto; background: #161b22; border: 1px solid #30363d;
           border-radius: 12px; padding: 18px 20px 24px; }
  .sheet-head { display: flex; align-items: center; justify-content: space-between; }
  .sheet h2 { font-size: 15px; margin: 0; }
  .sheet h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: #8b949e;
              margin: 20px 0 8px; border-top: 1px solid #21262d; padding-top: 14px; }
  .sheet .x { background: none; border: none; color: #8b949e; font-size: 18px; cursor: pointer;
               min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  .sheet .hint { color: #8b949e; font-size: 12px; line-height: 1.5; margin: 6px 0; }
  .sheet .hint.note { color: #6e7681; font-style: italic; min-height: 16px; }

  /* Settings hub (single tabbed modal). Tabs scroll horizontally on narrow phones and wrap/stack
     cleanly so every tab stays tappable at 390px. Each pane is a settings screen moved here from
     the old separate modals; IDs and handlers are preserved. */
  .settings-sheet { max-width: 900px; }
  .settings-tabs { display: flex; gap: 2px; border-bottom: 1px solid #30363d;
                   margin: 14px -20px 16px; padding: 0 20px; overflow-x: auto;
                   scrollbar-width: none; }
  .settings-tabs::-webkit-scrollbar { display: none; }
  .settings-tab { flex: 0 0 auto; padding: 8px 14px; background: none; border: none;
                  border-bottom: 2px solid transparent; color: #8b949e; font-size: 13px;
                  cursor: pointer; white-space: nowrap; min-height: 40px; }
  .settings-tab:hover { color: #c9d1d9; }
  .settings-tab.active { color: #e6edf3; border-bottom-color: #58a6ff; }
  .settings-tab[hidden] { display: none; }
  .settings-pane { display: none; }
  .settings-pane.active { display: block; }
  /* Moved settings modals keep their .sheet-head titles hidden inside the hub (the tab is the title). */
  .settings-pane .sheet-head { display: none; }

  /* WO-SETTINGS-OVERVIEW (chunk 4 of 5): metric cards + health row. */
  .overview-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin: 12px 0; }
  .overview-metric-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; }
  .overview-metric-label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; }
  .overview-metric-value { font-size: 18px; font-weight: 600; color: #e6edf3; }
  .overview-health { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin: 12px 0; }
  .overview-health-item { background: #0d1117; border: 1px solid #21262d; border-radius: 6px; padding: 10px 12px; display: flex; flex-direction: column; gap: 3px; }
  .overview-health-label { font-size: 11px; color: #8b949e; }
  .overview-health-value { font-size: 12px; font-weight: 500; color: #e6edf3; }
  .overview-health-value.online { color: #3fb950; }
  .overview-health-value.offline { color: #f85149; }
  .overview-health-value.unknown { color: #8b949e; }

  .dev-shortcuts { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
  .dev-shortcuts button { background: #21262d; color: #e6edf3; border: 1px solid #30363d;
                          border-radius: 6px; padding: 6px 12px; font-size: 12px; cursor: pointer; }
  .dev-shortcuts button:hover { background: #30363d; }

  /* WO-SETTINGS-CONNECTIONS (chunk 2 of 5): editable vault path + compact printers list. */
  .vault-path-row { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
  .vault-path-row input { flex: 1 1 auto; min-width: 240px; background: #0d1117; color: #e6edf3; border: 1px solid #30363d; border-radius: 6px; padding: 7px 10px; font-size: 13px; }
  .vault-path-row button { background: #1f6feb; color: #fff; border: none; border-radius: 6px; padding: 7px 14px; font-size: 13px; cursor: pointer; }
  .vault-path-row button:hover { background: #388bfd; }
  .vault-path-row button.danger { background: #21262d; color: #f85149; border: 1px solid #30363d; }
  .vault-path-row button.danger:hover { background: #30363d; }
  .connections-printers { margin: 10px 0 12px; }

  /* WO-SETTINGS-BRAINS (chunk 1 of 5): AI-model accounts as compact color-coded cards.
     Responsive grid, provider logo badge, type chip, live usage + status dot, usage bar,
     and Disable/Delete actions. Mobile-first: cards stack on a phone. */
  .brains-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; margin: 12px 0 18px; }
  .brain-card { background: #0d1117; border: 1px solid #21262d; border-radius: 10px; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
  /* T46: the global master default card at the top of the Brains tab. */
  .brains-master-card { margin-bottom: 16px; padding: 12px 14px; border-radius: 10px;
    border: 1px solid #21262d; background: #0d1117; }
  .brains-master-card .brain-master { margin: 10px 0 6px; }
  .brains-master-hd h3 { margin: 0 0 4px; }
  .brain-card.off { opacity: .55; }
  .brain-card.active { border-color: #2ea043; }
  .brain-head { display: flex; align-items: flex-start; gap: 12px; }
  .brain-logo { flex: 0 0 auto; width: 44px; height: 44px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 700; color: #fff; text-transform: uppercase; }
  .brain-meta { flex: 1; min-width: 0; }
  .brain-name { font-weight: 600; font-size: 14px; line-height: 1.3; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .brain-name .pill-active { font-size: 10px; color: #4ac26b; font-weight: 600; }
  .brain-sub { color: #8b949e; font-size: 11px; display: block; margin-top: 3px; }
  .brain-type-chip { display: inline-block; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: 3px 7px; border-radius: 999px; }
  .brain-type-subscription { background: #16341f; color: #4ac26b; }
  .brain-type-metered { background: #3a2a12; color: #f0883e; }
  .brain-type-free { background: #1c2b40; color: #58a6ff; }
  .brain-usage { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #c9d1d9; }
  .brain-status-dot { width: 8px; height: 8px; border-radius: 50%; background: #3fb950; }
  .brain-status-dot.near { background: #d29922; }
  .brain-status-dot.exhausted { background: #f85149; }
  .brain-usage-bar { height: 4px; background: #21262d; border-radius: 2px; overflow: hidden; }
  .brain-usage-bar > span { display: block; height: 100%; border-radius: 2px; min-width: 2px; }
  .brain-actions { display: flex; gap: 8px; margin-top: auto; }
  .brain-actions button { flex: 1 1 auto; padding: 6px 10px; font-size: 12px; background: #21262d; border: 1px solid #30363d; color: #c9d1d9; border-radius: 6px; cursor: pointer; }
  .brain-actions button:hover { background: #30363d; }
  .brains-add { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
  .brains-add select, .brains-add input { background: #0d1117; color: #e6edf3; border: 1px solid #30363d; border-radius: 6px; padding: 7px 10px; font-size: 13px; }
  .brains-add select { flex: 0 0 auto; min-width: 160px; }
  .brains-add input { flex: 1 1 auto; min-width: 140px; }
  .brains-add button { flex: 0 0 auto; background: #238636; color: #fff; border: none; border-radius: 6px; padding: 7px 14px; font-size: 13px; cursor: pointer; }
  .brains-add button:hover { background: #2ea043; }

  /* T60 (WO-TV-BIG-BOARD) · Settings → General → Shop TV. Mirrors .brains-add's shape so the
     credential console reads as part of the same settings language. The one-time reveal box is
     deliberately loud (amber border) — that link is unrecoverable once this panel closes. */
  .tvtok-mint { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
  .tvtok-mint input { flex: 1 1 auto; min-width: 160px; background: #0d1117; color: #e6edf3;
                      border: 1px solid #30363d; border-radius: 6px; padding: 7px 10px; font-size: 13px; }
  .tvtok-mint button { flex: 0 0 auto; background: #238636; color: #fff; border: none; border-radius: 6px;
                       padding: 7px 14px; font-size: 13px; cursor: pointer; }
  .tvtok-mint button:hover { background: #2ea043; }
  .tvtok-mint button:disabled { background: #30363d; cursor: default; }
  .tvtok-new { margin-top: 10px; padding: 10px 12px; border: 1px solid #9e6a03; border-radius: 8px;
               background: #1c1600; display: flex; flex-direction: column; gap: 8px; }
  .tvtok-new-hd { font-size: 13px; font-weight: 700; color: #ffd479; }
  .tvtok-new input { width: 100%; background: #0d1117; color: #e6edf3; border: 1px solid #30363d;
                     border-radius: 6px; padding: 7px 10px; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  .tvtok-new button { align-self: flex-start; background: #1f6feb; color: #fff; border: none;
                      border-radius: 6px; padding: 6px 12px; font-size: 13px; cursor: pointer; }
  .tvtok-list { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; }
  .tvtok-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; background: #0d1117;
               border: 1px solid #21262d; border-radius: 6px; font-size: 13px; }
  .tvtok-row.revoked { opacity: .55; }
  .tvtok-row .lab { font-weight: 600; }
  .tvtok-row .meta { color: #8b949e; font-size: 12px; margin-left: auto; text-align: right; }
  .tvtok-row .rev { flex: 0 0 auto; background: #30363d; color: #f85149; border: none; border-radius: 6px;
                    padding: 5px 10px; font-size: 12px; cursor: pointer; }
  .tvtok-row .rev:hover { background: #442222; }
  .tvtok-row .dead { flex: 0 0 auto; color: #6e7681; font-size: 12px; }

  /* WO-CHAT-DECLUTTER (task 357): Ops Dashboard sheet — wider than the default .sheet (it holds
     6 cards), fixed compact card sizing (no stretch — auto-fill + justify-content:start, never
     grid `1fr` stretch), mobile-first (single column below 760px). Each card's own h2 doubles as
     its collapse toggle; .ops-chevron rotates to show open/closed, matching the collapsed state
     persisted per session in sessionStorage (see toggleOpsCard). */
  .ops-sheet { max-width: 980px; }
  .ops-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 320px));
              gap: 16px; justify-content: start; align-content: start; margin-top: 14px; }
  .ops-grid .panel { align-self: start; }
  .ops-grid .panel > h2 { cursor: pointer; user-select: none; }
  .ops-chevron { display: inline-block; transition: transform .15s ease; color: #6e7681; }
  .ops-grid .panel.collapsed .ops-chevron { transform: rotate(-90deg); }
  .ops-grid .panel.collapsed > .body { display: none; }
  /* mobile column-stack for .ops-grid lives in the shared big @media (max-width: 759px) block
     below (search WO-CHAT-DECLUTTER) — a second standalone occurrence of that exact media query
     string here would hijack the lazy regex some dashboard tests use to locate THAT block. */

  /* IB-1435 · "the dashboard is his bench": hide is never delete — a data-user-hidden panel is
     display:none (kept in the DOM, listed in #ops-hidden-list, one click to bring back). Not
     gated to desktop: hide/unhide and the reset control both work on a phone too, only the
     native-HTML5-drag reorder above is effectively mouse-only. */
  .ops-grid .panel[data-user-hidden] { display: none !important; }
  .ops-hide-btn { background: none; border: none; color: #8b949e; cursor: pointer; font-size: 12px;
                  padding: 2px 4px; line-height: 1; margin-left: 6px; }
  .ops-hide-btn:hover { color: #f85149; }
  .ops-reset-btn { background: none; border: 1px solid #30363d; color: #8b949e; border-radius: 6px;
                   padding: 3px 9px; font-size: 11px; cursor: pointer; margin-left: auto; }
  .ops-reset-btn:hover { background: #21262d; color: #c9d1d9; }
  .ops-hidden-list { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 10px;
                      font-size: 11px; color: #8b949e; }
  .ops-hidden-list button { background: none; border: 1px solid #30363d; color: #8b949e; border-radius: 6px;
                             padding: 3px 9px; font-size: 11px; cursor: pointer; }
  .ops-hidden-list button:hover { background: #21262d; color: #c9d1d9; }

  /* WO-CODER-STRIP-ROW3 (task 368): coders moved OFF the board body entirely onto a THIRD header
     row, directly under the action-strip (row 2) — Brandon glances at the fleet before he ever
     scrolls to Tasks. flex-basis:100% is the SAME "own full-width row" trick the action-strip
     already uses on `header` (display:flex; flex-wrap:wrap, see above) — no new layout mechanism.
     Deliberately no @media override (as before task 359) — a plain non-wrapping flex row with
     overflow-x:auto + scroll-snap is already correct at every width, phone included, without
     touching the shared 759px block a dashboard test greps for by first match (the ≤759px order
     is still needed though — see `.coder-cards { order: 41 }` in that block, right after
     `.action-strip { order: 40 }`, or it would jump ahead of the hamburger like task 230's bug).
     WO-HEADER-HUD-POLISH (task 387): used to hide during a desk chat exactly like the old board
     panel (body.desk-chat-open); now the ONE ops element exempt from #357's clean-chat rule, per
     Brandon's explicit request — it stays visible/ticking on every screen, desk chats included.
     The identical markup/renderer is reused for the Ops Dashboard's Coders card (task 357, see
     renderCoderCards) so the two views can never drift apart. */
  /* IB-119 · the coder strip is a fixed-shape rail: six slots, never wrapping, never reflowing
     when a coder starts or finishes. Empty slots stay empty so the rest of the UI never shifts. */
  .coder-cards { flex-basis: 100%; display: flex; flex-wrap: nowrap; align-items: flex-start; gap: 6px;
                 padding: 1px 0 0; min-height: 46px; overflow-x: auto; scrollbar-width: none; }
  .coder-cards::-webkit-scrollbar { display: none; }
  /* WO-HEADER-HUD-POLISH (task 387, req 1): #chat-coders is the phone-only mount inside the chat
     panel (see the markup comment there) — hidden by default (this ID rule outranks the shared
     .coder-cards class above on specificity) and revealed only by ".chatpanel.on #chat-coders" in
     the ≤759px block, which outranks THIS rule the same way. No !important tug-of-war needed. */
  #chat-coders { display: none; }
  /* IB-119 · empty coder slot keeps the same footprint as a real card so the row shape is stable. */
  .coder-slot { flex: 0 0 auto; width: 104px; height: 72px; max-width: 100%; max-height: 72px;
                overflow: hidden; box-sizing: border-box;
                background: #0d1117; border: 1px dashed #21262d; border-radius: 8px; }
  /* WO-CODER-CARDS-SQUARE (task 456): reshaped from a wide 114x44 rectangle to a square-ish
     ~104x100 tile — the old shape wasted a lot of empty horizontal space per Brandon's feedback.
     Content is now a vertical stack (dot+number on top via .cc-left, task#/timer below via
     .cc-right) instead of a side-by-side pair, so it reads naturally in a square. IB-119 keeps the
     strip from wrapping; it scrolls horizontally if the viewport is too narrow. Still a FIXED size,
     never flex-stretched. */
  /* TIB-075 §4 · A CARD TOO NARROW FOR ITS WORDS SHOWS ITS SHORT FORM, NEVER A RIBBON.
     `max-width: 100%` means a strip narrower than the tile shrinks the tile instead of letting it
     overflow its own container, and `container-type: inline-size` makes that measured width
     queryable — see the @container rule under .cc-why, which drops the task line, the clock and the
     reason band below the width where any of them can say something useful. What survives is the
     dot and the number: the short form. */
  .coder-card { flex: 0 0 auto; width: 104px; height: 72px; max-width: 100%; max-height: 72px;
                overflow: hidden;
                box-sizing: border-box; background: #0d1117; border: 1px solid #21262d;
                border-top: 3px solid #6e7681; border-radius: 8px; padding: 5px 6px 12px;
                cursor: pointer; display: flex; flex-direction: column; align-items: center;
                justify-content: center; gap: 2px; position: relative;
                container-type: inline-size; }
  .coder-card:hover { border-color: #30363d; }
  .coder-card.cc-claude { border-top-color: #f0883e; }
  .coder-card.cc-glm { border-top-color: #f87171; }
  /* WO-KIMI-CHINA-BOUNDARY (task 464): second China-boundary lane — identical red to cc-glm. */
  .coder-card.cc-kimi { border-top-color: #f87171; }
  /* task 395 req 5: idle/generic coder — no task in flight, so no brain guess. Same neutral
     tone as the default border/tag color (kept explicit so it can't silently drift). */
  .coder-card.cc-generic { border-top-color: #6e7681; }
  .cc-brain-tag { position: absolute; top: 4px; right: 6px; font-size: 7px; font-weight: 700;
                  letter-spacing: .02em; color: #6e7681; }
  .coder-card.cc-claude .cc-brain-tag { color: #f0883e; }
  .coder-card.cc-glm .cc-brain-tag { color: #f87171; }
  .coder-card.cc-kimi .cc-brain-tag { color: #f87171; }
  .coder-card.cc-generic .cc-brain-tag { color: #6e7681; }
  .cc-left { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 3px; }
  .cc-num { font-size: 22px; font-weight: 700; color: #c9d1d9; line-height: 1; }
  .cc-dot { width: 8px; height: 8px; border-radius: 50%; background: #6e7681; flex: 0 0 auto; }
  .cc-dot.idle, .cc-dot.available { background: #6e7681; }
  .cc-dot.running { background: #3fb950; box-shadow: 0 0 6px #3fb950; animation: cc-pulse 1.4s ease-in-out infinite; }
  .cc-dot.blocked { background: #f85149; box-shadow: 0 0 6px #f85149; animation: cc-pulse 1.4s ease-in-out infinite; }
  /* TIB-038 · offline is the AMBER case — genuinely no signal is the thing worth flagging. */
  .cc-dot.offline { background: #f0a020; box-shadow: 0 0 6px #f0a020; }
  /* TIB-038 · QUIET IS NOT A FAULT. A coder thinking hard for twenty minutes is working, so it
     keeps the green of a running build and merely stops pulsing — a calm, obviously-alive card,
     not a warning. Red is reserved for hung and blocked; amber for genuinely no signal. */
  .cc-dot.quiet { background: #3fb950; box-shadow: 0 0 6px #3fb950; opacity: 0.75; }
  .cc-dot.hung { background: #f85149; box-shadow: 0 0 6px #f85149; animation: cc-pulse 1.4s ease-in-out infinite; }
  /* TIB-076 · "this screen cannot see the box" is not "this coder is dead", and it must not draw the
     same dot. A hollow amber ring: present, unlit, unjudged — the shape of a reading we do not have
     rather than a verdict we do. */
  .cc-dot.unseen { background: transparent; border: 2px solid #f0a020; box-shadow: none; }
  /* IB-161 · SUSPECT: this desk's own heartbeat says running/quiet, and independent evidence (an
     OS process behind the claim, a recent worktree write — IB-153's own signals) disagrees. A
     wedged loop can fake a heartbeat forever, so this must never look like healthy green — a
     fast amber pulse, distinct from offline's steady amber ring and hung's steady red pulse. */
  .cc-dot.suspect { background: #f0a020; box-shadow: 0 0 6px #f0a020; animation: cc-pulse 0.8s ease-in-out infinite; }
  /* TIB-038 · the stage badge follows the same rule: quiet stays calm, only hung goes red. */
  /* IB-1345 · a real stage word (building/claiming/pushing/working) is still bounded — same
     ellipsis backstop as every other text span on the card, belt and braces against a surprise
     value. */
  .stagebadge { font-size: 10px; padding: 1px 5px; border-radius: 6px; background: #21262d; color: #8b949e;
                max-width: 100%; box-sizing: border-box; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .stagebadge.quiet { color: #7ee787; }
  .stagebadge.hung { background: #4a1d1d; color: #ff9a92; }
  @keyframes cc-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
  .cc-right { flex: 0 0 auto; max-width: 100%; display: flex; flex-direction: column; align-items: center; gap: 1px; }
  .cc-task { font-size: 10px; color: #8b949e; max-width: 100%; white-space: nowrap; overflow: hidden;
             text-overflow: ellipsis; text-align: center; }
  /* IB-119 · spec number leads the card face; the short label stays small and grey. */
  .cc-spec { font-size: 12px; font-weight: 700; color: #e6edf3; line-height: 1.1; }
  .cc-task-sep { color: #484f58; font-size: 10px; }
  .cc-task-label { font-size: 9px; color: #8b949e; max-width: 100%; white-space: nowrap; overflow: hidden;
                   text-overflow: ellipsis; }
  .cc-timer { font-size: 9px; color: #6e7681; font-variant-numeric: tabular-nums; max-width: 100%;
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* TIB-055 §4 · WHY THAT BRAIN, ON THE CARD. The card showed "Kimi" and nothing else, which is how
     a China-hosted model reconciling the code that runs every agent in this fleet went unseen. One
     short phrase — "no protected paths in this diff" — makes the routing decision checkable at a
     glance; the full sentence is in the card's tooltip and on the task's own record.
     It is a BOTTOM BAND on the card itself, not another line inside the stacked cluster: .cc-right
     is only as wide as "0:09", so a reason line parented there was ellipsised down to nothing —
     present in the DOM, measurable, and invisible to a person. (Chrome caught that; reading the
     code did not.) Absolute means it also adds no height to a card whose 72px is pinned by two
     layout suites.
     WHERE IT IS DRAWN, AND WHY NOT EVERYWHERE. The base tile is 104×72 and that 72 is pinned by two
     layout suites; measured in Chrome, its dot/number/task/clock stack already fills it, and a
     fourth line lands on top of the clock. So the band is drawn on the two surfaces that were given
     the room for it — the header strip and the Ops Coders card, both 92px — and everywhere else the
     reason stays in the card's tooltip and on its detail sheet. A line that overlaps the clock is
     not "shown". */
  .cc-why { display: none; position: absolute; left: 4px; right: 4px; bottom: 3px;
            font-size: 7px; line-height: 1.15; color: #6e7681; white-space: nowrap;
            overflow: hidden; text-overflow: ellipsis; text-align: center; }
  .coder-cluster-cards .coder-card .cc-why, #ops-coders .coder-card .cc-why { display: block; }
  /* TIB-075 §4 · the short form. Below 88px a 13px task number, a clock and a 7px reason band are
     three things none of which can be read; the card says the one thing it can — which coder, and
     what colour it is. Written as a container query on the card itself (not a viewport query) so it
     fires on the card's OWN measured width, wherever the card is mounted. */
  @container (max-width: 88px) {
    .cc-task, .cc-timer, .cc-why, .cc-brain-tag, .stagebadge { display: none !important; }
  }
  #ops-coders .coder-card { height: 92px; }
  /* IB-1345 · a separate declaration, not folded into the rule above — that exact string is read
     by a test as one unit (same reasoning as the mobile-breakpoint .coder-card override below). */
  #ops-coders .coder-card { max-height: 92px; }
  .cc-lock { position: absolute; top: 4px; left: 6px; font-size: 9px; line-height: 1; }
  .coder-cards .empty { flex: 0 0 auto; color: #6e7681; font-size: 12px; padding: 4px 8px; white-space: nowrap; }
  .coder-card.cc-add { background: transparent; border-style: dashed;
                        color: #6e7681; font-size: 20px; }
  .coder-card.cc-add:hover { color: #adbac7; border-color: #6e7681; }

  /* ── IB-137 · #chat-coders' cards are the SECOND control that survives alongside the tab strip
     above them (#coder-status-tabs) — Brandon's phone showed two stacked rows both claiming to be
     "the coder tabs", one of them overflowing with a task's full spec/repo/routing string. The
     strip above is now the primary control (a real tab: tap it, its panel opens); this row stays
     as a glance-only status mirror, so it must read as visibly SUBORDINATE rather than a second,
     competing set of tabs — smaller than the strip's own 40px tabs, dot + number only.
     Below TIB-075's existing 88px container-query threshold (§4 above) on purpose: that query
     already strips a card down to its dot+number short form, so shrinking the card is the entire
     fix — no change to coderCardHtml's markup or the shared .coder-card rules other mounts (the
     header strip, the Ops Coders card) still rely on. #chat-coders ID specificity beats every
     plain .coder-card rule above, including the ≤759px mobile override, at every width. Scoped to
     .strip-coders only — the sibling .strip-printers slot (a different feature's cards) is
     untouched. */
  #chat-coders .strip-coders .coder-card, #chat-coders .strip-coders .coder-slot {
    width: 34px; height: 34px; padding: 2px; border-radius: 6px;
  }
  #chat-coders .strip-coders .coder-card .cc-num { font-size: 13px; }
  #chat-coders .strip-coders .coder-card .cc-dot { width: 6px; height: 6px; }
  #chat-coders .strip-coders .coder-card .cc-lock { display: none; }
  /* Same three-bucket shape language as the tab strip above (§ .cst-dot): needs-attention states
     read as a diamond, not just a colour, even at this size. */
  #chat-coders .strip-coders .coder-card .cc-dot.blocked,
  #chat-coders .strip-coders .coder-card .cc-dot.hung,
  #chat-coders .strip-coders .coder-card .cc-dot.offline,
  #chat-coders .strip-coders .coder-card .cc-dot.unseen,
  #chat-coders .strip-coders .coder-card .cc-dot.suspect {
    border-radius: 2px; transform: rotate(45deg);
  }

  /* ── TIB-037 (B1) · SIZE IS A CLAIM ABOUT IMPORTANCE ──────────────────────────────────────
     The header strip used to render each coder as a 46×28 chip — a dot, a digit, no task, no
     clock — beside a 150×72 printer card with a gradient, a 22px teal countdown and a progress
     bar. Measured in Chrome: the printer was EIGHT TIMES the area of the thing Brandon is
     actually trying to supervise, on the screen he says leaves him blind. That is the wrong
     claim, and this is the correction. The compact-chip overrides are DELETED (not overridden)
     so the strip renders the same .coder-card the board and the chat mount render — one card,
     three homes — and it renders it bigger: the full face (dot, number, task, clock, brain,
     lock) at a size that reads from across the room. The printer drops to 100×56 above.
     After the base rule on purpose: several suites read `.coder-card { … width … }` with a
     first-match regex, and that match must keep landing on the card's own definition.
     TIB-055 · 76 → 92px. The strip card was already full at 76: the dot clipped against the top
     border and there was no room at all for the routing reason. The card is the thing Brandon
     watches while four builds run, and "which brain, and why that brain" is the fact that went
     unseen for a month — so the tile grew by one line rather than the line being dropped. */
  .coder-cluster-cards .coder-card { width: 132px; height: 92px; }
  /* IB-1345 · same "own declaration" reasoning as the #ops-coders rule above — a test reads the
     rule above as an exact string, so the max-height backstop lives here instead. */
  .coder-cluster-cards .coder-card { max-height: 92px; overflow: hidden; }
  .coder-cluster-cards .coder-card .cc-num { font-size: 30px; }
  .coder-cluster-cards .coder-card .cc-task { font-size: 13px; font-weight: 600; color: #adbac7; }
  .coder-cluster-cards .coder-card .cc-timer { font-size: 12px; }
  .coder-cluster-cards .coder-card .cc-dot { width: 10px; height: 10px; }
  .coder-cluster-cards .coder-card .cc-brain-tag { font-size: 8px; }
  /* The reason line stays at the base 7px even here, where every other element grew: measured in
     Chrome, 8px cost four characters of a 30-character sentence and bought no legibility. */
  .coder-cluster-cards .coder-card .cc-why { font-size: 7px; }
  /* The open card owns the panel below it — a blue seam so it is obvious WHICH card the output
     belongs to when four of them are running. */
  .coder-card.cc-open { border-color: #1f6feb; box-shadow: 0 0 0 1px #1f6feb inset; }

  /* IB-152 · THE PRINTER CARD READS LIKE A MACHINE.
     Four questions, top to bottom, one reading pass: which machine / what it is making / how much
     longer / how far along. TIME REMAINING IS THE HEADLINE — it is the number Brandon acts on, so it
     is the largest thing on the card. The job name is middle-truncated so the TAIL (version, part
     number) survives. The progress bar is 8 px tall and high-contrast so it reads from across the
     room. Idle/offline machines are still listed but smaller and dimmer — working machines draw the
     eye. Measured contrast ratios: teal #00d0c0 on #161b22 ≈ 8.9:1; bar fill #00d0c0 on track
     #21262d ≈ 7.7:1. */
  .printer-card { flex: 0 0 auto; width: 132px; height: 78px; box-sizing: border-box;
    position: relative; overflow: hidden; border: 1px solid #30363d; border-radius: 8px;
    cursor: pointer; background: #161b22; transition: border-color .4s ease, box-shadow .4s ease;
    display: flex; flex-direction: column; padding: 5px 6px 6px; }
  .printer-card:hover { border-color: #58a6ff; }
  .printer-card:focus-visible { outline: 2px solid #58a6ff; outline-offset: 1px; }
  /* A working machine glows so it catches a glance before a word is read. */
  .printer-card.pc-printing { box-shadow: 0 0 0 1px rgba(0,208,192,.15) inset, 0 0 14px rgba(0,208,192,.18); border-color: rgba(0,208,192,.45); }
  .printer-card.pc-paused { box-shadow: 0 0 0 1px rgba(210,153,34,.15) inset, 0 0 14px rgba(210,153,34,.18); border-color: rgba(210,153,34,.45); }
  .printer-card.pc-error { box-shadow: 0 0 0 1px rgba(248,81,73,.15) inset, 0 0 14px rgba(248,81,73,.22); border-color: rgba(248,81,73,.55); }
  .printer-card.pc-complete { box-shadow: 0 0 0 1px rgba(63,185,80,.15) inset, 0 0 14px rgba(63,185,80,.18); border-color: rgba(63,185,80,.45); }
  .printer-card .pc-head { display: flex; align-items: center; gap: 4px; min-width: 0; }
  .printer-card .pc-pulse { width: 6px; height: 6px; border-radius: 50%; background: #3fb950;
    box-shadow: 0 0 6px #3fb950; animation: cc-pulse 1.4s ease-in-out infinite; flex: 0 0 auto; }
  .printer-card.pc-paused .pc-pulse { background: #d29922; box-shadow: 0 0 6px #d29922; }
  .printer-card.pc-error .pc-pulse { background: #f85149; box-shadow: 0 0 6px #f85149; }
  .printer-card.pc-complete .pc-pulse { background: #3fb950; box-shadow: 0 0 6px #3fb950; animation: none; }
  .printer-card .pc-name { font-size: 9px; font-weight: 800; color: #c9d1d9; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; min-width: 0; letter-spacing: .2px; }
  .printer-card .pc-badge { font-size: 8px; font-weight: 800; letter-spacing: .3px; color: #8b949e;
    text-transform: uppercase; flex: 0 0 auto; }
  .printer-card.pc-error .pc-badge { color: #f85149; }
  /* THE headline: how much longer. 18 px teal, tabular nums so it never twitches. */
  .printer-card .pc-time { font-size: 18px; font-weight: 800; color: #00d0c0; line-height: 1;
    margin-top: 5px; font-variant-numeric: tabular-nums; white-space: nowrap; letter-spacing: -.3px; }
  /* Percent and bar share a row, but percent is never welded to the time. */
  .printer-card .pc-foot { display: flex; align-items: center; gap: 6px; margin-top: auto; min-width: 0; }
  .printer-card .pc-pct { font-size: 10px; font-weight: 800; color: #fff; line-height: 1;
    font-variant-numeric: tabular-nums; flex: 0 0 auto; }
  .printer-card .pc-bar { flex: 1 1 auto; height: 8px; border-radius: 4px; background: #21262d;
    overflow: hidden; min-width: 0; }
  .printer-card .pc-bar > i { display: block; height: 100%; background: #00d0c0; border-radius: 4px; }
  .printer-card.pc-error .pc-bar > i { background: #f85149; }
  .printer-card.pc-complete .pc-bar > i { background: #3fb950; }
  /* Idle/offline: smaller, dimmer, no internal competition with a working machine. */
  .printer-card.pc-idle, .printer-card.pc-offline { width: 132px; height: 40px; background: #0d1117;
    border-color: #21262d; box-shadow: none; flex-direction: row; align-items: center; justify-content: center;
    gap: 6px; padding: 0 6px; opacity: .72; }
  .printer-card.pc-idle .pc-state-dot, .printer-card.pc-offline .pc-state-dot { width: 8px;
    height: 8px; border-radius: 50%; background: #6e7681; flex: 0 0 auto; }
  .printer-card.pc-offline .pc-state-dot { background: transparent; border: 1px solid #4d5560; }
  .printer-card.pc-idle .pc-state-txt, .printer-card.pc-offline .pc-state-txt { font-size: 10px;
    font-weight: 700; color: #6e7681; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .printer-card.pc-offline .pc-state-txt { color: #4d5560; }
  .printer-card.pc-unreachable .pc-state-dot { background: transparent; border: 1px solid #d29922; }
  .printer-card.pc-unreachable .pc-state-txt { color: #d29922; text-align: center; line-height: 1.25; }

  /* WO-BAMBU-BRIDGE (task 391, req 2): Ops Dashboard card — status dot + job/progress bar reusing
     the SAME track/fill pattern as .brain-prov-track/.brain-prov-fill above, so the bar language
     stays consistent across cards instead of a one-off. Temps sit behind a tap (.bb-temps toggled
     via bambuToggleTemps) to keep the card compact per the "mobile-first, fixed compact sizing"
     requirement. */
  .bb-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
  .bb-dot { width: 10px; height: 10px; border-radius: 50%; background: #6e7681; flex: 0 0 auto;
            transition: background-color .4s ease, box-shadow .4s ease, border-color .4s ease; }
  .bb-dot.printing { background: #3fb950; box-shadow: 0 0 6px #3fb950; }
  .bb-dot.error { background: #f85149; box-shadow: 0 0 6px #f85149; }
  .bb-dot.idle, .bb-dot.paused, .bb-dot.complete { background: #6e7681; }
  .bb-dot.offline { background: transparent; border: 1px solid #4d5560; }
  /* TIB-031: amber, and it wins over .bb-dot.offline because it is the more specific statement —
     not "we have no data" but "the box cannot open a connection". */
  .bb-dot.unreachable, .bb-dot.offline.unreachable { background: transparent; border: 1px solid #d29922;
    box-shadow: 0 0 5px rgba(210,153,34,.5); }
  .bb-job { font-size: 13px; font-weight: 600; color: #c9d1d9; flex: 1 1 auto; min-width: 0;
            overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .bb-pct { font-size: 12px; color: #8b949e; font-variant-numeric: tabular-nums; }
  .bb-track { height: 6px; border-radius: 3px; background: #21262d; overflow: hidden; margin-bottom: 8px; }
  .bb-fill { display: block; height: 100%; border-radius: 3px; background: #58a6ff; }
  .bb-meta { display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; color: #8b949e; margin-bottom: 6px; }
  .bb-temps-toggle { font-size: 11px; color: #58a6ff; cursor: pointer; background: none; border: none;
                      padding: 0; text-decoration: underline; }
  .bb-temps { display: none; margin-top: 6px; font-size: 12px; color: #8b949e; }
  .bb-temps.on { display: block; }

  /* ── WO-PRINTERS-DASHBOARD (task 446): dedicated full-screen Printers view ───────────────
     Opens from the header printer chip (renderBambuStrip) — NOT buried at the bottom of the Ops
     card. Full-screen on mobile AND desktop (position:fixed; inset:0), same idiom as .chatpanel.on
     (the desk chat full-screen). z-index 60 sits above the Ops Dashboard sheet (50) but below the
     alert takeover so a real decision can still interrupt. The grid reuses .bb-* dot/track/fill
     language so cards read as the same system as the Ops bambu card, just bigger.
     NOTE: the registry is now live — getPrintersList() consumes the array from
     /ops/bambu/printers/status (one row per enabled registry printer). */
  /* overrides base .modal (24px padding + translucent bg) for a true full-bleed dedicated view */
  .printers-modal { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #0d1117;
                    flex-direction: column; height: 100vh; height: 100dvh; padding: 0; overflow: hidden; }
  .printers-modal.on { display: flex; }
  .printers-head { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
                   padding: 10px 14px; padding-top: max(10px, env(safe-area-inset-top));
                   padding-left: max(14px, env(safe-area-inset-left)); padding-right: max(14px, env(safe-area-inset-right));
                   border-bottom: 1px solid #21262d; background: #0d1117; }
  .printers-head h2 { font-size: 16px; margin: 0; color: #e6edf3; display: flex; align-items: center; gap: 8px; }
  .printers-head .count { color: #8b949e; font-size: 13px; font-weight: 400; }
  .printers-head .x { margin-left: auto; background: none; border: none; color: #8b949e; font-size: 18px;
                       cursor: pointer; min-width: 44px; min-height: 44px; display: inline-flex;
                       align-items: center; justify-content: center; }
  .printers-back { background: none; border: 1px solid #30363d; color: #c9d1d9; border-radius: 8px;
                   padding: 6px 12px; font-size: 13px; cursor: pointer; }
  .printers-back:hover { border-color: #58a6ff; color: #58a6ff; }
  .printers-scroll { flex: 1 1 auto; overflow: auto; -webkit-overflow-scrolling: touch; padding: 16px 14px 32px; }
  /* TIB-061 requirement 3 · THE STRIP HOLDS FOUR MACHINES LIKE IT HOLDS THREE.
     `repeat(auto-fill, minmax(min(100%,300px),1fr))` inside a 1100px box gives exactly three columns
     on a desktop, so a fourth printer dropped onto a row of its own next to the dashed "+ Add
     printer" tile — and a four-printer shop read as three printers plus something else. The column
     count is DATA now (--pc-cols, set by printerStripColumns from the machine count and the measured
     width), so the machines either all share one row or all stack, and there is never an odd one
     out. The Add tile is no longer one of them: it sits below the strip, because it is not a
     machine and should not be shaped like one. */
  .printers-grid { display: grid; grid-template-columns: repeat(var(--pc-cols, 1), minmax(0, 1fr)); gap: 14px;
                  /* One shape: every card in the strip is the same width AND the same height, so no
                     machine reads as a different kind of thing because its sentence is shorter. */
                  align-items: stretch;
                  /* A single printer must not be stretched across a 1500px desktop — the strip is as
                     wide as the machines in it, and no wider. */
                  max-width: min(1500px, calc(var(--pc-cols, 1) * 380px)); margin: 0 auto; }
  .printers-grid > .pc-wrap > .pc { height: 100%; }
  /* The Add tile is not a machine and is no longer shaped like one. */
  .printers-addrow { max-width: min(1500px, calc(var(--pc-cols, 1) * 380px)); margin: 14px auto 0; }
  .printers-addrow .pc-add { min-height: 44px; flex-direction: row; padding: 10px 16px; }
  /* WO-PRODUCTION-BOARD: operator stage board. Mirrors .printers-modal (full-bleed dark canvas) but
     the body is a Kanban-style stage grid: 7 ordered pipeline columns + a flagged row beneath for
     blocked_material / rejected, and an active-alarms bar across the top. Same #0d1117 / #161b22 /
     #30363d palette as every other panel; the printing card gets the accent-glow (box-shadow) the
     spec asks for. auto-fit + min(100%,…) makes the 7 columns collapse to a single stack on a phone
     with no extra rule (the 759px block below forces 1fr there as belt-and-suspenders). */
  .production-modal { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #0d1117;
                      flex-direction: column; height: 100vh; height: 100dvh; padding: 0; overflow: hidden; }
  .production-modal.on { display: flex; }
  .production-head { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
                     padding: 10px 14px; padding-top: max(10px, env(safe-area-inset-top));
                     padding-left: max(14px, env(safe-area-inset-left)); padding-right: max(14px, env(safe-area-inset-right));
                     border-bottom: 1px solid #21262d; background: #0d1117; }
  .production-head h2 { font-size: 16px; margin: 0; color: #e6edf3; display: flex; align-items: center; gap: 8px; }
  .production-head .count { color: #8b949e; font-size: 13px; font-weight: 400; }
  .production-head .x { margin-left: auto; background: none; border: none; color: #8b949e; font-size: 18px;
                        cursor: pointer; min-width: 44px; min-height: 44px; display: inline-flex;
                        align-items: center; justify-content: center; }
  .production-scroll { flex: 1 1 auto; overflow: auto; -webkit-overflow-scrolling: touch; padding: 12px 14px 32px; }

  /* active-alarms bar — hidden until an alarm-severity alert is live (display toggled in JS) */
  .production-alarms { display: none; flex-direction: column; gap: 8px; margin: 0 auto 12px; max-width: 1400px; }
  .palarm { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px;
            background: rgba(248,81,73,.10); border: 1px solid rgba(248,81,73,.45);
            border-left: 3px solid #f85149; border-radius: 10px; padding: 9px 12px; font-size: 13px; }
  .palarm-who { color: #f85149; font-weight: 600; }
  .palarm-title { color: #e6edf3; font-weight: 600; }
  .palarm-detail { color: #8b949e; }

  /* the 7 ordered pipeline columns */
  .production-columns { display: grid; max-width: 1400px; margin: 0 auto;
                        grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); gap: 10px; }
  .pcol { background: #161b22; border: 1px solid #21262d; border-radius: 12px;
          display: flex; flex-direction: column; min-width: 0; }
  .pcol-head { display: flex; align-items: center; justify-content: space-between; gap: 6px;
               padding: 9px 10px; border-bottom: 1px solid #21262d; }
  .pcol-lb { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #8b949e; }
  .pcol-count { font-size: 11px; color: #6e7681; background: #0d1117; border: 1px solid #30363d;
                border-radius: 999px; padding: 1px 7px; min-width: 18px; text-align: center; }
  .pcol-body { display: flex; flex-direction: column; gap: 8px; padding: 8px; min-height: 40px; }
  .pcol-body:empty::before { content: '—'; color: #484f58; font-size: 14px; align-self: center; padding: 6px 0; }

  /* job card — compact, dark; accent-glow on the active (printing) stage */
  .pjob { background: #0d1117; border: 1px solid #30363d; border-radius: 10px; padding: 9px 10px;
          display: flex; flex-direction: column; gap: 6px; transition: border-color .15s ease, box-shadow .15s ease; }
  .pjob-name { font-size: 13px; font-weight: 600; color: #e6edf3; word-break: break-word; }
  .pjob-meta { display: flex; flex-wrap: wrap; gap: 4px 8px; font-size: 11px; color: #8b949e; }
  .pjob-serial { font-family: ui-monospace, SFMono-Regular, monospace; color: #c9d1d9; }
  .pjob-qty { color: #6e7681; }
  .pjob-reason { font-size: 11px; color: #f85149; background: rgba(248,81,73,.10); border-radius: 6px; padding: 4px 6px; word-break: break-word; }
  .pjob-acts { display: flex; gap: 6px; flex-wrap: wrap; }
  .pjob-act { background: #21262d; border: 1px solid #30363d; color: #c9d1d9; border-radius: 7px;
              padding: 4px 8px; font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; min-height: 28px; }
  .pjob-act:hover { border-color: #58a6ff; color: #58a6ff; }
  /* WO-LIVE-PRINT-SEND: the confirm-a-physical-print button. Amber + bold on purpose — it is the one
     control on this board whose effect is a machine moving, so it must never look like Advance. */
  .pjob-act.pjob-send { border-color: #d29922; color: #f0c674; background: rgba(210,153,34,.12); }
  .pjob-act.pjob-send:hover { border-color: #f0c674; color: #ffd684; background: rgba(210,153,34,.22); }
  .pjob-send-state { display: flex; flex-wrap: wrap; gap: 3px 8px; font-size: 11px; align-items: baseline; }
  .pjob-send { font-weight: 600; }
  .pjob-send.ready { color: #f0c674; }
  .pjob-send.confirmed { color: #58a6ff; }
  .pjob-send.sent { color: #3fb950; }
  .pjob-send.failed { color: #f85149; }
  .pjob-send-file { font-family: ui-monospace, SFMono-Regular, monospace; color: #8b949e; word-break: break-all; }
  .pjob-send-ams { color: #6e7681; }
  .pjob-send-err { color: #f85149; word-break: break-word; }
  .pjob-active { border-color: #58a6ff; box-shadow: 0 0 10px rgba(88,166,255,.45); }
  .pjob-blocked_material { border-left: 3px solid #d29922; background: rgba(210,153,34,.06); }
  .pjob-rejected, .pjob-failed, .pjob-cancelled { border-left: 3px solid #f85149; background: rgba(248,81,73,.06); }

  /* WO-BOARD-LIVE-PROGRESS: live print telemetry on a Printing-stage card — a progress bar + %,
     the current layer (n/total), and the assigned printer's friendly name/model. Uses the same
     #58a6ff accent as the card's accent-glow so the bar reads as part of the active card. Only
     rendered when the poll resolved live telemetry; an offline printer shows a muted "Offline" and
     a printer with no progress yet shows "—" — never a bar with no data behind it. The block is
     flex-column, so on a phone (where the columns stack to 1fr) it fills the card width naturally. */
  .pjob-prog { display: flex; flex-direction: column; gap: 4px; padding-top: 2px; }
  .pjob-prog-name { font-size: 11px; color: #c9d1d9; word-break: break-word; }
  .pjob-prog-bar { position: relative; height: 6px; background: #21262d; border-radius: 999px; overflow: hidden; }
  .pjob-prog-fill { position: absolute; inset: 0 auto 0 0; width: 0;
                    background: linear-gradient(90deg, #58a6ff, #79c0ff); border-radius: 999px;
                    transition: width .3s ease; }
  .pjob-prog-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; font-size: 11px; }
  .pjob-prog-pct { color: #58a6ff; font-weight: 600; font-variant-numeric: tabular-nums; }
  .pjob-prog-layer { color: #8b949e; font-variant-numeric: tabular-nums; }
  .pjob-prog-muted { font-size: 11px; color: #6e7681; }

  /* flagged section beneath the columns — only rendered while a terminal job exists */
  .production-flagged { max-width: 1400px; margin: 14px auto 0; }
  .pflag-body { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr)); gap: 12px; }
  .pflag-group { background: #161b22; border: 1px solid #21262d; border-radius: 12px; padding: 10px; }
  .pflag-head { display: flex; align-items: center; justify-content: space-between; margin: 0 0 8px; }
  .pflag-lb { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #d29922; }
  .pflag-group.pflag-rejected .pflag-lb,
  .pflag-group.pflag-failed .pflag-lb,
  .pflag-group.pflag-cancelled .pflag-lb,
  .pflag-group.pflag-other .pflag-lb { color: #f85149; }
  .pflag-cards { display: flex; flex-direction: column; gap: 8px; }

  /* ── WO-PRINTER-JOB-QUEUE (T45): the multi-printer print queue ──────────────────────────────
     Same full-screen modal shell as the Production board (reusing .production-head/.production-
     scroll verbatim — it is the same kind of surface), with three stacked sections: the printer
     FLEET as auto-fitting lanes, then RUNNING and QUEUE as single-column lists. Everything is
     bounded — min-width:0 on the lanes/rows and break-word on names/reasons — so a long product
     name or a long block reason wraps inside its row instead of scrolling the page sideways. */
  .printqueue-modal { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #0d1117;
                      flex-direction: column; height: 100vh; height: 100dvh; padding: 0; overflow: hidden; }
  .printqueue-modal.on { display: flex; }
  .pq-wrap { max-width: 1400px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }
  .pq-sec-head { display: flex; align-items: baseline; gap: 8px; margin: 0 0 8px; }
  .pq-sec-lb { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: #8b949e; }
  .pq-sec-count { font-size: 11px; color: #6e7681; font-variant-numeric: tabular-nums; }
  .pq-empty { font-size: 12px; color: #6e7681; padding: 8px 2px; }
  /* IB-01: Add printer button in the print-queue header. Kept compact so the title stays primary. */
  .pq-add-btn { background: #21262d; border: 1px solid #30363d; color: #c9d1d9; border-radius: 6px;
                padding: 5px 10px; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
                margin-left: auto; margin-right: 8px; }
  .pq-add-btn:hover { background: #30363d; color: #e6edf3; border-color: #58a6ff; }

  /* fleet lanes — one per enabled registered printer */
  .pq-fleet { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 12px; }
  .pq-printer { min-width: 0; background: #161b22; border: 1px solid #21262d; border-radius: 12px; padding: 10px; }
  .pq-printer.pq-free { border-color: #238636; }
  .pq-printer.pq-err { border-color: #f85149; background: #21161a; }
  .pq-p-head { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
  .pq-p-name { font-size: 13px; font-weight: 600; color: #e6edf3; word-break: break-word; }
  .pq-p-model { font-size: 11px; color: #6e7681; }
  .pq-state { margin-left: auto; font-size: 10px; font-weight: 700; text-transform: uppercase;
              letter-spacing: .04em; padding: 2px 7px; border-radius: 999px;
              background: #21262d; color: #8b949e; }
  .pq-state.st-idle { background: #12261a; color: #3fb950; }
  .pq-state.st-printing { background: #12233d; color: #58a6ff; }
  .pq-state.st-error { background: #3d1418; color: #f85149; }
  .pq-state.st-paused { background: #3a2d10; color: #d29922; }
  .pq-p-why { font-size: 11px; color: #8b949e; margin-top: 4px; word-break: break-word; }
  /* IB-01: detailed printer info on the print-queue dashboard — thumbnail, temps, time remaining */
  .pq-p-body { display: flex; gap: 10px; margin-top: 8px; align-items: flex-start; }
  .pq-p-thumb { flex: 0 0 auto; width: 80px; height: 60px; border-radius: 8px; object-fit: cover;
                background: #0d1117; border: 1px solid #30363d; }
  .pq-p-thumb:empty { display: none; }
  .pq-p-meta { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
  .pq-p-temps { display: flex; flex-wrap: wrap; gap: 6px; font-size: 10px; color: #8b949e; }
  .pq-p-temps span { background: #0d1117; border: 1px solid #30363d; border-radius: 999px; padding: 2px 7px; }
  .pq-p-temps b { color: #e6edf3; font-weight: 600; }
  .pq-p-time { font-size: 11px; color: #00d0c0; font-weight: 700; }
  .pq-ams { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }
  .pq-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 10px; color: #c9d1d9;
             background: #0d1117; border: 1px solid #30363d; border-radius: 999px; padding: 2px 7px; }
  .pq-chip-dot { width: 8px; height: 8px; border-radius: 50%; border: 1px solid #484f58; flex: 0 0 auto; }
  .pq-chip-none { color: #6e7681; }

  /* job rows — running + pending share one shape */
  .pq-list { display: flex; flex-direction: column; gap: 8px; }
  .pq-job { min-width: 0; background: #161b22; border: 1px solid #21262d; border-radius: 12px; padding: 10px; }
  .pq-job.pq-blocked { border-color: #d29922; background: #221c10; }
  .pq-j-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
  .pq-pos { font-size: 11px; font-weight: 700; color: #6e7681; font-variant-numeric: tabular-nums; }
  .pq-j-name { font-size: 13px; font-weight: 600; color: #e6edf3; word-break: break-word; }
  .pq-j-id { font-size: 11px; color: #6e7681; font-variant-numeric: tabular-nums; }
  .pq-j-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 5px; align-items: center; }
  .pq-on { font-size: 11px; color: #58a6ff; word-break: break-word; }
  .pq-why { font-size: 11px; color: #d29922; margin-top: 5px; word-break: break-word; }
  .pq-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; align-items: center; }
  .pq-act { font-size: 11px; padding: 4px 9px; border-radius: 7px; cursor: pointer;
            background: #21262d; border: 1px solid #30363d; color: #c9d1d9; }
  .pq-act:hover { background: #30363d; }
  .pq-act.pq-cancel { color: #f85149; border-color: #4a1f22; }
  .pq-sel { font-size: 11px; padding: 4px 6px; border-radius: 7px; max-width: 190px;
            background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; }
  .pq-sel:disabled { color: #6e7681; }

  /* ── WO-CODING-BOARD: the live coding board ────────────────────────────────────────────────
     Same full-screen modal shell as the Production board (it is the same kind of thing — cards
     moving through lanes), with its own lane/card treatment. Everything here is bounded:
     min-width:0 on the lane + card and break-all on the branch/SHA mean a long branch name
     scrolls nothing sideways, it wraps inside its own card. */
  .coding-modal { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #0d1117;
                  flex-direction: column; height: 100vh; height: 100dvh; padding: 0; overflow: hidden; }
  .coding-modal.on { display: flex; }

  .coding-head { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
                 padding: 10px 14px; padding-top: max(10px, env(safe-area-inset-top));
                 padding-left: max(14px, env(safe-area-inset-left)); padding-right: max(14px, env(safe-area-inset-right));
                 border-bottom: 1px solid #21262d; background: #0d1117; }
  .coding-head h2 { font-size: 16px; margin: 0; color: #e6edf3; display: flex; align-items: center; gap: 8px; }
  .coding-head .count { color: #8b949e; font-size: 13px; font-weight: 400; }
  .coding-head .x { margin-left: auto; background: none; border: none; color: #8b949e; font-size: 18px;
                    cursor: pointer; min-width: 44px; min-height: 44px; display: inline-flex;
                    align-items: center; justify-content: center; }
  /* TIB-074 §3 · the bulk Retire door. margin-left:auto pushes it (and the ✕ after it) to the
     right, so it reads as a board-level action rather than part of the title. Written AFTER the
     base .x rule on purpose — an override placed above the block it overrides is how a first-match
     CSS assertion elsewhere ends up reading this rule as the base one. */
  .coding-head .cb-retire { margin-left: auto; background: #21262d; border: 1px solid #30363d;
    color: #c9d1d9; font-size: 12px; padding: 5px 10px; border-radius: 6px; cursor: pointer; }
  .coding-head .cb-retire:hover { background: #30363d; color: #e6edf3; }
  .coding-head .cb-retire + .x { margin-left: 10px; }
  .coding-scroll { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch;
                   padding: 12px 14px 32px; }

  /* the four ordered lanes: Queued → Running → Landing → Merged */
  .coding-lanes { display: grid; max-width: 1400px; margin: 0 auto; width: 100%;
                  grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr)); gap: 10px; }
  .clane { background: #161b22; border: 1px solid #21262d; border-radius: 12px;
           display: flex; flex-direction: column; min-width: 0; }
  .clane-head { display: flex; align-items: center; justify-content: space-between; gap: 6px;
                padding: 9px 10px; border-bottom: 1px solid #21262d; }
  .clane-lb { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
              color: #8b949e; min-width: 0; overflow-wrap: anywhere; }
  .clane-awaiting .clane-lb { color: #d29922; }
  .clane-merged .clane-lb { color: #3fb950; }
  .clane-count { font-size: 11px; color: #6e7681; background: #0d1117; border: 1px solid #30363d;
                 border-radius: 999px; padding: 1px 7px; min-width: 18px; text-align: center; flex: 0 0 auto; }
  .clane-body { display: flex; flex-direction: column; gap: 8px; padding: 8px; min-height: 40px; min-width: 0; }
  .clane-body:empty::before { content: '—'; color: #484f58; font-size: 14px; align-self: center; padding: 6px 0; }

  /* one task card */
  .cbcard { background: #0d1117; border: 1px solid #30363d; border-radius: 10px; padding: 9px 10px;
            display: flex; flex-direction: column; gap: 6px; min-width: 0;
            transition: border-color .15s ease, box-shadow .15s ease; }
  .cbcard-running { border-color: #58a6ff; box-shadow: 0 0 10px rgba(88,166,255,.35); }
  .cbcard-awaiting { border-left: 3px solid #d29922; }
  .cbcard-merged { border-left: 3px solid #3fb950; }
  .cbcard-blocked { border-left: 3px solid #f85149; background: rgba(248,81,73,.06); }
  .cb-top { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
  .cb-t { font-family: ui-monospace, SFMono-Regular, monospace; font-size: 12px; font-weight: 700; color: #58a6ff; }
  .cb-fold { font-size: 10px; font-weight: 700; color: #c9d1d9; background: #21262d;
             border-radius: 999px; padding: 1px 6px; }
  .cb-repo { font-size: 10px; color: #8b949e; background: #161b22; border: 1px solid #30363d;
             border-radius: 999px; padding: 1px 6px; overflow-wrap: anywhere; }
  .cb-repo.cb-repo-core { color: #f0c674; border-color: rgba(210,153,34,.5); }
  .cb-title { font-size: 13px; font-weight: 600; color: #e6edf3; overflow-wrap: anywhere; }
  .cb-meta { display: flex; flex-wrap: wrap; gap: 4px 8px; font-size: 11px; color: #8b949e; min-width: 0; }
  .cb-coder { color: #c9d1d9; }
  .cb-brain { font-weight: 600; }
  .cb-brain-claude { color: #a371f7; }
  .cb-brain-glm { color: #f0c674; }
  .cb-brain-kimi { color: #56d4dd; }
  .cb-branch, .cb-sha { font-family: ui-monospace, SFMono-Regular, monospace; word-break: break-all; }
  .cb-branch { color: #8b949e; }
  .cb-sha { color: #3fb950; }
  .cb-state { font-size: 10px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
              border-radius: 999px; padding: 1px 7px; border: 1px solid transparent; }
  .cb-state-deployed { color: #3fb950; border-color: rgba(63,185,80,.5); background: rgba(63,185,80,.10); }
  .cb-state-merged { color: #3fb950; border-color: rgba(63,185,80,.35); }
  .cb-state-escalated, .cb-state-conflict, .cb-state-green_gate_failed, .cb-state-gate_incomplete { color: #f0c674; border-color: rgba(210,153,34,.5); background: rgba(210,153,34,.10); }
  .cb-state-reverted, .cb-state-error { color: #f85149; border-color: rgba(248,81,73,.5); background: rgba(248,81,73,.10); }
  .cb-state-blocked { color: #f85149; border-color: rgba(248,81,73,.5); background: rgba(248,81,73,.10); }
  .cb-state-hold { color: #d29922; border-color: rgba(210,153,34,.5); }
  /* IB-1445 · plain/grey by default — red is earned, not the default, per isUrgentStatusKind
     (src/lib/codingBoard.js): only FAILED / NEEDS_YOU get .cb-reason-red. A rate limit, a deploy
     still in flight, or a "confirmed live" note all render in this same neutral box. */
  .cb-reason { font-size: 11px; color: #8b949e; background: rgba(139,148,158,.08); border-radius: 6px;
               padding: 4px 6px; overflow-wrap: anywhere; }
  .cb-reason-red { color: #f85149; background: rgba(248,81,73,.10); }
  .cb-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
  .cb-when { font-size: 11px; color: #6e7681; }
  .cb-acts { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; }
  .cb-act { background: #21262d; border: 1px solid #30363d; color: #c9d1d9; border-radius: 7px;
            padding: 4px 8px; font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; min-height: 28px; }
  .cb-act:hover { border-color: #58a6ff; color: #58a6ff; }
  /* Approve & merge is the one control here that changes main — amber + bold, like the Production
     board's physical-print button, so it never reads as a routine tap. */
  .cb-act.cb-approve { border-color: #d29922; color: #f0c674; background: rgba(210,153,34,.12); }
  .cb-act.cb-approve:hover { border-color: #f0c674; color: #ffd684; background: rgba(210,153,34,.22); }
  .coding-empty { max-width: 1400px; margin: 24px auto; text-align: center; color: #6e7681; font-size: 13px; }
  /* ── TIB-059 · THE LIVING MAP ───────────────────────────────────────────────────────────────
     KEEP THIS BLOCK BELOW THE CODING BOARD'S LAST RULE. It was originally inserted between
     `.coding-modal` and `.coding-head`, which put the map's fixed 2400×1500 canvas geometry inside
     the span coding-board.test.js reads as "the board's CSS" — and that test forbids a fixed pixel
     width, because one would defeat the board's fluid phone grid. The rules are fine; they were
     just in the wrong place. Moved here by TIB-060 (nothing about either block changed).

     Three panes: the conversation rail (left, where the map is actually edited), the canvas
     (centre), and the read-only detail panel (right). Same full-screen shell as the Coding board
     above — this is a place you go, not a card you glance at.

     The canvas is a fixed 2400×1500 coordinate space (src/ops/livingMap.js owns those numbers)
     that the stage scales and pans over, so an x/y saved from a drag on a laptop lands in exactly
     the same place on a phone. Boxes are absolutely-positioned DIVs (real text, selectable,
     draggable with a pointer or a thumb); connections are ONE SVG layer underneath at the same
     coordinate scale, so a line follows its boxes for free. */
  .map-modal { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #0d1117;
               flex-direction: column; height: 100vh; height: 100dvh; padding: 0; overflow: hidden; }
  .map-modal.on { display: flex; }
  .map-head { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
              padding: 10px 14px; padding-top: max(10px, env(safe-area-inset-top));
              padding-left: max(14px, env(safe-area-inset-left)); padding-right: max(14px, env(safe-area-inset-right));
              border-bottom: 1px solid #21262d; background: #0d1117; }
  /* TIB-081 · the title is a flex item inside a flex head, so without min-width:0 it refuses to
     shrink and the browser hyphen-free-wraps the count instead — measured at 390px as an 87×97
     column reading "10 / boxes / · / 9 / connections", one word per line. min-width:0 lets the
     title shrink and `nowrap` on the count keeps the sentence a sentence at every width. */
  .map-head h2 { font-size: 16px; margin: 0; color: #e6edf3; display: flex; align-items: center;
                 gap: 8px; min-width: 0; flex-wrap: wrap; }
  .map-head .count { color: #8b949e; font-size: 13px; font-weight: 400; white-space: nowrap; }
  .map-head .x { margin-left: auto; background: none; border: none; color: #8b949e; font-size: 18px;
                 cursor: pointer; min-width: 44px; min-height: 44px; display: inline-flex;
                 align-items: center; justify-content: center; }
  /* IB-1437 · a pure grouping wrapper on desktop/tablet: display:contents removes .map-controls
     from the box model entirely, so .map-alt/.map-trace-btn/.map-zoom lay out as direct flex
     children of .map-head exactly as before the wrapper existed. Only the ≤430px block (below)
     turns it into a real, scrollable flex row. */
  .map-controls { display: contents; }
  .map-zoom { display: flex; gap: 4px; margin-left: 12px; }
  .map-zoom button { background: #161b22; border: 1px solid #30363d; color: #c9d1d9; border-radius: 6px;
                     min-width: 34px; height: 30px; cursor: pointer; font-size: 13px; }
  .map-zoom button:hover { border-color: #58a6ff; color: #e6edf3; }
  .map-body { flex: 1 1 auto; display: grid; grid-template-columns: 320px minmax(0, 1fr) 300px;
              min-height: 0; overflow: hidden; }

  /* Left: the conversation rail. This is the input surface — talking is the only way to edit the
     map in v1, so the rail gets a full pane, not a popover. */
  .map-rail { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid #21262d;
              background: #0d1117; }
  .map-log { flex: 1 1 auto; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
  .map-turn { font-size: 13px; line-height: 1.5; border-radius: 8px; padding: 7px 10px; max-width: 100%;
              word-wrap: break-word; overflow-wrap: anywhere; }
  .map-turn.you { background: #1f2b3d; color: #d7e3f4; align-self: flex-end; }
  .map-turn.agent { background: #161b22; color: #c9d1d9; border: 1px solid #21262d; }
  .map-turn.pending { color: #8b949e; font-style: italic; }
  /* OPERATION RECEIPTS (spec item 5). Monospace, one line per change, and deliberately visually
     distinct from the agent's prose: the prose is the model talking, a receipt is the DATA
     reporting. Brandon must be able to see what moved without hunting the canvas. */
  .map-receipts { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 0; }
  .map-receipt { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
                 padding: 4px 8px; border-radius: 6px; background: #0f2416; color: #7ee787;
                 border-left: 2px solid #3fb950; word-wrap: break-word; overflow-wrap: anywhere; }
  .map-receipt.bad { background: #2d1a10; color: #ffa657; border-left-color: #d29922; }
  /* TIB-060 · THE MAP'S OWN ANSWER, printed under the model's words. Monospaced and pre-wrapped
     because it is a computed list, not prose — and visually distinct from the reply bubble above it
     for exactly one reason: so Brandon can see which of the two came from the data. A `not-covered`
     answer is styled as a refusal, because that is what it is and it must never look like an
     answer. */
  .map-answer { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
                line-height: 1.5; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: anywhere;
                background: #0f1c14; color: #b7e2c0; border-left: 3px solid #3fb950;
                border-radius: 0 6px 6px 0; padding: 7px 9px; margin-top: 4px; }
  .map-answer.ungrounded { background: #2d1a10; color: #ffa657; border-left-color: #d29922; }
  .map-answer .ma-head { display: block; font-size: 10px; text-transform: uppercase;
                         letter-spacing: .06em; color: #8b949e; margin-bottom: 4px; }
  /* ── TIB-064 · THE FOCUS CHIP ─────────────────────────────────────────────────────────────
     Directly above the message box, because its whole job is to answer "what does 'this' mean
     right now?" before he presses Enter. It sits in the reading path between the rail and the
     input; anywhere else and it is a status light nobody looks at. */
  .map-chip { flex: 0 0 auto; display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
              padding: 8px 10px 0; font-size: 12px; }
  .map-chip-label { color: #6e7681; text-transform: uppercase; letter-spacing: .06em; font-size: 10px; }
  .map-chip-card { display: inline-flex; align-items: center; gap: 5px; background: #10253f;
                   border: 1px solid #1f6feb; color: #cae0ff; border-radius: 999px;
                   padding: 3px 9px; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
                   white-space: nowrap; }
  /* The click ORDER, shown on the chip and on the card. "Link these" wires 1 → 2, so the numbers
     are not decoration — they are the direction of the arrow he is about to draw. */
  .map-chip-card i { font-style: normal; font-size: 10px; font-weight: 700; color: #0d1117;
                     background: #58a6ff; border-radius: 50%; width: 14px; height: 14px;
                     display: inline-flex; align-items: center; justify-content: center; }
  .map-chip-x { background: none; border: none; color: #8b949e; cursor: pointer; font-size: 13px;
                padding: 2px 4px; line-height: 1; }
  .map-chip-x:hover { color: #e6edf3; }
  .map-chip-none { color: #6e7681; font-style: italic; }
  .map-ask { flex: 0 0 auto; display: flex; gap: 6px; padding: 10px; border-top: 1px solid #21262d;
             padding-bottom: max(10px, env(safe-area-inset-bottom)); }
  .map-ask input { flex: 1 1 auto; min-width: 0; background: #0d1117; border: 1px solid #30363d;
                   color: #e6edf3; border-radius: 8px; padding: 9px 10px; font-size: 14px; }
  .map-ask input:focus { outline: none; border-color: #58a6ff; }
  .map-ask button { background: #238636; border: none; color: #fff; border-radius: 8px; padding: 0 14px;
                    font-size: 13px; cursor: pointer; min-height: 38px; }
  .map-ask button[disabled] { opacity: .5; cursor: default; }
  /* The microphone is DRAWN AND NOT WIRED — voice input is explicitly out of scope in v1. It is
     disabled and says so on hover, rather than being a button that silently does nothing. */
  .map-mic { background: #161b22 !important; border: 1px solid #30363d !important; color: #6e7681 !important;
             padding: 0 10px !important; }

  /* Centre: the canvas. */
  .map-canvas-wrap { position: relative; overflow: hidden; background:
      radial-gradient(circle at 1px 1px, #1c2128 1px, transparent 0) 0 0/28px 28px, #0b0f14;
      min-width: 0; touch-action: none; }
  .map-stage { position: absolute; top: 0; left: 0; width: 2400px; height: 1500px;
               transform-origin: 0 0; }
  /* TIB-087 · the ONLY animation the canvas ever does, and it is capped at 200ms because it fires
     while he is reading: nudging the selected card clear of the sheet, and landing on a card he
     tapped an edge indicator for. A permanent transition here would fight every drag. */
  .map-stage.gliding { transition: transform .2s ease-out; }
  @media (prefers-reduced-motion: reduce) { .map-stage.gliding { transition: none; } }

  /* ── TIB-087 · NOTHING IS EVER LOST OFF-SCREEN ─────────────────────────────────────────────
     The fence is gone, so a card can be dragged anywhere — which means the map now owes him a way
     back to one he cannot see. Every card outside the viewport gets a marker pinned to the edge it
     is past, naming it; tapping the marker pans to it. Fit is the other half of the same promise. */
  .map-marks { position: absolute; inset: 0; pointer-events: none; z-index: var(--z-raised); overflow: hidden; }
  /* Anchored to the edge it names, not centred on it — a marker centred on the right-hand edge
     hangs half of itself off the screen, which is the one place it cannot be read. */
  .map-mark { pointer-events: auto; position: absolute; transform: translate(-50%, -50%);
              display: inline-flex; align-items: center; gap: 5px; max-width: 60%;
              background: #1c2431; border: 1px solid #3d4757; color: #c9d1d9; cursor: pointer;
              border-radius: 999px; padding: 5px 9px; font: inherit; font-size: 11px;
              box-shadow: 0 2px 8px rgba(0,0,0,.55); }
  .map-mark.s-left   { transform: translateY(-50%); }
  .map-mark.s-right  { transform: translate(-100%, -50%); }
  .map-mark.s-top    { transform: translateX(-50%); }
  .map-mark.s-bottom { transform: translate(-50%, -100%); }
  .map-mark:hover { border-color: #58a6ff; color: #e6edf3; }
  .map-mark span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .map-mark i { flex: 0 0 auto; font-style: normal; color: #58a6ff; font-size: 10px; }
  .map-edges { position: absolute; top: 0; left: 0; width: 2400px; height: 1500px; overflow: visible;
               pointer-events: none; }
  .map-edge { fill: none; stroke: #495765; stroke-width: 2; }
  .map-edge-hit { fill: none; stroke: transparent; stroke-width: 14; }
  /* A money-flow connection is an ANIMATED DASHED GREEN line — the one connection type that reads
     differently at a glance, because "where does the money come from" is the question the map
     exists to answer. */
  .map-edge.money { stroke: #3fb950; stroke-width: 2.5; stroke-dasharray: 9 7;
                    animation: mapFlow 1.1s linear infinite; }
  @keyframes mapFlow { to { stroke-dashoffset: -32px; } }
  /* Clicking a box highlights EVERY line touching it — in and out, both directions. */
  .map-edge.lit { stroke: #58a6ff; stroke-width: 3.5; }
  .map-edge.lit.money { stroke: #56d364; }
  .map-edge.dim { opacity: .18; }
  .map-edge-label { font-size: 12px; fill: #8b949e; text-anchor: middle; paint-order: stroke;
                    stroke: #0b0f14; stroke-width: 4px; stroke-linejoin: round; }
  .map-edge-label.lit { fill: #c9d1d9; }
  .map-edge-label.dim { opacity: .18; }
  .map-arrow { fill: #495765; }
  .map-arrow.lit { fill: #58a6ff; }
  .map-arrow.money { fill: #3fb950; }
  /* TIB-064 · an arrowhead dims with its line. Without this a spotlit neighbourhood is surrounded
     by bright arrowheads pointing at dimmed nothing. */
  .map-arrow.dim { opacity: .18; }

  .map-box { position: absolute; width: 200px; min-height: 92px; box-sizing: border-box;
             background: #161b22; border: 1px solid #30363d; border-radius: 10px;
             border-left-width: 5px; padding: 9px 11px; cursor: grab; user-select: none;
             color: #e6edf3; box-shadow: 0 2px 10px rgba(0,0,0,.35); }
  .map-box:active { cursor: grabbing; }
  .map-box.sel { border-color: #58a6ff; box-shadow: 0 0 0 2px rgba(88,166,255,.35), 0 2px 14px rgba(0,0,0,.5); }
  .map-box-name { font-size: 14px; font-weight: 600; line-height: 1.25; word-wrap: break-word; overflow-wrap: anywhere; }
  .map-box-kind { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: #8b949e; margin-top: 3px; }
  .map-box-sub { font-size: 11px; color: #8b949e; margin-top: 4px; line-height: 1.35;
                 word-wrap: break-word; overflow-wrap: anywhere; }
  /* The colour stripe per kind (spec item 3). The left border IS the stripe. */
  .map-box.k-trunk      { border-left-color: #d29922; }
  .map-box.k-engine     { border-left-color: #a371f7; }
  .map-box.k-capability { border-left-color: #58a6ff; }
  .map-box.k-product    { border-left-color: #3fb950; }
  .map-box.k-shared     { border-left-color: #39c5cf; }
  .map-box.k-question   { border-left-color: #f0883e; border-style: dashed; border-left-style: solid; }
  .map-box-flag { font-size: 10px; color: #f0883e; margin-top: 4px; }

  /* ── TIB-087 · A CARD HE HAS DRESSED ───────────────────────────────────────────────────────
     Deliberately BELOW the six `.k-*` rules above, so the override wins on source order rather
     than on specificity — and so the first-match regexes other suites read `.map-box` rules with
     still find the base rule first.

     The override paints the SAME thing the kind stripe paints (the left border), plus a faint wash
     of the colour across the card so it reads from across the map at a glance. It deliberately does
     NOT touch `border-color`, which `.sel` owns — the selection ring must stay legible on a card
     that has been made orange. */
  .map-box.has-color { border-left-color: var(--card-accent);
                       background: linear-gradient(100deg, var(--card-wash), #161b22 62%); }
  /* The five faces a title can wear. A short closed list, not a font field: the map has to stay
     readable, and every stack here falls back to something every machine already has. */
  .map-box-name.f-serif   { font-family: Georgia, 'Iowan Old Style', 'Times New Roman', serif; }
  .map-box-name.f-slab    { font-family: Rockwell, 'Roboto Slab', 'Courier New', Georgia, serif;
                            letter-spacing: .01em; }
  .map-box-name.f-mono    { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
                            letter-spacing: -.02em; }
  .map-box-name.f-display { font-family: 'Avenir Next', Futura, 'Trebuchet MS', ui-rounded, sans-serif;
                            letter-spacing: .02em; text-transform: uppercase; font-size: 13px; }
  /* Three steps, and this is how a trunk reads as bigger than a chunk without a separate feature. */
  .map-box-name.ts-small  { font-size: 11px; }
  .map-box-name.ts-large  { font-size: 19px; line-height: 1.15; }
  .map-box-name.f-display.ts-small { font-size: 10px; }
  .map-box-name.f-display.ts-large { font-size: 17px; }

  /* ── TIB-064 · THE CHUNK BADGE, THE ORDER PIN, AND SPOTLIGHT ──────────────────────────────── */
  /* The badge is the fold control AND the count: "4 ▾" says this card has four chunks and they
     are showing. It is the only clickable thing on a card, which is why it is a real <button> —
     the rest of the card is a drag handle. */
  .map-fold { position: absolute; top: -9px; right: -9px; min-width: 26px; height: 20px;
              padding: 0 6px; border-radius: 999px; border: 1px solid #30363d; background: #21262d;
              color: #c9d1d9; font-size: 11px; font-weight: 600; cursor: pointer; line-height: 1;
              display: inline-flex; align-items: center; justify-content: center; gap: 2px;
              box-shadow: 0 1px 4px rgba(0,0,0,.5); }
  .map-fold:hover { border-color: #58a6ff; color: #e6edf3; }
  .map-fold.folded { background: #1f2b3d; border-color: #1f6feb; color: #cae0ff; }
  /* A folded card's own outline goes dashed — the map says "there is more under here" even when
     what is under here is off screen. */
  .map-box.has-folded { border-style: dashed; border-left-style: solid; }
  /* The click-order pin, drawn only when more than one card is focused. */
  .map-box-pin { position: absolute; top: -8px; left: -8px; width: 18px; height: 18px;
                 border-radius: 50%; background: #58a6ff; color: #0d1117; font-size: 11px;
                 font-weight: 700; display: flex; align-items: center; justify-content: center; }
  /* SPOTLIGHT (spec item 5): everything except one card and what it is directly wired to is
     dimmed. OPACITY ONLY — no pointer-events change, deliberately. A dimmed card is quiet, not
     dead: he can still click one to point at it, which is how he walks from one neighbourhood to
     the next without clearing the spotlight first. */
  .map-box.spot-dim { opacity: .18; }
  .map-box.spot-lit { box-shadow: 0 0 0 1px rgba(88,166,255,.4), 0 6px 26px rgba(0,0,0,.7); }

  /* ── TIB-060 · WORK, ON THE BOX ────────────────────────────────────────────────────────────
     One pip per attached spec, coloured by what the BOARD says that spec is doing right now. The
     colours are the ones this dashboard already uses for those states everywhere else, so a green
     pip means the same thing here it means on the Coding Board.

     THE PULSE IS THE POINT. A box with a spec actually running breathes — that is the difference
     between a diagram of the ecosystem and a live view of it, and it is the one thing on this
     screen that moves without Brandon touching anything. It rides on box-shadow, not on transform:
     a transform would fight the absolute positioning a drag writes into `left`/`top`. */
  .map-box-specs { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; align-items: center; }
  .map-pip { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 auto;
             border: 1px solid rgba(0,0,0,.35); }
  .map-pip.s-running { background: #3fb950; box-shadow: 0 0 6px rgba(63,185,80,.85); }
  .map-pip.s-queued  { background: #58a6ff; }
  .map-pip.s-blocked { background: #f85149; }
  .map-pip.s-landed  { background: #6e7681; }
  .map-pip.s-unknown { background: transparent; border-color: #6e7681; border-style: dashed; }
  .map-spec-count { font-size: 10px; color: #8b949e; letter-spacing: .02em; }
  /* The trunk roll-up (build item 4): how much of the fleet is pointed at this trunk right now,
     counted across everything underneath it — not just what is pinned to the trunk itself. */
  .map-rollup { position: absolute; top: -9px; right: -8px; background: #1f2b3d; color: #d7e3f4;
                border: 1px solid #30363d; border-radius: 999px; font-size: 10px; font-weight: 600;
                padding: 2px 7px; white-space: nowrap; box-shadow: 0 2px 6px rgba(0,0,0,.45); }
  .map-rollup.live { border-color: #3fb950; color: #7ee787; }
  .map-box.running { animation: mapPulse 1.9s ease-in-out infinite; border-color: #3fb950; }
  @keyframes mapPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(63,185,80,.55), 0 2px 10px rgba(0,0,0,.35); }
    50%      { box-shadow: 0 0 0 7px rgba(63,185,80,0), 0 2px 10px rgba(0,0,0,.35); }
  }
  /* One person's "visible pulse" is another's headache. Anyone who has asked their OS to stop
     animating things gets a steady green ring instead — still visibly different from every other
     box, still no movement. */
  @media (prefers-reduced-motion: reduce) {
    .map-box.running { animation: none; box-shadow: 0 0 0 2px rgba(63,185,80,.75), 0 2px 10px rgba(0,0,0,.35); }
  }

  /* Right: the detail panel. READ-ONLY in v1 — edits happen by talking, so there is deliberately
     nothing here to type into. */
  .map-detail { border-left: 1px solid #21262d; overflow-y: auto; padding: 14px; background: #0d1117;
                min-width: 0; }
  .map-detail h3 { margin: 0 0 2px; font-size: 16px; color: #e6edf3; word-wrap: break-word; overflow-wrap: anywhere; }
  .map-detail .md-kind { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: #8b949e; }
  .map-detail .md-sub { font-size: 12px; color: #8b949e; margin-top: 6px; }
  .map-detail .md-desc { font-size: 13px; line-height: 1.55; color: #c9d1d9; margin-top: 12px;
                         white-space: pre-wrap; word-wrap: break-word; overflow-wrap: anywhere; }
  .map-detail .md-sec { margin-top: 16px; }
  .map-detail .md-sec h4 { margin: 0 0 6px; font-size: 11px; text-transform: uppercase;
                           letter-spacing: .06em; color: #8b949e; font-weight: 600; }
  .map-detail .md-row { font-size: 12px; color: #c9d1d9; padding: 4px 0; border-bottom: 1px solid #161b22;
                        word-wrap: break-word; overflow-wrap: anywhere; }
  .map-detail .md-row .md-lbl { color: #8b949e; }
  .map-detail .md-row .md-money { color: #3fb950; }
  /* TIB-060: a spec row in the detail panel is the pip plus the number plus the word, because a
     colour alone is not a state anybody can read out loud. */
  .map-detail .md-spec { display: flex; align-items: center; gap: 7px; }
  .map-detail .md-spec .md-state { color: #8b949e; font-size: 11px; }
  .map-detail .md-spec .md-state.s-running { color: #7ee787; }
  .map-detail .md-spec .md-state.s-blocked { color: #ff7b72; }
  .map-detail .md-spec .md-state.s-queued { color: #79c0ff; }
  .map-detail .md-empty { font-size: 12px; color: #6e7681; font-style: italic; }
  .map-detail img { max-width: 100%; border-radius: 6px; margin-top: 6px; display: block; }
  .map-detail a { color: #58a6ff; word-break: break-all; }
  .map-legend { display: flex; flex-wrap: wrap; gap: 6px 10px; margin-top: 18px; padding-top: 12px;
                border-top: 1px solid #21262d; }
  .map-legend span { font-size: 10px; color: #8b949e; display: inline-flex; align-items: center; gap: 4px; }
  .map-legend i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }

  /* ── TIB-087 · THE SHEET, AND WHY IT IS MOSTLY NOT THERE ───────────────────────────────────
     "I want as much visible space on screen for mobile to go to seeing the cards. Popups should be
     minimal with NO white space."

     Two things do that. The RENDER drops every empty section — no "none", no "no specs attached",
     no "No description yet"; a row that would say nothing does not exist. And the LAYOUT below
     tightens every gap that survived, so nothing on this sheet gets more vertical space than the
     text inside it needs. Every one of these numbers replaces a larger one. */
  .map-detail .md-grip { display: none; }              /* a phone affordance; the sidebar has none */
  .map-detail .md-line { font-size: 12px; color: #8b949e; margin-top: 3px; line-height: 1.35;
                         overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* The counts, as chips. "2 depends · 2 feeds" answers the only question the compact sheet is for
     — is this card wired to anything — in one line instead of two headed sections. */
  .map-detail .md-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
  .map-detail .md-chip { font-size: 11px; color: #adbac7; background: #1c2431; border: 1px solid #30363d;
                         border-radius: 999px; padding: 2px 8px; white-space: nowrap; }
  .map-detail .md-more { margin-top: 8px; width: 100%; background: #161b22; border: 1px solid #30363d;
                         color: #8b949e; border-radius: 8px; padding: 6px; font: inherit; font-size: 11px;
                         cursor: pointer; letter-spacing: .04em; text-transform: uppercase; }
  .map-detail .md-more:hover { border-color: #58a6ff; color: #c9d1d9; }

  /* ── TIB-087 · THE PALETTE ─────────────────────────────────────────────────────────────────
     Twelve colours, five faces, three sizes — and in every one of the three rows the FIRST control
     is the way back to the default. An override he cannot undo is a trap, not a feature. */
  .map-detail .md-look { margin-top: 12px; }
  .md-swatches { display: flex; flex-wrap: wrap; gap: 6px; }
  .md-swatch { width: 24px; height: 24px; border-radius: 6px; border: 1px solid #30363d; cursor: pointer;
               padding: 0; font: inherit; font-size: 11px; color: #8b949e; background: #161b22;
               display: inline-flex; align-items: center; justify-content: center; }
  .md-swatch.on { border-color: #e6edf3; box-shadow: 0 0 0 2px rgba(230,237,243,.35); }
  .md-picks { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
  .md-pick { background: #161b22; border: 1px solid #30363d; color: #8b949e; border-radius: 6px;
             padding: 3px 9px; font: inherit; font-size: 11px; cursor: pointer; }
  .md-pick.on { border-color: #58a6ff; color: #e6edf3; background: #10253f; }
  .md-pick.p-serif   { font-family: Georgia, 'Iowan Old Style', 'Times New Roman', serif; }
  .md-pick.p-slab    { font-family: Rockwell, 'Roboto Slab', 'Courier New', Georgia, serif; }
  .md-pick.p-mono    { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
  .md-pick.p-display { font-family: 'Avenir Next', Futura, 'Trebuchet MS', ui-rounded, sans-serif; }

  /* ── IB-111 · GATES, DONE, THE THROTTLE, AND THE MAP THAT FOLDS ITSELF ─────────────────────
     A gate is AMBER AND DASHED and the card it holds wears a padlock. Amber because it is the one
     colour on this screen that already means "held, not broken" (the merge tray's decision holds
     use it); dashed because a hold is a line that is not flowing yet. Both, not one: colour alone
     is not a state anybody can read out loud, and dash alone disappears at low zoom.

     These rules sit BELOW everything above them on purpose — same source-order discipline TIB-087's
     `.has-color` follows — so a card that is not gated, not done and not throttled draws byte for
     byte the way it did before this build. */
  .map-edge.gate { stroke: #d29922; stroke-width: 2.5; stroke-dasharray: 7 6; }
  .map-arrow.gate { fill: #d29922; }
  /* A MET gate is still drawn — the hold is a fact about the map and it did not stop being true
     when it opened. It goes quiet: same amber, no weight, so a chain of met gates reads as a path
     already walked rather than as four more things to worry about. */
  .map-edge.gate.met { stroke: #6f5a1e; stroke-width: 2; stroke-dasharray: 3 6; }
  .map-arrow.gate.met { fill: #6f5a1e; }
  .map-edge.gate.lit { stroke: #f0b429; stroke-width: 3.5; }

  /* The padlock. Bottom-left, so it never collides with the chunk badge or the roll-up (top-right)
     or the click-order pin (top-left) — three corners were already spoken for. */
  .map-lock { position: absolute; bottom: -9px; left: -8px; display: inline-flex; align-items: center;
              gap: 3px; padding: 1px 7px 1px 5px; border-radius: 999px; background: #2d2410;
              border: 1px solid #d29922; color: #f0b429; font-size: 10px; font-weight: 600;
              line-height: 1.6; white-space: nowrap; max-width: 190px; overflow: hidden;
              text-overflow: ellipsis; box-shadow: 0 2px 6px rgba(0,0,0,.5); }
  .map-box.waiting { border-color: #d29922; border-style: dashed; border-left-style: solid; }

  /* DONE. A checked card is quiet and unmistakable: the tick, the name struck through, and the
     whole card dropped back in contrast — it is finished, so it stops competing for his eye. */
  .map-box.done { opacity: .72; background: #12191b; border-color: #2a3a2c; }
  .map-box.done .map-box-name { text-decoration: line-through; text-decoration-color: #3fb950;
                                text-decoration-thickness: 2px; color: #9db3a2; }
  .map-done-tick { position: absolute; bottom: -9px; left: -8px; display: inline-flex; align-items: center;
                   gap: 3px; padding: 1px 8px; border-radius: 999px; background: #10251a;
                   border: 1px solid #3fb950; color: #7ee787; font-size: 10px; font-weight: 700;
                   line-height: 1.6; box-shadow: 0 2px 6px rgba(0,0,0,.5); }

  /* THE THROTTLE. Subdued, not hidden — a throttled card is still a card he can read and click; it
     is just visibly downstream of something red. Desaturated rather than dimmed, so it stays apart
     from `.spot-dim` (which means "not what we are talking about") at a glance. */
  .map-box.throttled { filter: saturate(.35) brightness(.72); }
  .map-box.throttled::after { content: ''; position: absolute; inset: 0; border-radius: 9px;
                              background: repeating-linear-gradient(135deg, rgba(248,81,73,.10) 0 6px, transparent 6px 12px);
                              pointer-events: none; }
  /* And the red card itself: ringed, so the thing causing it is never mistaken for one of the
     things suffering from it. */
  .map-box.bottleneck { box-shadow: 0 0 0 2px rgba(248,81,73,.65), 0 2px 14px rgba(0,0,0,.5); }

  /* THE CASCADE BANNER. One line across the top of the canvas, and it says the two numbers that
     matter — how many cards are showing and how many there are — plus the one control that
     puts them all back. A fold he cannot see is a map that lost cards. */
  .map-cascade { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: var(--z-raised);
                 display: none; align-items: center; gap: 10px; max-width: calc(100% - 24px);
                 background: #10253f; border: 1px solid #1f6feb; border-radius: 999px;
                 padding: 5px 6px 5px 14px; color: #cae0ff; font-size: 12px; white-space: nowrap;
                 box-shadow: 0 6px 20px rgba(0,0,0,.55); }
  .map-cascade.on { display: flex; }
  .map-cascade b { font-weight: 700; color: #e6edf3; }
  .map-cascade .mc-what { overflow: hidden; text-overflow: ellipsis; }
  .map-cascade button { background: #1f6feb; border: none; color: #fff; border-radius: 999px;
                        padding: 3px 12px; font: inherit; font-size: 11px; cursor: pointer; }
  .map-cascade button:hover { background: #388bfd; }

  /* THE ALTITUDE CONTROL. Three steps, always all three visible with their counts, because the
     point of an altitude is knowing what you are NOT looking at. */
  .map-alt { display: flex; gap: 0; margin-left: 10px; border: 1px solid #30363d; border-radius: 8px;
             overflow: hidden; }
  .map-alt button { background: #161b22; border: none; border-right: 1px solid #30363d; color: #8b949e;
                    font: inherit; font-size: 11px; padding: 4px 9px; cursor: pointer; white-space: nowrap; }
  .map-alt button:last-child { border-right: none; }
  .map-alt button:hover { color: #e6edf3; }
  .map-alt button.on { background: #10253f; color: #cae0ff; }
  .map-alt button i { font-style: normal; color: #6e7681; margin-left: 4px; }
  .map-alt button.on i { color: #79c0ff; }

  /* ── IB-114 · TRACE, ROUTES AND THE REPLAY ─────────────────────────────────────────────────
     A traced path has to be readable WHILE it is being clicked — the badge says which step this
     card is, and the line says the path so far. Both are drawn from the same ordered list of ids,
     so a badge and the line can never disagree about the order.

     THE STEP BADGE sits top-right-of-centre, clear of all four corners already spoken for: the
     chunk badge (top-right), the click-order pin (top-left), the padlock and the done tick
     (bottom-left). A trace badge landing on top of a padlock would hide a hold. */
  .map-trace-pin { position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
                   min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px;
                   background: #a371f7; color: #0d1117; font-size: 12px; font-weight: 800;
                   display: flex; align-items: center; justify-content: center;
                   box-shadow: 0 0 0 3px rgba(163,113,247,.28), 0 2px 8px rgba(0,0,0,.6); }
  .map-box.traced { border-color: #a371f7; box-shadow: 0 0 0 2px rgba(163,113,247,.35), 0 2px 14px rgba(0,0,0,.5); }
  /* The line through the trace, drawn in the same SVG layer as the wires so it pans and zooms with
     them. It GLOWS rather than just being coloured: the trace is the one thing on the canvas that
     is being built right now, and it has to read as live against fifty static wires. */
  .map-trace-line { fill: none; stroke: #a371f7; stroke-width: 4; stroke-linecap: round;
                    stroke-linejoin: round; stroke-dasharray: 14 10; opacity: .95;
                    filter: drop-shadow(0 0 6px rgba(163,113,247,.9));
                    animation: mapTraceGlow 1s linear infinite; }
  @keyframes mapTraceGlow { to { stroke-dashoffset: -48px; } }
  /* Trace mode itself, on the control in the header. */
  .map-trace-btn { background: #161b22; border: 1px solid #30363d; color: #c9d1d9; border-radius: 8px;
                   padding: 4px 11px; font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap;
                   margin-left: 10px; }
  .map-trace-btn:hover { border-color: #a371f7; color: #e6edf3; }
  .map-trace-btn.on { background: #241a3a; border-color: #a371f7; color: #d2b8ff; }
  /* IB-125 · COMPLETE is green rather than purple — it is the one control on this row that commits
     instead of toggling a mode, and it needs to read as different at a glance from Trace itself. */
  .map-trace-complete-btn { border-color: #2ea043; color: #7ee787; }
  .map-trace-complete-btn:hover { border-color: #3fb950; color: #b7f5c4; }
  .map-trace-btn:disabled { opacity: .5; cursor: default; }
  .map-trace-btn:disabled:hover { border-color: #30363d; color: #c9d1d9; }

  /* IB-125 · THE REVIEW PANEL. It takes over the same right-hand sheet a card's own detail uses —
     one panel, one place to look, whichever the screen is showing right now — so Save/Discard read
     exactly like every other action in the .map-detail column already does. */
  .map-review-row { display: flex; align-items: center; gap: 8px; }
  .map-review-step { flex: 0 0 auto; min-width: 20px; height: 20px; border-radius: 999px;
                     background: #a371f7; color: #0d1117; font-size: 11px; font-weight: 800;
                     display: flex; align-items: center; justify-content: center; }
  .map-review-name { flex: 1 1 auto; color: #e6edf3; }
  .map-review-kind { flex: 0 0 auto; font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
                     color: #8b949e; }
  .map-review-save { display: flex; gap: 6px; margin-top: 16px; }
  .map-review-save input { flex: 1 1 auto; min-width: 0; background: #0d1117; border: 1px solid #30363d;
                           border-radius: 8px; color: #e6edf3; font: inherit; font-size: 12px; padding: 6px 9px; }
  .map-review-save input:focus { outline: none; border-color: #58a6ff; }
  .map-review-save-btn { flex: 0 0 auto; background: #10251a; border: 1px solid #3fb950; color: #7ee787;
                         border-radius: 8px; padding: 6px 12px; font: inherit; font-size: 12px; cursor: pointer; }
  .map-review-save-btn:hover { border-color: #56d364; color: #b7f5c4; }
  .map-review-discard-btn { width: 100%; margin-top: 8px; background: #161b22; border: 1px solid #30363d;
                            color: #c9d1d9; border-radius: 8px; padding: 6px; font: inherit; font-size: 12px;
                            cursor: pointer; }
  .map-review-discard-btn:hover { border-color: #ff7b72; color: #ffb4ac; }

  /* THE ROUTE PILLS. On the map surface, always one glance away — bottom-left of the canvas in
     screen coordinates, so they do not pan away with the cards. The name plays the route; the ⤡
     folds the map to its members. Two controls in one pill because they are two things to do with
     one route, and a pill that only played would leave the fold with nowhere to live. */
  .map-routes { position: absolute; left: 10px; bottom: 10px; z-index: var(--z-raised); display: flex;
                flex-wrap: wrap; gap: 6px; max-width: calc(100% - 20px); }
  .map-route-pill { display: inline-flex; align-items: stretch; border: 1px solid #30363d;
                    border-radius: 999px; overflow: hidden; background: #161b22;
                    box-shadow: 0 4px 14px rgba(0,0,0,.5); }
  .map-route-pill button { background: none; border: none; color: #c9d1d9; font: inherit;
                           font-size: 12px; padding: 4px 11px; cursor: pointer; white-space: nowrap; }
  .map-route-pill .mrp-play:hover { color: #d2b8ff; }
  .map-route-pill .mrp-fold { border-left: 1px solid #30363d; color: #6e7681; padding: 4px 9px; }
  .map-route-pill .mrp-fold:hover { color: #cae0ff; }
  .map-route-pill i { font-style: normal; color: #6e7681; margin-left: 5px; }
  .map-route-pill.playing { border-color: #a371f7; background: #241a3a; }
  .map-route-pill.playing .mrp-play { color: #d2b8ff; }
  .map-route-pill.folded { border-color: #1f6feb; background: #10253f; }
  .map-route-pill.folded .mrp-fold { color: #cae0ff; }

  /* THE REPLAY. Deliberately NOT a second dimming system: a step being lit and everything off the
     route being quiet is exactly what spotlight already draws, so the replay reuses `.spot-dim` /
     `.spot-lit` and adds only the one thing spotlight has no idea about — WHICH STEP this is. */
  .map-box.step-now { border-color: #a371f7; box-shadow: 0 0 0 3px rgba(163,113,247,.5), 0 8px 30px rgba(0,0,0,.7); }
  .map-step-pin { position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
                  min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px;
                  background: #30363d; color: #c9d1d9; font-size: 12px; font-weight: 700;
                  display: flex; align-items: center; justify-content: center; }
  .map-box.step-now .map-step-pin { background: #a371f7; color: #0d1117;
                                    box-shadow: 0 0 0 3px rgba(163,113,247,.3); }

  /* The card sheet's three new controls: check it off, see what is holding it, fold to its world. */
  .map-detail .md-act { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
  .map-detail .md-act button { background: #161b22; border: 1px solid #30363d; color: #c9d1d9;
                               border-radius: 8px; padding: 5px 11px; font: inherit; font-size: 12px;
                               cursor: pointer; }
  .map-detail .md-act button:hover { border-color: #58a6ff; color: #e6edf3; }
  .map-detail .md-act button.md-done-on { background: #10251a; border-color: #3fb950; color: #7ee787; }
  .map-detail .md-act button.md-cascade-on { background: #10253f; border-color: #1f6feb; color: #cae0ff; }
  .map-detail .md-gate { display: flex; align-items: center; gap: 7px; }
  .map-detail .md-gate .md-gs { font-size: 11px; }
  .map-detail .md-gate .md-gs.unmet { color: #f0b429; }
  .map-detail .md-gate .md-gs.met { color: #7ee787; }
  .map-detail .md-gate .md-ungate { margin-left: auto; background: none; border: none; color: #6e7681;
                                    cursor: pointer; font-size: 12px; padding: 0 2px; }
  .map-detail .md-gate .md-ungate:hover { color: #ff7b72; }

  /* Phone/tablet: the three panes stack into rail-over-canvas, and the detail panel becomes a
     sheet at the bottom. The canvas keeps the same coordinate space — only the viewport changes. */
  @media (max-width: 900px) {
    .map-body { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr) 210px; }
    .map-rail { grid-row: 2; border-right: none; border-top: 1px solid #21262d; }
    .map-canvas-wrap { grid-row: 1; }
    .map-detail { display: none; }
    /* TIB-087 · A FIRST TAP GETS ~30% OF THE VIEWPORT, NOT HALF THE SCREEN. `max-height` (not
       height) is the point: an empty card's sheet is three lines tall and takes three lines — the
       30% is a ceiling, never a floor, so the map keeps everything the sheet did not need. */
    .map-detail.open { display: flex; flex-direction: column; position: absolute; inset: auto 0 0 0;
                       max-height: 30vh; max-height: 30dvh; z-index: var(--z-raised); border-top: 1px solid #30363d;
                       padding: 0 12px 10px; padding-bottom: max(10px, env(safe-area-inset-bottom));
                       border-radius: 14px 14px 0 0; box-shadow: 0 -8px 26px rgba(0,0,0,.55);
                       overflow: hidden; }
    /* Dragged up: the whole card, scrolling. */
    .map-detail.open.expanded { max-height: 88vh; max-height: 88dvh; overflow-y: auto; }
    /* The grab handle. It is also the tap target that toggles compact/full, and it is a full-width
       44px band rather than the 4px pill it looks like — a thumb aims at the pill and lands here. */
    /* Sticky, not merely first: the expanded sheet scrolls, and a grip that scrolled away would
       leave him with no way to drag it back down. */
    .map-detail .md-grip { display: block; position: sticky; top: 0; z-index: var(--z-raised); flex: 0 0 auto;
                           height: 22px; margin: 0 -12px; cursor: grab; touch-action: none;
                           background: #0d1117; }
    .map-detail .md-grip::before { content: ''; position: absolute; left: 50%; top: 9px; width: 40px;
                                   height: 4px; margin-left: -20px; border-radius: 999px; background: #495765; }
    /* Every gap on this sheet, tightened. Nothing gets more room than its text needs. */
    .map-detail.open h3 { font-size: 15px; }
    .map-detail.open .md-sub { margin-top: 2px; }
    .map-detail.open .md-desc { margin-top: 8px; font-size: 12px; line-height: 1.45; }
    .map-detail.open .md-sec { margin-top: 10px; }
    .map-detail.open .md-sec h4 { margin-bottom: 3px; }
    .map-detail.open .md-row { padding: 3px 0; }
    .map-detail.open .map-legend { margin-top: 12px; padding-top: 8px; }
  }
  /* one printer card */
  .pc { background: #161b22; border: 1px solid #30363d; border-top: 3px solid #6e7681; border-radius: 12px;
        padding: 14px; cursor: pointer; text-align: left; font: inherit; color: inherit;
        transition: border-color .15s ease, transform .08s ease; display: flex; flex-direction: column; gap: 10px; }
  .pc:hover { border-color: #58a6ff; }
  .pc:active { transform: translateY(1px); }
  .pc.pc-printing { border-top-color: #3fb950; }
  .pc.pc-paused { border-top-color: #d29922; }
  .pc.pc-error { border-top-color: #f85149; }
  .pc.pc-complete { border-top-color: #58a6ff; }
  /* TIB-031: amber for "cannot reach it", and it must beat .pc-offline's default because a card can
     carry both classes. The outage sentence sits where the job name would be, in the same amber. */
  .pc.pc-unreachable { border-top-color: #d29922; }
  .pc .pc-unreach-line { color: #d29922; font-size: 12px; }
  .pc-top { display: flex; align-items: center; gap: 12px; }
  .pc-thumb { width: 56px; height: 56px; border-radius: 10px; background: #21262d; flex: 0 0 auto;
              display: flex; align-items: center; justify-content: center; font-size: 28px; overflow: hidden; }
  .pc-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* WO-PRINTER-CARD-DEDUPE req 3: the X1 Carbon product thumbnail, with the old 🖨️ glyph kept in
     the DOM UNDERNEATH it as the fallback. The <img> sits on top and covers the glyph; if the asset
     is missing/404s its onerror removes the <img> and the glyph is simply revealed — no broken-image
     frame, no second render pass, and nothing to re-check on later polls. */
  .pt-wrap { position: relative; }
  .pt-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
            display: block; background: #21262d; }
  .pc-id { flex: 1 1 auto; min-width: 0; }
  .pc-name { font-size: 15px; font-weight: 700; color: #e6edf3; white-space: nowrap; overflow: hidden;
             text-overflow: ellipsis; display: flex; align-items: center; gap: 8px; }
  /* req 2: click-to-edit printer name. The pencil only appears on hover/focus so the card reads
     clean at rest; the input is a real <input> (which is why .pc is a div[role=button] and not a
     <button> — an input nested in a button is invalid and unfocusable in several browsers). */
  .pc-name .pc-name-txt { overflow: hidden; text-overflow: ellipsis; }
  .pc-rename-btn { border: 0; background: none; padding: 0 2px; cursor: pointer; color: #6e7681;
                   font-size: 12px; line-height: 1; opacity: 0; transition: opacity .12s ease; flex: 0 0 auto; }
  .pc:hover .pc-rename-btn, .pd-name:hover .pc-rename-btn, .pc-rename-btn:focus-visible { opacity: 1; }
  .pc-rename-btn:hover { color: #58a6ff; }
  .pc-rename { display: flex; align-items: center; gap: 6px; }
  .pc-rename input { font: inherit; font-size: 15px; font-weight: 700; color: #e6edf3; background: #0d1117;
                     border: 1px solid #58a6ff; border-radius: 6px; padding: 3px 8px; min-width: 0; width: 100%;
                     max-width: 260px; }
  .pc-rename .pc-rn-save, .pc-rename .pc-rn-cancel { border: 1px solid #30363d; background: #21262d;
                     color: #e6edf3; border-radius: 6px; padding: 3px 9px; font-size: 12px; font-weight: 600;
                     cursor: pointer; flex: 0 0 auto; }
  .pc-rename .pc-rn-save { border-color: rgba(63,185,80,.45); color: #3fb950; }
  .pc-rename .pc-rn-err { font-size: 11px; color: #f85149; font-weight: 600; }
  .pc-model { font-size: 10px; font-weight: 700; letter-spacing: .03em; color: #58a6ff;
              background: rgba(88,166,255,.12); border: 1px solid rgba(88,166,255,.3); border-radius: 5px;
              padding: 1px 6px; white-space: nowrap; }
  .pc-state { font-size: 12px; color: #8b949e; display: flex; align-items: center; gap: 6px; margin-top: 3px; flex-wrap: wrap; }
  .pc-state .bb-dot { width: 8px; height: 8px; }
  .pc-state b { color: #c9d1d9; font-weight: 600; }
  .pc-job { font-size: 13px; color: #c9d1d9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .pc-job .muted { color: #6e7681; }
  /* IB-135: the bar is a plain visual fill now — the percentage it used to hold in .pc-pct beside it
     is said once, in .pc-state above, not repeated here. */
  .pc-bar .bb-track { margin-bottom: 0; height: 8px; }
  /* TIB-061 · the card's three new lines, each with exactly one job.
     .pc-jobname  — WHICH job (only when there is one)
     .pc-notes    — the ONE explanatory sentence: setup gap, outage, fault, or a disagreement
     .pc-caps     — what this machine can do here, always present */
  .pc-jobname { font-size: 13px; color: #c9d1d9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .pc-notes { display: flex; flex-direction: column; gap: 4px; }
  .pc-notes .pc-job { white-space: normal; font-size: 12px; line-height: 1.4; }
  .pc .pc-setup-line { color: #8b949e; }
  .pc .pc-fault-line { color: #f0857d; }
  .pc .pc-disagree-line { color: #d29922; }
  .pc .pc-hold-line { color: #8b949e; }
  /* The capability line. Deliberately quiet — it is a standing fact about the machine, not news —
     but never absent, because "monitoring only" being missing is what made a deliberate limit read
     as a broken feature. */
  .pc-caps { margin-top: auto; padding-top: 8px; border-top: 1px solid #21262d;
             font-size: 11px; color: #6e7681; line-height: 1.6; }
  .pc-caps .pc-cap-make { font-weight: 700; color: #8b949e; letter-spacing: .02em; }
  .pc-caps .pc-cap-sep { color: #30363d; }
  /* A read-only machine's line is the one that has to be readable at a glance, so it gets the
     amber the rest of this surface already uses for "true, and worth knowing". */
  .pc-caps .pc-cap-ro { color: #d29922; font-weight: 600; }
  /* A row that has never been set up is not an error and must not look like one. */
  .bb-dot.unconfigured { background: transparent; border: 1px solid #4d5560; }
  /* "+ Add printer" tile opens the add-printer modal */
  .pc-add { border-style: dashed; border-top-style: dashed; color: #6e7681; align-items: center; justify-content: center;
            min-height: 150px; font-size: 14px; gap: 6px; }
  .pc-add .plus { font-size: 24px; line-height: 1; }
  .pc-add .soon { font-size: 11px; color: #6e7681; }
  .pc-add:hover { color: #adbac7; border-color: #6e7681; }
  /* detail view */
  .printers-detail { max-width: 820px; margin: 0 auto; }
  /* WO-PRINTER-INLINE-DETAIL: expand detail in place under the clicked card */
  .pc-wrap { display: flex; flex-direction: column; min-width: 0; }
  .pc-wrap.expanded { grid-column: 1 / -1; }
  .pc-wrap.expanded .pc { width: 100%; }
  .pc-detail-inline { margin-top: 10px; }
  .pc-detail-inline .printers-detail { max-width: none; margin: 0; }
  /* task 532 (dense layout): ONE tight card, not a stack of separately-boxed sections. .pd-hero now
     carries the head (name/model/status), the model/camera hero media, big progress + job line all
     inside ONE panel with reduced internal padding; the sections below it (stats/AMS/controls) are
     tightened to match rather than each reading as its own card. */
  .pd-hero { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 14px; margin-bottom: 10px; }
  .pd-hero-top { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
  .pd-thumb { width: 40px; height: 40px; border-radius: 9px; background: #21262d; flex: 0 0 auto;
              display: flex; align-items: center; justify-content: center; font-size: 20px; overflow: hidden; }
  .pd-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .pd-name .pc-rename input { font-size: 18px; max-width: 320px; }
  .pd-name { font-size: 18px; font-weight: 700; color: #e6edf3; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  /* ── task 532 (2 + 3): the hero media — MODEL thumbnail (the slicer's plate preview, from the
     job's .3mf) and the live CAMERA frame, one Model/Camera toggle switching between them in the
     SAME viewport rather than two separate sections. Square-ish and content-driven (no fixed
     breakpoint) so it reflows cleanly from a phone to a wide desktop card. */
  .pd-media { margin-bottom: 10px; }
  .pd-media-tabs { display: flex; gap: 6px; margin-bottom: 6px; }
  .pd-media-tab { border-radius: 999px; padding: 5px 12px; font-size: 12px; font-weight: 700; cursor: pointer;
                  border: 1px solid #30363d; background: #21262d; color: #8b949e; }
  .pd-media-tab:hover { border-color: #58a6ff; color: #c9d1d9; }
  .pd-media-tab.on { background: rgba(88,166,255,.16); border-color: #58a6ff; color: #58a6ff; }
  .pd-media-body { position: relative; width: 100%; max-width: 320px; aspect-ratio: 4 / 3;
                    border-radius: 12px; border: 1px solid #30363d; background: #0d1117; overflow: hidden; }
  .pd-media-pane { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
  .pd-media-img { width: 100%; height: 100%; object-fit: contain; display: block; background: #0d1117; }
  .pd-media-empty { display: flex; flex-direction: column; align-items: center; gap: 6px; color: #6e7681;
                     font-size: 12px; text-align: center; padding: 10px; }
  .pd-media-empty .glyph { font-size: 30px; }
  .pd-media-capture { position: absolute; right: 8px; bottom: 8px; border-radius: 999px; padding: 6px 12px;
                       font-size: 12px; font-weight: 700; cursor: pointer; border: 1px solid rgba(88,166,255,.4);
                       background: rgba(13,17,23,.85); color: #58a6ff; }
  .pd-media-capture:hover { border-color: #58a6ff; background: rgba(88,166,255,.16); }
  .pd-media-capture:disabled { opacity: .6; cursor: not-allowed; }
  .pd-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 6px; margin-bottom: 8px; }
  .pd-stat { background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 7px 10px; }
  .pd-stat .k { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: #6e7681; }
  .pd-stat .v { font-size: 15px; font-weight: 700; color: #e6edf3; font-variant-numeric: tabular-nums; margin-top: 1px; }
  .pd-stat .v small { color: #6e7681; font-weight: 400; }
  .pd-controls { display: flex; flex-wrap: wrap; gap: 8px; }
  .pd-btn { display: inline-flex; align-items: center; gap: 6px; border-radius: 10px; padding: 8px 14px;
            font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid #30363d; background: #21262d; color: #e6edf3; }
  .pd-btn:hover { border-color: #58a6ff; }
  .pd-btn:disabled { opacity: .5; cursor: not-allowed; }
  .pd-btn.pd-pause:hover { border-color: #d29922; color: #d29922; }
  .pd-btn.pd-stop { background: rgba(248,81,73,.12); border-color: rgba(248,81,73,.4); color: #f85149; }
  .pd-btn.pd-stop:hover { background: rgba(248,81,73,.2); border-color: #f85149; }
  .pd-btn.pd-light.on { background: rgba(210,153,34,.16); border-color: #d29922; color: #f0c674; }
  .pd-cam-wrap { margin-top: 10px; }
  .pd-cam-img { width: 100%; max-height: 420px; object-fit: contain; border-radius: 10px; border: 1px solid #30363d; background: #0d1117; display: block; }
  .pd-cam-empty { border: 1px dashed #30363d; border-radius: 10px; padding: 28px; text-align: center; color: #6e7681; font-size: 13px; }
  .pd-note { font-size: 12px; color: #6e7681; margin-top: 8px; line-height: 1.5; }
  .pd-section-hd { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: #8b949e; margin: 10px 0 6px; }
  /* ── T43 (WO-PRINTER-CONTROLS): resume / speed / light / skip-objects / error-ack ─────────────
     Segmented rows for the modal controls (speed, light) so the ACTIVE mode is legible at a glance
     without a second readout, plus the loud command-failure banner every control reports through. */
  .pd-btn.pd-resume { background: rgba(63,185,80,.12); border-color: rgba(63,185,80,.4); color: #3fb950; }
  .pd-btn.pd-resume:hover { background: rgba(63,185,80,.2); border-color: #3fb950; }
  .pd-seg { display: flex; flex-wrap: wrap; gap: 8px; }
  .pd-seg button { border-radius: 999px; padding: 8px 14px; font-size: 13px; font-weight: 600; cursor: pointer;
                   border: 1px solid #30363d; background: #21262d; color: #c9d1d9; }
  .pd-seg button:hover:not(:disabled) { border-color: #58a6ff; color: #58a6ff; }
  .pd-seg button:disabled { opacity: .5; cursor: not-allowed; }
  .pd-seg button.on { background: rgba(88,166,255,.16); border-color: #58a6ff; color: #58a6ff; }
  .pd-skip { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
  .pd-skip select, .pd-skip input { background: #0d1117; border: 1px solid #30363d; border-radius: 8px;
                                    color: #e6edf3; padding: 8px 10px; font-size: 13px; min-width: 180px; }
  .pd-skipped { font-size: 12px; color: #8b949e; margin-top: 8px; }
  /* TIB-062 · the command record on a printer card. A physical action with no record is the one
     thing this shop does not do, so this reads as a log and not as a status: newest first, one row
     per command, colour carrying only whether the MACHINE took it. */
  .pd-cmdlog { list-style: none; margin: 8px 0 0; padding: 0; max-height: 220px; overflow-y: auto; }
  .pd-cmdlog li { font-size: 12px; padding: 6px 8px; border-left: 3px solid #30363d; margin-bottom: 4px; background: #0d1117; border-radius: 0 4px 4px 0; }
  .pd-cmdlog li b { text-transform: uppercase; letter-spacing: .04em; }
  .pd-cmdlog-ok { border-left-color: #3fb950; }
  .pd-cmdlog-err { border-left-color: #f85149; }
  .pd-cmdlog-wait { border-left-color: #d29922; }
  .pd-err-ack { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
  .pd-cmd-err { margin-top: 12px; border: 1px solid #f85149; border-left-width: 4px; border-radius: 8px;
                background: rgba(248,81,73,.12); color: #ffa198; font-size: 13px; padding: 10px 12px; line-height: 1.5; }
  .pd-cmd-err .x { margin-left: 10px; cursor: pointer; color: #f85149; font-weight: 700; }
  .pd-cmd-ok { margin-top: 12px; border: 1px solid rgba(63,185,80,.5); border-left-width: 4px; border-radius: 8px;
               background: rgba(63,185,80,.1); color: #7ee787; font-size: 13px; padding: 10px 12px; line-height: 1.5; }
  /* ── T44 (WO-AMS-MANAGEMENT): the AMS slot grid. One tile per slot: the RFID material + color
     swatch, a remaining-% meter, the unit's humidity, and the two per-slot controls. Same tile
     vocabulary as .pd-stat above (same surface, border, radius) so the panel reads as one card. */
  /* task 532: a compact 4-slot strip rather than a loose card grid — minmax dropped from 210px so
     all four AMS trays comfortably fit one row on a normal-width card. */
  .pd-ams { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 6px; }
  .ams-slot { background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 7px 9px; }
  .ams-slot.ams-warn { border-color: rgba(210,153,34,.5); }
  .ams-slot.ams-empty { opacity: .65; }
  .ams-hd { display: flex; align-items: center; gap: 8px; font-size: 11px; text-transform: uppercase;
            letter-spacing: .05em; color: #6e7681; }
  .ams-sw { width: 14px; height: 14px; border-radius: 4px; border: 1px solid #30363d; flex: 0 0 auto; }
  .ams-mat { font-size: 14px; font-weight: 700; color: #e6edf3; margin: 4px 0 6px; }
  .ams-meter { height: 6px; border-radius: 3px; background: #21262d; overflow: hidden; }
  .ams-meter span { display: block; height: 100%; background: #3fb950; border-radius: 3px; }
  .ams-meter.low span { background: #d29922; }
  .ams-meter.out span { background: #f85149; }
  .ams-figs { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; color: #8b949e;
              margin-top: 5px; font-variant-numeric: tabular-nums; }
  .ams-figs .warn { color: #d29922; font-weight: 600; }
  .ams-acts { display: flex; gap: 6px; margin-top: 8px; }
  .ams-btn { flex: 1 1 auto; border-radius: 8px; padding: 6px 8px; font-size: 12px; font-weight: 600;
             cursor: pointer; border: 1px solid #30363d; background: #21262d; color: #e6edf3; }
  .ams-btn:hover { border-color: #58a6ff; }
  .ams-btn:disabled { opacity: .45; cursor: not-allowed; }
  .ams-empty-note { color: #6e7681; font-size: 13px; border: 1px dashed #30363d; border-radius: 10px; padding: 16px; }
  /* IB-142: the slot a paused job needs material in, stated plainly. */
  .pd-ams-needed { color: #f85149; font-size: 13px; margin: 6px 0 10px; padding: 8px 10px; background: rgba(248,81,73,.08); border: 1px solid rgba(248,81,73,.25); border-radius: 8px; }
  /* IB-142: recent finished prints, with their plate pictures or an honest "no picture" note. */
  .pd-recent-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; }
  .pd-recent-item { background: #0d1117; border: 1px solid #21262d; border-radius: 10px; overflow: hidden; }
  .pd-recent-pic { aspect-ratio: 4 / 3; background: #0d1117; display: flex; align-items: center; justify-content: center; }
  .pd-recent-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .pd-recent-nopic { display: flex; flex-direction: column; align-items: center; gap: 6px; color: #6e7681; font-size: 12px; text-align: center; padding: 10px; }
  .pd-recent-nopic .glyph { font-size: 26px; }
  .pd-recent-meta { padding: 8px 10px; }
  .pd-recent-name { font-size: 13px; font-weight: 600; color: #e6edf3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .pd-recent-sub { font-size: 11px; color: #8b949e; margin-top: 2px; text-transform: capitalize; }
  .pd-recent-loading, .pd-recent-empty { color: #6e7681; font-size: 13px; padding: 10px 0; }
  /* IB-142: every interactive target in the detail is at least 44 CSS pixels, even on a 390pt phone. */
  .pd-btn, .ams-btn, .pd-media-tab, .pd-seg button, .pd-media-capture { min-height: 44px; min-width: 44px; }
  .printers-empty { text-align: center; color: #6e7681; padding: 60px 20px; font-size: 14px; max-width: 480px; margin: 0 auto; }
  /* ── WO-PRINTER-COST-MAINTENANCE (task 462): Cost & Maintenance card, appended below the printer
     grid in this same full-screen view. Fleet summary (teal/amber/green metric values, matching
     the approved mockup's palette for money) + one card per registry printer with its purchase
     info and maintenance log. Reuses .printers-grid's dark surface language (#161b22 panels,
     #30363d borders) so it reads as the same view, not a bolt-on. */
  .cm-section { max-width: 1100px; margin: 22px auto 0; }
  .cm-title { font-size: 15px; font-weight: 700; color: #e6edf3; margin-bottom: 10px; }
  .cm-title .cm-sub { font-weight: 600; color: #6e7681; font-size: 12px; }
  /* WO-PRINTER-CARD-DEDUPE req 1: the per-printer cost card, folded into that printer's own
     expanded detail so one machine = one block on the page. */
  .pd-cm { margin-top: 4px; }
  .pd-cm .cm-card { margin-bottom: 0; }
  .cm-summary { background: linear-gradient(135deg, #163330, #12181f); border: 1px solid #30363d;
                border-radius: 14px; padding: 14px; margin-bottom: 14px; }
  .cm-srow { display: flex; gap: 18px; flex-wrap: wrap; }
  .cm-metric { min-width: 110px; }
  .cm-metric .k { font-size: 11px; color: #8b949e; letter-spacing: .04em; text-transform: uppercase; }
  .cm-metric .v { font-size: 22px; font-weight: 800; margin-top: 2px; color: #e6edf3; font-variant-numeric: tabular-nums; }
  .cm-metric .v.teal { color: #2dd4bf; }
  .cm-metric .v.green { color: #3fb950; }
  .cm-metric .v.amber { color: #d29922; }
  .cm-breakeven { margin-top: 12px; }
  .cm-berow { display: flex; justify-content: space-between; font-size: 11px; color: #8b949e; margin-bottom: 4px; }
  .cm-bar { height: 8px; background: #21262d; border-radius: 8px; overflow: hidden; }
  .cm-bar > i { display: block; height: 100%; background: linear-gradient(90deg, #f85149, #d29922); }
  .cm-card { background: #161b22; border: 1px solid #30363d; border-radius: 14px; padding: 14px; margin-bottom: 12px; }
  .cm-chead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .cm-chead .name { font-size: 14px; font-weight: 700; color: #e6edf3; }
  .cm-chead .model { font-size: 10px; background: #21262d; border: 1px solid #30363d; border-radius: 20px; padding: 2px 8px; color: #8b949e; }
  .cm-chead .price { margin-left: auto; font-size: 18px; font-weight: 800; color: #2dd4bf; }
  .cm-meta { font-size: 12px; color: #8b949e; margin-top: 6px; display: flex; gap: 10px; flex-wrap: wrap; }
  .cm-meta span { background: #21262d; border: 1px solid #30363d; border-radius: 6px; padding: 1px 7px; color: #c9d1d9; }
  .cm-maint { margin-top: 12px; border-top: 1px solid #21262d; padding-top: 10px; }
  .cm-mtitle { font-size: 11px; color: #8b949e; letter-spacing: .04em; text-transform: uppercase;
               margin-bottom: 8px; display: flex; align-items: center; }
  .cm-addm { margin-left: auto; font-size: 11px; color: #2dd4bf; border: 1px solid #1f6f65; border-radius: 7px;
             padding: 3px 9px; cursor: pointer; }
  .cm-addm:hover { background: rgba(45,212,191,.1); }
  .cm-mrow { display: flex; align-items: center; gap: 10px; font-size: 12px; padding: 5px 0; color: #c9d1d9; }
  .cm-mrow .date { color: #8b949e; width: 64px; flex: none; }
  .cm-mrow .act { flex: 1 1 auto; }
  .cm-mrow .cost { font-weight: 700; flex: none; }
  .cm-mrow .cost.free { color: #3fb950; }
  .cm-empty { font-size: 12px; color: #6e7681; font-style: italic; padding: 4px 0; }
  .cm-form { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
  .cm-form input { background: #0d1117; border: 1px solid #30363d; border-radius: 8px; color: #e6edf3;
                    padding: 6px 8px; font-size: 12px; }
  .cm-form input[type="text"] { flex: 1 1 160px; }
  .cm-form input[type="number"] { width: 110px; }
  .cm-form button { background: #2dd4bf; color: #0d1117; border: none; border-radius: 8px; padding: 6px 14px;
                     font-size: 12px; font-weight: 700; cursor: pointer; }
  .cm-form button:hover { background: #4de1cd; }
  /* WO-PRINTER-COST-MAINTENANCE-FIX: expandable consumables breakdown */
  .cm-metric.consumables { cursor: pointer; }
  .cm-metric.consumables .v { display: flex; align-items: center; gap: 6px; }
  .cm-metric.consumables .v::after { content: '▾'; font-size: 11px; color: #8b949e; }
  .cm-metric.consumables.open .v::after { content: '▴'; }
  .cm-breakdown { display: none; width: 100%; margin-top: 10px; background: #0d1117; border: 1px solid #21262d;
                  border-radius: 10px; padding: 10px 12px; }
  .cm-breakdown.open { display: block; }
  .cm-bline { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; padding: 4px 0; color: #c9d1d9; }
  .cm-bline .src { color: #6e7681; font-size: 11px; margin-left: 6px; }
  .cm-bline .cost { font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
  .cm-btotal { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; font-weight: 700;
               border-top: 1px solid #30363d; margin-top: 6px; padding-top: 6px; color: #e6edf3; }
  .cm-bnote { font-size: 11px; color: #6e7681; margin-top: 8px; font-style: italic; }
  /* No standalone @media block here on purpose: tests like dashboard-chat-ui capture the FIRST
     `@media (max-width: 759px)` block in the file and assert chat rules live inside it, so adding
     one earlier in the <style> hijacks that capture. The grid above already collapses to one
     column on narrow screens via minmax(min(100%, 300px), 1fr) with no media query needed. */

  /* WO-BRAIN-SWITCH (task 269): header 🧠 bottom sheet — master row, usage bars, per-desk list. */
  .brain-master { display: flex; gap: 8px; margin: 14px 0; }
  .brain-master-btn { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px;
    padding: 12px 6px; border-radius: 10px; border: 1px solid #30363d; background: #0d1117;
    color: #adbac7; font-size: 13px; font-weight: 600; cursor: pointer; }
  .brain-master-btn:hover { border-color: #484f58; }
  .brain-master-btn:disabled { opacity: .6; cursor: not-allowed; }
  .brain-master-btn .bm-ic { font-size: 18px; }
  .brain-master-btn.on.claude { border-color: #ffa657; background: #3a2412; color: #ffa657; }
  .brain-master-btn.on.glm { border-color: #f87171; background: #3a0d0d; color: #f87171; }
  .brain-master-btn.on.kimi { border-color: #f87171; background: #3a0d0d; color: #f87171; }
  .brain-master-btn.on.auto { border-color: #3fb950; background: #0f2818; color: #3fb950; }
  .brain-providers { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
  .brain-prov-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px;
    background: #0d1117; border: 1px solid #21262d; font-size: 12px; }
  .brain-prov-lb { font-weight: 600; min-width: 46px; color: var(--bp-c, #c9d1d9); }
  .brain-prov-track { flex: 1; height: 6px; border-radius: 3px; background: #21262d; overflow: hidden; }
  .brain-prov-fill { display: block; height: 100%; border-radius: 3px; background: var(--bp-c, #58a6ff); }
  .brain-prov-pct { min-width: 32px; text-align: right; font-variant-numeric: tabular-nums; color: var(--bp-c, #c9d1d9); }
  .brain-prov-reset { color: #6e7681; font-size: 11px; white-space: nowrap; }
  .brain-region { font-size: 10px; padding: 1px 6px; border-radius: 20px; font-weight: 700; letter-spacing: .03em; }
  .brain-region-us { background: #133a1f; color: #3fb950; }
  .brain-region-cn { background: #3a1f1f; color: #f85149; }
  .brain-desks { display: flex; flex-direction: column; gap: 6px; }
  .brain-desk-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px;
    background: #0d1117; border: 1px solid #21262d; }
  .brain-desk-lb { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  .brain-desk-name { font-size: 13px; font-weight: 600; color: #e6edf3; }
  .brain-desk-eff { font-size: 11px; color: #8b949e; }
  .brain-desk-lock { font-size: 12px; flex: 0 0 auto; }
  .brain-desk-sel { flex: 0 0 auto; background: #161b22; color: #adbac7; border: 1px solid #30363d;
    border-radius: 6px; padding: 4px 6px; font-size: 12px; max-width: 40vw; cursor: pointer; }
  .brain-desk-sel:disabled { opacity: .5; cursor: not-allowed; }
  .brain-desk-dd { position: relative; flex: 0 0 auto; }
  .brain-desk-menu { position: absolute; right: 0; top: calc(100% + 4px); min-width: 150px; display: none;
    background: #161b22; border: 1px solid #30363d; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.4);
    z-index: var(--z-dropdown); overflow: hidden; }
  .brain-desk-dd.open .brain-desk-menu { display: block; }
  .brain-desk-opt { padding: 8px 10px; font-size: 12px; color: #adbac7; cursor: pointer; white-space: nowrap; }
  .brain-desk-opt:hover { background: #21262d; }
  .brain-desk-opt.disabled { opacity: .5; cursor: not-allowed; }
  .brain-desk-opt.selected { background: #21262d; color: #e6edf3; font-weight: 600; }
  .brain-toast { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
    background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 8px;
    padding: 10px 16px; font-size: 13px; z-index: var(--z-toast); box-shadow: 0 4px 16px rgba(0,0,0,.4);
    max-width: 90vw; text-align: center; opacity: 0; pointer-events: none; transition: opacity .2s; }
  .brain-toast.on { opacity: 1; }

  /* WO-CODER-SPINUP-UI (task 317): the Coders group header's "+" (provision a real coder desk)
     + its modal — same sheet/hint/empty primitives the brain-switch sheet already uses. */
  .grp-add { margin-left: auto; flex: 0 0 auto; width: 18px; height: 18px; border-radius: 5px;
    border: 1px solid #30363d; background: #161b22; color: #8b949e; font-size: 13px; line-height: 1;
    cursor: pointer; display: flex; align-items: center; justify-content: center; }
  .grp-add:hover { border-color: #58a6ff; color: #79c0ff; }
  .coder-form { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
  .coder-form input, .coder-form select { background: #161b22; color: #e6edf3; border: 1px solid #30363d;
    border-radius: 6px; padding: 8px 10px; font-size: 13px; }
  .coder-form button { align-self: flex-start; }
  .coder-desks { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
  .coder-desk-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px;
    background: #0d1117; border: 1px solid #21262d; }
  .coder-desk-lb { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  .coder-desk-name { font-size: 13px; font-weight: 600; color: #e6edf3; }
  .coder-desk-meta { font-size: 11px; color: #8b949e; }
  .coder-desk-del { flex: 0 0 auto; background: none; border: 1px solid #30363d; color: #ff7b72;
    border-radius: 6px; padding: 4px 8px; font-size: 12px; cursor: pointer; }
  .coder-desk-del:hover { border-color: #f85149; }

  /* WO-BLOCKED-TAP (task 257): the blocked-task/blocked-worker panel is a genuine bottom sheet —
     anchored to the bottom edge (flex align-items:flex-end) at every width rather than a
     min-width breakpoint, so it's mobile-first by construction (no new @media block needed —
     several existing tests grab the FIRST @media(max-width:759px)/(min-width:760px) block by a
     lazy string search; adding another one here would be a landmine for those). */
  .modal.blocked-modal { align-items: flex-end; justify-content: center; padding: 0; }
  .modal.blocked-modal.on { display: flex; }
  .blocked-sheet { width: 100%; max-width: 520px; max-height: 85vh; overflow: auto;
    border-radius: 16px 16px 0 0; margin: 0; padding-bottom: calc(20px + env(safe-area-inset-bottom)); }
  .bk-meta { margin: 10px 0 4px; }
  .bk-brow { font-size: 12px; color: #8b949e; padding: 3px 0; }
  .bk-brow b { color: #c9d1d9; font-weight: 600; }
  /* WO-DECISION-HOLD-GUARD (task 330): the hold badge must read as distinctly "hands off the
     robots" — same red family as the approval-takeover card so the two surfaces agree visually. */
  .bk-brow.bk-hold { color: #f85149; font-weight: 700; font-size: 13px; padding: 8px 0 4px; }
  .bk-reason { font-size: 14px; line-height: 1.5; color: #e6edf3; background: #0d1117;
    border: 1px solid #21262d; border-radius: 8px; padding: 10px 12px; margin: 8px 0; }
  .bk-details-toggle { background: none; border: none; color: #58a6ff; font-size: 12px;
    cursor: pointer; padding: 4px 0; }
  .bk-details { display: none; margin-top: 6px; max-height: 220px; overflow: auto;
    background: #0d1117; border: 1px solid #21262d; border-radius: 6px; padding: 8px 10px;
    font-size: 11px; color: #8b949e; white-space: pre-wrap; overflow-wrap: anywhere; }
  .bk-details.on { display: block; }
  .bk-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
  .bk-actions .gate-btn { min-height: 44px; flex: 1 1 44%; }
  .bk-note { font-size: 12px; color: #8b949e; font-style: italic; margin-top: 8px; }

  /* WO-LIVE-CODERS-PANEL (T64): full-width list of every coder slot in the Ops dashboard. */
  .live-coders-list { display: flex; flex-direction: column; gap: 6px; }
  .live-coder-row { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 8px;
    background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 8px 10px;
    cursor: pointer; transition: border-color .12s; }
  .live-coder-row:hover { border-color: #30363d; }
  .live-coder-row.lc-idle { opacity: .62; }
  .live-coder-row.lc-stuck { border-color: #f85149; }
  .lc-left { display: flex; align-items: center; gap: 8px; min-width: 0; }
  .lc-num { font-size: 13px; font-weight: 700; color: #c9d1d9; min-width: 54px; }
  .lc-pill { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
    padding: 2px 6px; border-radius: 10px; white-space: nowrap; }
  .lc-pill-claude { background: rgba(240,134,62,.15); color: #f0883e; }
  .lc-pill-kimi { background: rgba(248,113,113,.15); color: #f87171; }
  .lc-pill-glm { background: rgba(248,113,113,.15); color: #f87171; }
  .lc-pill-nemotron { background: rgba(88,166,255,.15); color: #58a6ff; }
  .lc-pill-generic { background: #21262d; color: #8b949e; }
  .lc-pill-stage { background: #21262d; color: #c9d1d9; }
  .lc-pill-stuck { background: rgba(248,81,73,.18); color: #f85149; }
  .lc-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .lc-task { font-size: 12px; color: #c9d1d9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .lc-task-idle { font-size: 12px; color: #6e7681; font-style: italic; }
  .lc-sub { font-size: 11px; color: #8b949e; }
  .lc-right { display: flex; align-items: center; gap: 8px; justify-content: flex-end; }
  .lc-elapsed { font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: #c9d1d9; min-width: 44px; text-align: right; }
  .lc-empty { font-size: 12px; color: #6e7681; padding: 6px 2px; }
  .lc-detail-sheet { width: 100%; max-width: 720px; max-height: 85vh; overflow: hidden;
    border-radius: 16px 16px 0 0; margin: 0; display: flex; flex-direction: column;
    padding-bottom: calc(20px + env(safe-area-inset-bottom)); }
  .lc-detail-meta { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 8px; }
  .lc-detail-summary { font-size: 14px; line-height: 1.5; color: #e6edf3; background: #0d1117;
    border: 1px solid #21262d; border-radius: 8px; padding: 10px 12px; margin: 0 0 10px; }
  .lc-detail-summary.lc-stuck { border-color: #f85149; color: #ff7b72; }
  .lc-log { flex: 1 1 auto; min-height: 160px; background: #0d1117; border: 1px solid #21262d;
    border-radius: 8px; padding: 10px 12px; overflow: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px; line-height: 1.45; color: #c9d1d9; }
  .lc-log-line { white-space: pre-wrap; overflow-wrap: anywhere; padding: 1px 0; }
  .lc-log-line.lc-err { color: #f85149; }
  .lc-log-empty { color: #6e7681; font-style: italic; }
  .lc-log-loading { color: #8b949e; }

  /* WO-SPLIT-SUBTASKS: split a blocked/too-large task into 2-4 smaller child tasks.
     Mobile-first bottom sheet (reuses the blocked-sheet shape), with thumb-sized inputs
     and a sticky action bar. Purely additive — no existing modal breakpoints touched. */
  .split-sheet .split-meta { font-size: 12px; color: #8b949e; margin: 6px 0 12px; }
  .split-sheet .split-meta b { color: #c9d1d9; font-weight: 600; }
  .split-list { display: flex; flex-direction: column; gap: 10px; }
  .split-item { background: #0d1117; border: 1px solid #21262d; border-radius: 10px; padding: 12px; }
  .split-item .si-h { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700;
                      color: #8b949e; text-transform: uppercase; letter-spacing: .03em; margin-bottom: 8px; }
  .split-item .si-num { width: 24px; height: 24px; display: inline-flex; align-items: center;
                        justify-content: center; border-radius: 50%; background: #21262d; color: #c9d1d9; }
  .split-item input, .split-item textarea { width: 100%; background: #161b22; color: #e6edf3;
    border: 1px solid #30363d; border-radius: 8px; padding: 12px; font: inherit; }
  .split-item input { min-height: 44px; margin-bottom: 8px; }
  .split-item textarea { min-height: 72px; resize: vertical; }
  .split-item input:focus, .split-item textarea:focus { outline: none; border-color: #58a6ff; }
  .split-count { display: flex; gap: 8px; margin: 12px 0; }
  .split-count button { flex: 1; min-height: 40px; background: #21262d; color: #8b949e;
    border: 1px solid #30363d; border-radius: 8px; font-size: 13px; cursor: pointer; }
  .split-count button.active { background: #1f6feb; color: #fff; border-color: #1f6feb; }
  .split-actions { display: flex; gap: 10px; margin-top: 16px; }
  .split-actions .gate-btn { flex: 1; min-height: 48px; }
  .split-err { color: #f85149; font-size: 12px; min-height: 18px; margin-top: 6px; }
  .parent-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600;
                 color: #79c0ff; background: #121d2f; border: 1px solid #1f6feb44; border-radius: 12px;
                 padding: 2px 8px; margin-left: 6px; white-space: nowrap; }

  /* ── WO-FOCUS-MODE (task 233): single-task focus mode ─────────────────────
     Mobile-first: a full-screen fixed overlay covering the viewport (notch/home-indicator
     respected via env(safe-area-inset-*)). Desktop (min-width:760px below) narrows it to a
     centered card instead of edge-to-edge. Purely additive. */
  .focus-overlay { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #0d1117;
                   flex-direction: column;
                   padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom);
                   padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
  .focus-overlay.on { display: flex; }
  .fm { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .fm-top { display: flex; align-items: center; gap: 10px; padding: 14px 16px; flex: 0 0 auto; }
  .fm-lane { display: inline-block; font-size: 11px; font-weight: 700; text-transform: uppercase;
             letter-spacing: .05em; background: #2a2140; color: #b392f0; border: 1px solid #4b3a7a;
             border-radius: 20px; padding: 4px 12px; white-space: nowrap; }
  .fm-session { color: #8b949e; font-size: 13px; margin-left: auto; }
  .fm-x { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 10px;
          width: 44px; height: 44px; font-size: 16px; cursor: pointer; flex: 0 0 auto; }
  .fm-x:active { background: #30363d; }
  .fm-body { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column;
             align-items: center; justify-content: center; text-align: center; padding: 12px 20px; gap: 14px; }
  .fm-onlabel { color: #8b949e; font-size: 14px; }
  .fm-title { font-size: 26px; font-weight: 700; line-height: 1.3; max-width: 100%; word-break: break-word; }
  .fm-next { display: inline-flex; align-items: center; gap: 8px; background: #133a1f; color: #56d364;
             border: 1px solid #1c5330; border-radius: 20px; padding: 8px 16px; font-size: 14px;
             max-width: 100%; }
  .fm-next b { font-weight: 600; }
  .fm-timer { font-size: 52px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 6px; }
  .fm-tlen { display: flex; align-items: center; gap: 10px; color: #8b949e; font-size: 13px; }
  .fm-tlen button { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 8px;
                     width: 32px; height: 32px; font-size: 16px; cursor: pointer; }
  .fm-start { background: #1f6feb; color: #fff; border: none; border-radius: 14px; padding: 16px 32px;
              font-size: 16px; font-weight: 600; cursor: pointer; min-height: 52px; min-width: 200px; }
  .fm-start:active { background: #1a5fc9; }
  .fm-start.running { background: #21262d; border: 1px solid #30363d; color: #e6edf3; }
  .fm-empty { color: #6e7681; padding: 10px 4px; }
  .fm-add { display: flex; flex-direction: column; gap: 10px; width: 100%; max-width: 360px; margin-top: 6px; }
  .fm-add input, .fm-add select { background: #161b22; color: #e6edf3; border: 1px solid #30363d;
          border-radius: 10px; padding: 12px 14px; font: inherit; min-height: 48px; }
  .fm-add button { background: #238636; color: #fff; border: 1px solid #2ea043; border-radius: 10px;
          padding: 12px; font-size: 15px; font-weight: 600; cursor: pointer; min-height: 48px; }

  .fm-acts { display: flex; gap: 10px; padding: 0 16px 12px; flex: 0 0 auto; }
  .fm-act { flex: 1 1 0; min-height: 56px; border: none; border-radius: 14px; color: #fff; font-size: 14px;
            font-weight: 700; cursor: pointer; display: flex; flex-direction: column; align-items: center;
            justify-content: center; gap: 2px; }
  .fm-act .fm-ic { font-size: 20px; }
  .fm-done { background: #238636; }
  .fm-done:active { background: #2ea043; }
  .fm-stuck { background: #9e6a03; }
  .fm-stuck:active { background: #bb8009; }
  .fm-park { background: #1f6feb; }
  .fm-park:active { background: #388bfd; }

  .fm-advisor { margin: 0 16px 12px; background: #241640; color: #c8a6ff; border: 1px solid #4b3a7a;
                border-radius: 12px; padding: 14px; font-size: 14px; font-weight: 600; cursor: pointer;
                min-height: 48px; flex: 0 0 auto; }
  .fm-advisor:active { background: #2d1a52; }

  .fm-dump-row { display: flex; gap: 8px; padding: 0 16px 6px; flex: 0 0 auto; }
  .fm-dump { flex: 1 1 auto; background: #161b22; color: #e6edf3; border: 1px solid #30363d;
             border-radius: 12px; padding: 12px 14px; font: inherit; font-size: 16px; min-height: 48px; }
  .fm-dump:focus { outline: none; border-color: #58a6ff; }
  .fm-dump-send { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 12px;
                  padding: 0 16px; font-size: 14px; cursor: pointer; min-width: 64px; }
  .fm-dump-note { color: #3fb950; font-size: 12px; padding: 0 16px; min-height: 16px; flex: 0 0 auto; }
  .fm-footer { text-align: center; color: #6e7681; font-size: 12px;
               padding: 4px 16px calc(14px + env(safe-area-inset-bottom)); flex: 0 0 auto; }

  @media (min-width: 760px) {
    .focus-overlay { background: rgba(0,0,0,.6); align-items: center; justify-content: center; padding: 24px; }
    .fm { flex: 0 0 auto; width: 100%; max-width: 480px; max-height: calc(100vh - 48px); background: #161b22;
          border: 1px solid #30363d; border-radius: 18px; overflow: auto; }
    .fm-body { padding: 24px; }
    .fm-footer { padding-bottom: 14px; }
  }

  /* ── WO-MORNING-GO (task 249): zero-decision morning screen ────────────────────────────────
     Mobile-first full-screen overlay, same trick as .focus-overlay above — but deliberately
     sparser: one path forward (GO), one small escape hatch (view day). No form controls. */
  .morning-overlay { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #0d1117;
                      flex-direction: column; align-items: center; justify-content: center;
                      padding: 24px; padding-top: calc(24px + env(safe-area-inset-top));
                      padding-bottom: calc(24px + env(safe-area-inset-bottom));
                      padding-left: calc(24px + env(safe-area-inset-left));
                      padding-right: calc(24px + env(safe-area-inset-right)); }
  .morning-overlay.on { display: flex; }
  .mo { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 20px;
        width: 100%; max-width: 420px; }
  .mo-greeting { font-size: 22px; font-weight: 700; color: #e6edf3; }
  .mo-goals { display: flex; flex-direction: column; gap: 8px; width: 100%; }
  .mo-goal { display: flex; align-items: center; gap: 10px; background: #161b22; border: 1px solid #30363d;
             border-radius: 12px; padding: 12px 16px; text-align: left; font-size: 15px; color: #e6edf3; }
  .mo-goal-n { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; background: #a371f7;
               color: #fff; font-weight: 700; font-size: 13px; display: flex; align-items: center; justify-content: center; }
  .mo-block { color: #8b949e; font-size: 14px; }
  .mo-block b { color: #e6edf3; }
  .mo-go { background: #1f6feb; color: #fff; border: none; border-radius: 20px; padding: 22px 48px;
           font-size: 22px; font-weight: 800; letter-spacing: .03em; cursor: pointer;
           min-height: 72px; width: 100%; max-width: 280px; }
  .mo-go:active { background: #1a5fc9; }
  .mo-viewday { background: none; border: none; color: #6e7681; font-size: 13px; text-decoration: underline;
                cursor: pointer; padding: 8px; min-height: 32px; }

  @media (min-width: 760px) {
    .morning-overlay { background: rgba(0,0,0,.6); }
  }

  /* ── WO-SHUTDOWN-SHELL-REDO (task 252): evening "Shutdown" mode shell + Card 1 ──────────────
     Mobile-first full-screen overlay, same trick as .focus-overlay/.morning-overlay above. Deep
     indigo/evening palette (deliberately distinct from the daytime blue of #238's planner) —
     req 4: no guilt language, no red anywhere in here. Top pane = compact read-only TOMORROW
     timeline; bottom pane = one card at a time + 6 progress dots. */
  .shutdown-overlay { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: linear-gradient(180deg,#0a0e1a,#0d1129 60%,#141a33);
                       flex-direction: column;
                       padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom);
                       padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
  .shutdown-overlay.on { display: flex; }
  .sd { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .sd-top { display: flex; align-items: center; gap: 10px; padding: 14px 16px 8px; flex: 0 0 auto; }
  .sd-title { font-size: 15px; font-weight: 700; color: #c9d1ff; display: flex; align-items: center; gap: 8px; }
  .sd-x { margin-left: auto; background: #161b2e; color: #c9d1ff; border: 1px solid #2a3158; border-radius: 10px;
          width: 44px; height: 44px; font-size: 16px; cursor: pointer; }
  .sd-x:active { background: #202750; }

  .sd-tomorrow { flex: 0 0 38vh; min-height: 180px; max-height: 320px; overflow-y: auto; margin: 0 16px;
                 background: #0f1430; border: 1px solid #262d55; border-radius: 14px; position: relative; }
  .sd-tomorrow-head { position: sticky; top: 0; z-index: var(--z-raised); background: #0f1430; padding: 8px 12px;
                       font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
                       color: #7d87c9; border-bottom: 1px solid #202750; }
  .sd-tl-wrap { position: relative; padding: 6px 0 6px 54px; }
  .sd-tl-hourlb { position: absolute; left: 0; width: 46px; text-align: right; padding-right: 8px;
                  font-size: 11px; color: #5a63a0; transform: translateY(-6px); }
  .sd-tl-hourline { position: absolute; left: 50px; right: 8px; border-top: 1px solid #1b2148; }
  .sd-empty { color: #5a63a0; text-align: center; padding: 24px 12px; font-size: 13px; }
  .sd-candidates { display: flex; flex-wrap: wrap; gap: 6px; padding: 8px 12px 10px 54px; }
  .sd-chip { background: #1b2455; border: 1px solid #333f83; color: #c9d1ff; border-radius: 20px;
             padding: 6px 12px; font-size: 12px; animation: sd-chip-in .28s ease-out; }
  @keyframes sd-chip-in { from { opacity: 0; transform: translateY(6px) scale(.9); } to { opacity: 1; transform: none; } }

  .sd-dots { display: flex; justify-content: center; gap: 8px; padding: 10px 0 4px; flex: 0 0 auto; }
  .sd-dot { width: 8px; height: 8px; border-radius: 50%; background: #2a3158; }
  .sd-dot.on { background: #7c86ff; }
  .sd-dot.done { background: #3fb950; }

  .sd-card { flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column;
             padding: 8px 16px calc(16px + env(safe-area-inset-bottom)); gap: 14px; }
  .sd-card-title { font-size: 20px; font-weight: 700; color: #e6edf3; text-align: center; margin-top: 6px; }
  .sd-wins { display: flex; flex-direction: column; gap: 8px; }
  .sd-win { display: flex; align-items: center; gap: 10px; background: #122616; border: 1px solid #1f4a28;
            color: #7ee2a8; border-radius: 12px; padding: 10px 14px; font-size: 14px; }
  .sd-items { display: flex; flex-direction: column; gap: 8px; }
  .sd-item { display: flex; align-items: center; gap: 10px; background: #161b22; border: 1px solid #30363d;
             border-radius: 12px; padding: 10px 12px; }
  .sd-item.decided { opacity: .55; }
  .sd-item-title { flex: 1 1 auto; font-size: 14px; color: #e6edf3; word-break: break-word; }
  .sd-item-kind { flex: 0 0 auto; font-size: 10px; color: #6e7681; text-transform: uppercase; letter-spacing: .05em; }
  .sd-item-acts { display: flex; gap: 6px; flex: 0 0 auto; }
  .sd-item-btn { border: none; border-radius: 10px; padding: 8px 10px; font-size: 12px; font-weight: 600;
                 cursor: pointer; min-height: 36px; color: #fff; }
  .sd-roll { background: #3852c9; }
  .sd-roll:active { background: #4863e0; }
  .sd-park { background: #454b57; }
  .sd-park:active { background: #565d6b; }
  .sd-item-decision { flex: 0 0 auto; font-size: 11px; color: #8b949e; min-width: 64px; text-align: right; }

  .sd-empty-triage { color: #6e7681; text-align: center; padding: 14px; font-size: 13px; }
  .sd-continue { background: #3852c9; color: #fff; border: none; border-radius: 14px; padding: 15px;
                 font-size: 16px; font-weight: 700; cursor: pointer; min-height: 52px; margin-top: auto; }
  .sd-continue:disabled { background: #21262d; color: #6e7681; cursor: not-allowed; }
  .sd-placeholder { color: #8b949e; text-align: center; padding: 30px 16px; }

  /* WO-SHUTDOWN-TOP3-REDO (task 283): Card 2 "Top 3 for tomorrow" — pick exactly 3 chips, hard
     capped with a friendly toast instead of an error state (req: zero guilt, never red). */
  .sd-sub { color: #7d87c9; text-align: center; font-size: 12px; margin-top: -6px; }
  .sd-top3-chips { display: flex; flex-wrap: wrap; gap: 8px; }
  .sd-top3-chip { display: inline-flex; align-items: center; gap: 8px; background: #161b22; border: 1px solid #30363d;
                  color: #e6edf3; border-radius: 20px; padding: 9px 14px; font-size: 13px; cursor: pointer;
                  min-height: 40px; }
  .sd-top3-chip:active { background: #1c2333; }
  .sd-top3-chip.picked { background: #22285a; border-color: #7c86ff; color: #dfe3ff; }
  .sd-top3-badge { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;
                    border-radius: 50%; background: #7c86ff; color: #0d1129; font-size: 11px; font-weight: 800; }
  .sd-top3-add-row { display: flex; gap: 8px; }
  .sd-top3-add-input { flex: 1 1 auto; min-width: 0; background: #0f1430; border: 1px solid #262d55; color: #e6edf3;
                        border-radius: 10px; padding: 10px 12px; font-size: 14px; }
  .sd-top3-add-btn { flex: 0 0 auto; background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 10px;
                      padding: 0 16px; font-size: 13px; font-weight: 600; cursor: pointer; min-height: 40px; }
  .sd-top3-add-btn:active { background: #30363d; }
  .sd-toast { min-height: 0; text-align: center; font-size: 12px; color: #c9d1ff; background: #22285a;
              border: 1px solid #333f83; border-radius: 10px; padding: 0; opacity: 0; max-height: 0; overflow: hidden;
              transition: opacity .2s ease, padding .2s ease, max-height .2s ease; }
  .sd-toast.on { opacity: 1; padding: 9px 12px; max-height: 60px; }

  /* WO-SHUTDOWN-DAYBUILD-REDO (task 284): Card 3 "Fixed for tomorrow" + Card 4 "Block the day" —
     the sd-tomorrow pane above switches from the chip tray to a real positioned timeline; fixed
     (calendar) blocks are gray + immovable, draft blocks (focus/email/lunch/wrap) are tappable. */
  .sd-sched-block { position: absolute; left: 50px; right: 8px; box-sizing: border-box; border-left: 3px solid #8b949e;
                     background: #161b22; border-radius: 8px; padding: 3px 10px; overflow: hidden; cursor: pointer;
                     display: flex; flex-direction: column; justify-content: center; }
  .sd-sched-fixed { opacity: .82; cursor: default; }
  .sd-sched-time { font-size: 10px; color: #8b949e; line-height: 1.3; }
  .sd-sched-label { font-size: 12px; color: #e6edf3; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .sd-sched-selected { outline: 2px solid #7c86ff; outline-offset: 1px; }
  .sd-sched-block.sd-sched-in { animation: sd-sched-in .32s ease-out both; }
  @keyframes sd-sched-in { from { opacity: 0; transform: translateX(8px) scale(.95); } to { opacity: 1; transform: none; } }
  .sd-nudge-row { display: flex; gap: 8px; }
  .sd-nudge-row .sd-item-btn { flex: 1 1 0; text-align: center; }

  /* WO-SHUTDOWN-ALARM-LOCK (task 248): Card 5 "Alarm level" toggles + the timeline's bell/red
     tell, and Card 6 "Locked in"'s final review + confirmation state. */
  .sd-alarm-toggle { flex: 0 0 auto; border: 1px solid #30363d; background: #21262d; color: #8b949e;
                      border-radius: 20px; padding: 7px 12px; font-size: 11px; font-weight: 700;
                      letter-spacing: .02em; }
  .sd-alarm-toggle.on { background: #3a1420; border-color: #f85149; color: #ffb4ab; }
  .sd-alarm-toggle:active { opacity: .8; }
  .sd-sched-alarm { border-left-color: #f85149 !important; }
  .sd-top3-final { display: flex; flex-direction: column; gap: 8px; }
  .sd-top3-final-row { display: flex; align-items: center; gap: 10px; background: #161b22;
                        border: 1px solid #30363d; border-radius: 10px; padding: 10px 12px; font-size: 14px; color: #e6edf3; }
  .sd-locked-confirm { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 18px 12px; }
  .sd-locked-check { width: 52px; height: 52px; border-radius: 50%; background: #122616; border: 1px solid #1f4a28;
                      color: #3fb950; font-size: 26px; display: flex; align-items: center; justify-content: center; }
  .sd-locked-msg { color: #c9d1ff; font-size: 15px; text-align: center; }

  @media (min-width: 760px) {
    .shutdown-overlay { background: rgba(6,8,20,.72); align-items: center; justify-content: center; padding: 24px; }
    .sd { width: 100%; max-width: 520px; max-height: calc(100vh - 48px); background: #0d1129;
          border: 1px solid #262d55; border-radius: 18px; overflow: hidden; }
  }

  /* WO-MORNING-GO (task 249): the lock control lives inside the existing #238 planner sheet —
     locking is what makes the Morning screen fire on the next app-open. Purely additive. */
  .pd-lock-row { display: flex; align-items: center; justify-content: space-between; gap: 10px;
                 background: #161b22; border: 1px solid #30363d; border-radius: 10px;
                 padding: 10px 14px; margin: 8px 0 4px; }
  .pd-lock-status { font-size: 12px; color: #8b949e; }
  .pd-lock-status.on { color: #3fb950; font-weight: 600; }
  .pd-lock-btn { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 8px;
                 padding: 8px 14px; font-size: 12px; font-weight: 600; cursor: pointer; min-height: 36px; }
  .pd-lock-btn.on { background: #1c5330; border-color: #2ea043; color: #56d364; }

  /* ── WO-28 · Live usage meter ─────────────────────────────────────────────
     Always-visible top-bar mini-meter (weekly Claude-Max % + live tokens/min)
     + the full usage panel. Mobile-first; all numbers are REAL ledger reads. */
  .usage-mini { display: inline-flex; align-items: center; gap: 7px; padding: 3px 10px 3px 6px;
                border-radius: 20px; border: 1px solid #30363d; background: #161b22; cursor: pointer;
                font-variant-numeric: tabular-nums; line-height: 1; }
  .usage-mini:hover { border-color: #484f58; }
  .usage-mini .um-ring { width: 18px; height: 18px; border-radius: 50%; flex: 0 0 auto; position: relative;
                         background: conic-gradient(var(--um-c, #3fb950) calc(var(--um-pct, 0) * 1%), #30363d 0); }
  .usage-mini .um-ring::after { content: ''; position: absolute; inset: 3px; border-radius: 50%; background: #161b22; }
  .usage-mini .um-pct { font-size: 12px; font-weight: 700; color: #adbac7; }
  .usage-mini .um-rate { font-size: 10px; color: #8b949e; }
  .usage-mini.state-green  { --um-c: #3fb950; } .usage-mini.state-green  .um-pct { color: #3fb950; }
  .usage-mini.state-amber  { --um-c: #d29922; } .usage-mini.state-amber  .um-pct { color: #d29922; }
  .usage-mini.state-red    { --um-c: #f85149; border-color: #f85149; } .usage-mini.state-red .um-pct { color: #f85149; }
  .usage-mini.state-red .um-ring { animation: um-pulse 1.4s ease-in-out infinite; }
  @keyframes um-pulse { 0%,100% { opacity: 1; } 50% { opacity: .5; } }

  .usage-sheet { max-width: 520px; }
  .um-throttle { display: none; margin: 8px 0 2px; padding: 9px 12px; border-radius: 8px;
                 background: #3a1418; border: 1px solid #f85149; color: #ff9d96; font-size: 12px; font-weight: 600; }
  .um-throttle.on { display: block; }

  .um-gauge-wrap { display: flex; flex-direction: column; align-items: center; margin: 10px 0 2px; }
  .um-gauge { position: relative; width: 200px; height: 200px; }
  .um-gauge svg { transform: rotate(-90deg); display: block; }
  .um-gauge .um-track { stroke: #21262d; }
  .um-gauge .um-arc { transition: stroke-dasharray .5s ease, stroke .4s ease; stroke-linecap: round; }
  .um-cap-marker { position: absolute; inset: 0; pointer-events: none; }
  .um-cap-marker i { position: absolute; top: 5px; left: 50%; width: 2px; height: 24px; margin-left: -1px;
                     background: #e6edf3; border-radius: 1px; box-shadow: 0 0 3px rgba(0,0,0,.6); }
  .um-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
  .um-big { font-size: 40px; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; color: #e6edf3; }
  .um-gauge.state-green .um-big { color: #3fb950; } .um-gauge.state-green .um-arc { stroke: #3fb950; }
  .um-gauge.state-amber .um-big { color: #d29922; } .um-gauge.state-amber .um-arc { stroke: #d29922; }
  .um-gauge.state-red   .um-big { color: #f85149; } .um-gauge.state-red   .um-arc { stroke: #f85149; }
  .um-sub { font-size: 11px; color: #8b949e; margin-top: 5px; text-align: center; }
  .um-reset { font-size: 11px; color: #6e7681; margin-top: 3px; }
  /* WO-31: reset-counters control at the foot of the usage panel. */
  .um-resetctl { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-top: 12px;
                 padding-top: 10px; border-top: 1px solid #21262d; }
  .um-reset-btn { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; border-radius: 6px;
                  padding: 5px 12px; font-size: 12px; cursor: pointer; }
  .um-reset-btn:hover { background: #30363d; border-color: #6e7681; }
  .um-reset-btn:disabled { opacity: .55; cursor: default; }
  .um-reset-note { font-size: 11px; color: #6e7681; }
  .um-caplegend { font-size: 11px; color: #8b949e; margin: 6px 0 2px; text-align: center; }
  .um-caplegend b { color: #adbac7; }
  /* live cap slider (Brandon #6709): drag → the gauge cap line moves + POSTs fmops_claude_cap_pct */
  .um-capctl { display: flex; align-items: center; gap: 10px; width: 100%; max-width: 320px; margin: 8px 0 2px; }
  .um-capctl-lab { font-size: 12px; color: #8b949e; white-space: nowrap; }
  .um-capctl-lab b { color: #e6edf3; font-variant-numeric: tabular-nums; }
  .um-cap-slider { flex: 1; accent-color: #d29922; cursor: pointer; }
  .um-budgetctl { display: flex; align-items: center; gap: 6px; font-size: 11px; color: #8b949e; margin: 2px 0; flex-wrap: wrap; justify-content: center; }
  .um-budget-input { width: 84px; background: #0d1117; border: 1px solid #30363d; color: #e6edf3;
                     border-radius: 6px; padding: 3px 6px; font-size: 11px; font-variant-numeric: tabular-nums; }
  .um-budget-note { color: #6e7681; font-style: italic; }

  .um-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
  .um-rate-big { font-size: 28px; font-weight: 800; font-variant-numeric: tabular-nums; }
  .um-rate-unit { font-size: 12px; color: #8b949e; font-weight: 500; }
  .um-spark { width: 130px; height: 30px; }
  .um-spark polyline { fill: none; stroke: #58a6ff; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
  .um-spark .um-spark-area { fill: rgba(88,166,255,.12); stroke: none; }
  .um-active { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
  .um-chip { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; border-radius: 14px;
             background: #161b22; border: 1px solid #30363d; font-size: 11px; }
  .um-chip .um-dot { width: 6px; height: 6px; border-radius: 50%; background: #3fb950; box-shadow: 0 0 6px #3fb950; }
  .um-chip b { color: #adbac7; font-variant-numeric: tabular-nums; }
  .um-idle { color: #6e7681; font-size: 12px; font-style: italic; margin-top: 8px; }

  .um-tier { display: grid; grid-template-columns: 96px 1fr auto; gap: 9px; align-items: center; margin: 7px 0; font-size: 12px; }
  .um-tier-name { color: #adbac7; }
  .um-tier-bar { height: 8px; border-radius: 4px; background: #21262d; overflow: hidden; }
  .um-tier-fill { height: 100%; border-radius: 4px; transition: width .5s ease; }
  .um-tier-val { color: #8b949e; font-variant-numeric: tabular-nums; white-space: nowrap; }
  .um-tier-val b { color: #adbac7; }
  .um-tier.tier-claudeMax .um-tier-fill { background: #a371f7; }
  .um-tier.tier-cheap     .um-tier-fill { background: #f0883e; }
  .um-tier.tier-local     .um-tier-fill { background: #3fb950; }
  .um-tier.tier-other     .um-tier-fill { background: #6e7681; }

  .um-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 6px; }
  .um-stat { background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 10px 12px; }
  .um-stat-k { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: .04em; }
  .um-stat-v { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; margin-top: 2px; }
  .um-stat-sub { font-size: 11px; color: #6e7681; font-variant-numeric: tabular-nums; }

  /* WO-PROVIDER-USAGE-PANEL (task 182): one row per provider account — Claude Max, GLM/Z.ai,
     hosted-cheap, and any future provider the ledger has seen. */
  .pu-list { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
  .pu-row { background: #0d1117; border: 1px solid #21262d; border-radius: 9px; padding: 10px 12px; }
  .pu-row.pu-exhausted { border-color: #f85149; }
  .pu-row.pu-near-cap { border-color: #d29922; }
  .pu-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
  .pu-name { font-weight: 600; font-size: 13px; color: #e6edf3; }
  .pu-plan { color: #6e7681; font-size: 11px; font-weight: 400; margin-left: 6px; }
  .pu-badge { font-size: 10px; padding: 2px 8px; border-radius: 999px; text-transform: uppercase;
              letter-spacing: .04em; font-weight: 700; white-space: nowrap; }
  .pu-badge.pu-healthy { background: #16341f; color: #4ac26b; }
  .pu-badge.pu-near-cap { background: #3a2a12; color: #d29922; }
  .pu-badge.pu-exhausted { background: #3a1418; color: #f85149; }
  .pu-meters { display: flex; flex-direction: column; gap: 5px; margin-top: 8px; }
  .pu-meter-row { display: grid; grid-template-columns: 52px 1fr auto; gap: 8px; align-items: center; font-size: 11px; }
  .pu-meter-lab { color: #8b949e; }
  .pu-meter-bar { height: 7px; border-radius: 4px; background: #21262d; overflow: hidden; }
  .pu-meter-fill { height: 100%; border-radius: 4px; background: #3fb950; transition: width .5s ease, background .3s ease; }
  .pu-meter-fill.pu-near-cap { background: #d29922; }
  .pu-meter-fill.pu-exhausted { background: #f85149; }
  .pu-meter-val { color: #adbac7; font-variant-numeric: tabular-nums; white-space: nowrap; }
  .pu-note { font-size: 11px; color: #6e7681; margin-top: 6px; font-style: italic; }
  .pu-exhausted-msg { font-size: 11px; color: #ff9d96; margin-top: 6px; }
  .pu-exhausted-msg b { color: #ffb4ad; }
  .pu-volume { font-size: 11px; color: #8b949e; margin-top: 6px; }

  /* ── WO-USAGE-DRAIN-PANEL: live Usage panel inside the Ops Dashboard ────────────────────────────
     Per-provider token rows (with quota % + status), per-role breakdown (Manager pinned),
     live burn rate, and a no-idle-drain indicator. Mobile-first, reuses .panel/.subsec/.subhd. */
  .up-row { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center;
            padding: 7px 0; border-bottom: 1px solid #1b2129; font-size: 12px; }
  .up-row:last-child { border-bottom: none; }
  .up-row.manager { background: rgba(240, 136, 62, .08); margin: 0 -8px; padding: 7px 8px; border-radius: 6px; border-bottom: none; }
  .up-lab { display: flex; align-items: center; gap: 6px; color: #e6edf3; font-weight: 600; }
  .up-lab .cn { font-size: 9px; padding: 1px 5px; border-radius: 4px; background: #3a1418; color: #ff9d96;
                text-transform: uppercase; letter-spacing: .03em; }
  .up-sub { color: #8b949e; font-size: 11px; font-weight: 400; margin-top: 1px; }
  .up-val { text-align: right; color: #adbac7; font-variant-numeric: tabular-nums; white-space: nowrap; }
  .up-val b { color: #e6edf3; }
  .up-bar { height: 6px; border-radius: 3px; background: #21262d; overflow: hidden; margin-top: 4px; }
  .up-bar > span { display: block; height: 100%; border-radius: 3px; background: #3fb950; transition: width .4s ease; }
  .up-bar > span.near { background: #d29922; }
  .up-bar > span.exhausted { background: #f85149; }
  .up-status { font-size: 10px; padding: 2px 7px; border-radius: 999px; text-transform: uppercase;
               letter-spacing: .04em; font-weight: 700; }
  .up-status.healthy { background: #16341f; color: #4ac26b; }
  .up-status.near-cap { background: #3a2a12; color: #d29922; }
  .up-status.exhausted { background: #3a1418; color: #f85149; }
  .up-drain { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px;
              font-size: 11px; font-weight: 700; border: 1px solid; }
  .up-drain.green { background: #16341f; color: #4ac26b; border-color: #2ea043; }
  .up-drain.amber { background: #3a2a12; color: #d29922; border-color: #d29922; }
  .up-drain.red { background: #3a1418; color: #f85149; border-color: #f85149; }
  .up-burn { display: flex; align-items: baseline; gap: 10px; font-size: 12px; color: #8b949e; }
  .up-burn b { font-size: 18px; color: #e6edf3; font-variant-numeric: tabular-nums; }
  .up-note { font-size: 11px; color: #6e7681; margin-top: 4px; font-style: italic; }
  /* WO-USAGE-LIVE (task 494): auto-refresh controls + burn-since-open delta. */
  .up-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .up-head button { background: #21262d; border: 1px solid #30363d; color: #8b949e;
                    border-radius: 6px; padding: 3px 8px; font-size: 12px; cursor: pointer;
                    display: inline-flex; align-items: center; gap: 4px; }
  .up-head button:hover { background: #30363d; color: #e6edf3; }
  .up-head button:disabled { opacity: .55; cursor: not-allowed; }
  .up-head .ago { font-size: 11px; color: #6e7681; font-style: italic; white-space: nowrap; }
  .up-head .ago.polling { color: #58a6ff; }
  .up-head .ago.paused { color: #8b949e; }
  .up-delta { font-size: 10px; font-weight: 700; padding: 1px 5px; border-radius: 4px;
              background: #16341f; color: #4ac26b; margin-left: 4px; }
  .up-delta.negative { background: #3a1418; color: #f85149; }

  .sheet .err { color: #f85149; font-size: 12px; min-height: 14px; }
  .sheet code { background: #21262d; padding: 1px 4px; border-radius: 4px; font-size: 11px; }
  .acct-add, .assign-add { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
  .acct-add select, .acct-add input, .assign-add select, .assign-add input {
    background: #0d1117; border: 1px solid #30363d; color: #e6edf3; border-radius: 7px; padding: 7px 9px; font-size: 13px; }
  .acct-add input, .assign-add input { flex: 1; min-width: 120px; }
  .sheet button:not(.x) { background: #21262d; border: 1px solid #30363d; color: #e6edf3;
    border-radius: 7px; padding: 7px 12px; font-size: 13px; cursor: pointer; }
  .acct-list { display: flex; flex-direction: column; gap: 8px; }
  /* WO-SETTINGS-ROUTING (chunk 3 of 5): routing rows with colored brain badges */
  .routing-row { display: flex; align-items: center; gap: 8px; background: #0d1117; border: 1px solid #21262d;
                border-radius: 9px; padding: 9px 11px; flex-wrap: wrap; }
  .routing-target { font-weight: 600; font-size: 13px; color: #e6edf3; min-width: 80px; }
  .routing-arrow { color: #8b949e; font-size: 13px; margin: 0 4px; }
  .routing-brain { display: inline-flex; align-items: center; padding: 3px 8px; border-radius: 999px; font-size: 11px;
                   font-weight: 700; text-transform: uppercase; letter-spacing: .04em; background: rgba(255,255,255,.08);
                   color: var(--routing-c, #8b949e); border: 1px solid var(--routing-c, #8b949e); }
  .routing-detail { color: #8b949e; font-size: 12px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .routing-remove { padding: 4px 10px; font-size: 12px; background: #21262d; border: 1px solid #30363d; color: #adbac7;
                    border-radius: 6px; cursor: pointer; transition: all .15s; }
  .routing-remove:hover { background: #30363d; border-color: #8b949e; color: #e6edf3; }
  .acct { display: flex; align-items: center; gap: 10px; background: #0d1117; border: 1px solid #21262d;
          border-radius: 9px; padding: 9px 11px; }
  .acct .who { flex: 1; min-width: 0; }
  .acct .nm { font-weight: 600; font-size: 13px; }
  .acct .sub { color: #8b949e; font-size: 11px; display: block; margin-top: 2px; }
  .acct .acts { display: flex; gap: 6px; align-items: center; }
  .acct .acts button { padding: 4px 8px; font-size: 12px; }
  .cc { font-size: 10px; padding: 2px 7px; border-radius: 999px; text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
  .cc-subscription { background: #16341f; color: #4ac26b; }
  .cc-metered { background: #3a2a12; color: #f0883e; }
  .cc-free { background: #1c2b40; color: #58a6ff; }
  .acct.active { border-color: #2ea043; }
  .acct.off { opacity: .5; }
  .pill-active { font-size: 10px; color: #4ac26b; font-weight: 600; }
  /* Obsidian vault status (WO-11) */
  .vault-status { background: #0d1117; border: 1px solid #21262d; border-radius: 9px; padding: 10px 12px; margin-top: 8px; }
  .vs-head { display: flex; align-items: center; gap: 8px; }
  .vs-head b { font-size: 13px; }
  .vs-state { color: #8b949e; font-size: 11px; }
  .vs-reindex { margin-left: auto; padding: 4px 8px !important; font-size: 12px; }
  .vs-dot { width: 8px; height: 8px; border-radius: 999px; background: #6e7681; display: inline-block; }
  .vs-ok { background: #4ac26b; } .vs-warn { background: #f0883e; } .vs-off { background: #6e7681; }
  .vs-body { color: #8b949e; font-size: 11px; margin-top: 4px; }
  .vs-path { color: #6e7681; font-size: 11px; margin-top: 3px; font-family: ui-monospace, monospace; word-break: break-all; }
  .vs-reindex:disabled { opacity: 0.6; cursor: default; }

  /* CAP-UI (capabilities-screen-redesign) · ONE desk's capabilities, grouped into labeled sections.
     Mobile-first: full-width rows, big touch targets, NO horizontal scroll. The wide desks×caps
     matrix — and its overflow-x scroller, sticky desk column and min-width table — is gone. */
  .cap-wrap { margin-top: 10px; }
  /* The shared toggle + status-dot (the one switch idiom); reused on every capability row. */
  .cap-toggle { position: relative; width: 42px; height: 24px; border-radius: 999px; background: #30363d;
                border: 1px solid #30363d; cursor: pointer; padding: 0; flex: 0 0 auto; }
  .cap-toggle::after { content: ''; position: absolute; top: 1px; left: 1px; width: 20px; height: 20px;
                        border-radius: 50%; background: #8b949e; transition: transform .15s ease, background .15s ease; }
  .cap-toggle.on { background: #16341f; border-color: #2ea043; }
  .cap-toggle.on::after { transform: translateX(18px); background: #4ac26b; }
  .cap-toggle.mixed { background: #332707; border-color: #d4a72c; }
  .cap-toggle.mixed::after { transform: translateX(9px); background: #f2cc60; }
  .cap-toggle:disabled { opacity: .45; cursor: default; }
  .cap-toggle.busy { opacity: .6; cursor: wait; }
  .cap-dot { width: 7px; height: 7px; border-radius: 999px; background: #6e7681; display: inline-block; flex: 0 0 auto; }
  .cap-dot.cap-on { background: #4ac26b; box-shadow: 0 0 5px #4ac26b; }
  .cap-dot.cap-off { background: #6e7681; }
  .cap-dot.cap-dim { background: #3a4048; opacity: .55; }
  .cap-dot.cap-locked { background: #6e7681; opacity: .7; }

  /* WO-DESK-CAPABILITY-TOGGLES (task T25) + CAP-UI · ONE desk's capabilities as readable switches,
     now the whole tab (no separate modal). The desk header carries the name + lane + enabled count
     and the Switch-desk picker; the lane banner and Claude-model picker sit under it; then the
     capabilities, grouped into labeled sections. */
  .deskcap-head { margin-bottom: 12px; }
  .deskcap-headrow { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
  .deskcap-namewrap { min-width: 0; flex: 1 1 60%; }
  .deskcap-name { font-size: 17px; font-weight: 700; color: #e6edf3; line-height: 1.2; overflow-wrap: anywhere; }
  .deskcap-count { font-size: 12px; color: #8b949e; margin-top: 3px; }
  .deskcap-pickwrap { display: flex; flex-direction: column; gap: 3px; flex: 0 0 auto; max-width: 100%; }
  .deskcap-picklbl { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: #8b949e; }
  .deskcap-pick { background: #010409; color: #e6edf3; border: 1px solid #30363d; border-radius: 8px;
                  padding: 8px 10px; font-size: 14px; min-width: 0; max-width: 100%; }
  /* Lane banner — which lane this desk runs on right now. */
  .deskcap-lane { display: flex; align-items: flex-start; gap: 8px; font-size: 12px; margin: 0 0 12px;
                  padding: 9px 11px; border-radius: 8px; border: 1px solid #21262d; background: #0d1117; color: #8b949e;
                  line-height: 1.4; }
  .deskcap-lane b { color: #e6edf3; }
  .deskcap-lane.on-api { border-color: #9e6a03; background: #1c1608; }
  /* TIB-042: the banner now stacks a prediction and an observation, so it is a column, not a row. */
  .deskcap-lane { flex-direction: column; }
  .deskcap-lane-ran { margin-top: 6px; padding-top: 6px; border-top: 1px solid #21262d; }
  .deskcap-lane-ran-differs { color: #f0883e; }
  /* WO-DESK-MODEL-TIER (task T37) · the per-desk Claude picker, directly under the lane banner —
     the two answer the same question in sequence: which lane this desk runs on, then which Claude
     answers on it. Same card idiom as the capability rows below so it reads as part of the sheet. */
  .deskcap-model { margin: 0 0 14px; padding: 9px 11px; border-radius: 8px;
                   border: 1px solid #21262d; background: #0d1117; }
  .deskcap-model-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
  .deskcap-model-lbl { font-size: 12px; color: #e6edf3; font-weight: 600; }
  .deskcap-model-sel { flex: 1 1 auto; min-width: 120px; background: #010409; color: #e6edf3;
                       border: 1px solid #30363d; border-radius: 7px; padding: 6px 8px; font-size: 12px; }
  .deskcap-model-sel:disabled { opacity: .5; }
  .deskcap-model-note { margin-top: 6px; font-size: 11px; color: #8b949e; }
  .deskcap-model-note b { color: #e6edf3; }
  .deskcap-preset { margin: 0 0 14px; padding: 9px 11px; border-radius: 8px;
                    border: 1px solid #21262d; background: #0d1117; }
  .deskcap-preset-wrap { display: flex; flex-direction: column; gap: 10px; }
  .deskcap-preset-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
  .deskcap-preset-title { font-size: 12px; color: #e6edf3; font-weight: 600; }
  .deskcap-preset-active { font-size: 11px; color: #8b949e; }
  .deskcap-preset-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; }
  .deskcap-preset-card { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; text-align: left;
                         padding: 10px; border-radius: 9px; border: 1px solid #30363d; background: #11161d;
                         color: #adbac7; cursor: pointer; }
  .deskcap-preset-card:hover:not(:disabled) { border-color: #58a6ff; }
  .deskcap-preset-card.on { border-color: #2ea043; background: linear-gradient(180deg, #13251a 0%, #0f1f14 100%); color: #e6edf3; box-shadow: inset 0 0 0 1px rgba(74, 194, 107, 0.25); }
  .deskcap-preset-card:disabled { opacity: .65; cursor: default; }
  .deskcap-preset-name { font-size: 12px; font-weight: 700; display: flex; align-items: center; gap: 6px; }
  .deskcap-preset-check { color: #4ac26b; font-weight: 800; }
  .deskcap-preset-desc { font-size: 11px; line-height: 1.35; color: #8b949e; }
  /* T46 · brain-switch-consolidate: the merged per-desk Brain control that replaced the T37
     Claude-model picker AND the retired header Brain Switch sheet's per-desk provider picker —
     provider pills (Claude/GLM/Kimi/Auto) +, when the desk runs Claude, the tier sub-row.
     Same colour key as the global master pills (claude=orange, glm/kimi=red, auto=green). */
  .deskcap-brain-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
  .deskcap-brain-lbl { font-size: 12px; color: #e6edf3; font-weight: 600; }
  .deskcap-brain-lock { font-size: 11px; font-weight: 700; color: #8b949e; }
  .deskcap-brain-pills { display: flex; gap: 6px; flex-wrap: wrap; }
  .brain-pill { flex: 1 1 auto; min-width: 64px; display: flex; align-items: center; justify-content: center;
    gap: 4px; padding: 8px 6px; border-radius: 8px; border: 1px solid #30363d; background: #0d1117;
    color: #adbac7; font-size: 12px; font-weight: 600; cursor: pointer; }
  .brain-pill:hover:not(:disabled) { border-color: #484f58; }
  .brain-pill:disabled { opacity: .55; cursor: not-allowed; }
  .brain-pill .bp-ic { font-size: 13px; }
  .brain-pill.pinned { box-shadow: inset 0 0 0 1px #8b949e; }
  .brain-pill.on.claude { border-color: #ffa657; background: #3a2412; color: #ffa657; }
  .brain-pill.on.glm, .brain-pill.on.kimi { border-color: #f87171; background: #3a0d0d; color: #f87171; }
  .brain-pill.on.xai { border-color: #d4a72c; background: #332707; color: #d4a72c; }
  .brain-pill.on.chatgpt-codex { border-color: #10a37f; background: #072b24; color: #34d3a2; }
  .brain-pill.on.auto { border-color: #3fb950; background: #0f2818; color: #3fb950; }
  .deskcap-brain-tier { display: flex; align-items: center; justify-content: space-between; gap: 10px;
    flex-wrap: wrap; margin-top: 10px; }
  .deskcap-brain-tier-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: #8b949e; }
  /* The grouped list — vertical only, no horizontal scroll. */
  .deskcap-list { display: flex; flex-direction: column; gap: 16px; }
  .deskcap-sec { display: flex; flex-direction: column; gap: 8px; }
  .deskcap-sec-hd { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
  .deskcap-sec-open { border: 0; background: transparent; color: #8b949e; padding: 0; margin: 0;
                      font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; cursor: pointer; }
  .deskcap-sec-meta { display: flex; align-items: center; gap: 10px; }
  .deskcap-sec-count { font-size: 11px; color: #8b949e; }
  .deskcap-sec-body { display: none; flex-direction: column; gap: 8px; }
  .deskcap-sec-body.open { display: flex; }
  /* One capability row: icon · (label + tags + description + warnings) · (dot + toggle). The middle
     column is min-width:0 and wraps its text, so a long label or description never forces horizontal
     scroll; the icon and the control column are fixed-width. */
  .deskcap-row { display: flex; align-items: flex-start; gap: 11px; width: 100%; box-sizing: border-box;
                 background: #0d1117; border: 1px solid #21262d; border-radius: 10px; padding: 12px; }
  .deskcap-row.is-on { border-color: #2ea043; }
  .deskcap-row.is-locked, .deskcap-row.is-soon { opacity: .55; }
  .deskcap-icon { flex: 0 0 auto; font-size: 18px; line-height: 1.35; width: 22px; text-align: center; }
  .deskcap-info { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
  .deskcap-nm { font-size: 14px; font-weight: 600; color: #e6edf3; display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
  .deskcap-desc { font-size: 12.5px; color: #8b949e; margin-top: 4px; line-height: 1.45; overflow-wrap: anywhere; }
  .deskcap-tag { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
                 border-radius: 999px; padding: 2px 7px; border: 1px solid #30363d; color: #8b949e; white-space: nowrap; }
  .deskcap-tag.metered { border-color: #9e6a03; color: #e3b341; }
  .deskcap-tag.free { border-color: #2ea043; color: #4ac26b; }
  .deskcap-tag.local { border-color: #2ea043; color: #4ac26b; }
  .deskcap-tag.unwired { border-color: #30363d; color: #6e7681; }
  .deskcap-tag.locked { border-color: #6e7681; color: #8b949e; }
  .deskcap-warn { font-size: 11.5px; color: #e3b341; margin-top: 6px; line-height: 1.4; overflow-wrap: anywhere; }
  .deskcap-ctl { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 5px; padding-top: 2px; }


  .spin { display: inline-block; animation: vs-spin 0.9s linear infinite; }
  @keyframes vs-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

  /* WO-TTS-VOICE-PICKER (task 213): device speechSynthesis voice list (English first) as
     tappable rows (no <select>) + speed/pitch sliders + Save. Reuses .modal/.sheet chrome. */
  .voice-sheet { max-width: 480px; }
  .voice-list { display: flex; flex-direction: column; gap: 6px; max-height: 42vh; overflow-y: auto;
                -webkit-overflow-scrolling: touch; margin-top: 10px; border: 1px solid #21262d;
                border-radius: 9px; padding: 6px; }
  /* WO-CHAT-TTS-NEURAL (task 281): section labels splitting the picker into the OpenAI cloud
     voices (top) and the free device voices (below), so it's clear which rows cost money. */
  .voice-group-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
                        color: #8b949e; padding: 6px 4px 2px; }
  .voice-group-label:first-child { padding-top: 0; }
  .voice-row.voice-row-neural.active { border-color: #a371f7; background: #1c1530; }
  .voice-row.voice-row-neural.active .voice-play { border-color: #a371f7; }
  .voice-row { display: flex; align-items: center; justify-content: space-between; gap: 10px;
               background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 9px 10px;
               cursor: pointer; }
  .voice-row.active { border-color: #3fb950; background: #122016; }
  .voice-info { display: flex; flex-direction: column; min-width: 0; }
  .voice-name { font-size: 13px; font-weight: 600; color: #e6edf3; overflow: hidden;
                text-overflow: ellipsis; white-space: nowrap; }
  .voice-lang { font-size: 11px; color: #8b949e; }
  .voice-play { flex: 0 0 auto; width: 32px; height: 32px; border-radius: 50%; background: #21262d;
                border: 1px solid #30363d; color: #e6edf3; font-size: 13px; cursor: pointer;
                display: flex; align-items: center; justify-content: center; padding: 0 !important; }
  .voice-play:hover { background: #30363d; }
  .voice-row.active .voice-play { border-color: #3fb950; }
  .voice-sliders { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }
  .voice-slider-row { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: #8b949e; }
  .voice-slider-lab b { color: #e6edf3; font-variant-numeric: tabular-nums; }
  .voice-slider-row input[type=range] { width: 100%; accent-color: #3fb950; cursor: pointer; }
  .voice-save-row { display: flex; align-items: center; gap: 10px; margin-top: 14px; }

  /* WO-BOX-COMMAND-CONSOLE (task 200): one-click flow buttons + free-text allowlisted input +
     streamed output panel. */
  .console-flows { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0; }
  .console-input { display: flex; gap: 8px; margin: 8px 0; }
  .console-input input { flex: 1; background: #0d1117; border: 1px solid #30363d; color: #e6edf3;
    border-radius: 7px; padding: 7px 9px; font-size: 13px; font-family: ui-monospace, monospace; }
  .console-status { font-size: 12px; color: #8b949e; min-height: 16px; margin-top: 4px; }
  .console-status.ok { color: #4ac26b; }
  .console-status.fail { color: #f85149; }
  .console-status.running { color: #f0883e; }
  .console-output { background: #0d1117; border: 1px solid #21262d; border-radius: 9px; padding: 10px 12px;
    font-size: 12px; font-family: ui-monospace, monospace; white-space: pre-wrap; word-break: break-word;
    max-height: 360px; overflow-y: auto; margin-top: 8px; min-height: 0; }
  .console-output:empty { display: none; }

  /* WO-24 Google connectors */
  .conn-connect { padding: 6px 12px !important; font-size: 13px; }
  .conn-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-top: 1px solid #21262d; font-size: 12px; }
  .conn-row:first-of-type { border-top: 0; }
  .conn-svc { text-transform: capitalize; font-weight: 600; min-width: 74px; }
  .conn-sub { color: #8b949e; font-size: 11px; }
  .conn-acts { margin-left: auto; display: flex; gap: 6px; }
  .conn-acts button { padding: 3px 8px !important; font-size: 12px; }
  .conn-off { opacity: 0.55; }

  /* WO-GOOGLE-MULTI-ACCOUNT (task 255): N Google accounts, each grouped with its own
     Calendar/Gmail/Drive rows. Reuses .conn-row/.conn-acts (already mobile-safe, ≥44px on a
     phone via the media query below) inside a per-account wrapper. */
  .google-acct { border: 1px solid #21262d; border-radius: 9px; padding: 8px 10px; margin: 8px 0; }
  .google-acct.google-acct-active { border-color: #2d6cdf; background: rgba(45, 108, 223, 0.06); }
  .google-kind-pill { text-transform: capitalize; font-size: 10px; padding: 1px 7px; border-radius: 999px;
    background: #21262d; color: #8b949e; border: 1px solid #30363d; }
  .google-acct-active-note { font-size: 11px; color: #4ac26b; }

  /* WO-MULTI-ACCOUNT-GOOGLE: a desk's GRANTED accounts, one chip each, in the "Google accounts per
     desk" list. ★ = the primary (what a call with no `account` lands on); the other chips are
     accounts a call may target explicitly. Each chip carries its own make-primary / ✕ revoke. */
  .google-desk-accts { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 3px; }
  .google-desk-acct { display: inline-flex; align-items: center; gap: 5px; padding: 2px 4px 2px 9px;
    border-radius: 999px; border: 1px solid #30363d; background: #161b22; color: #8b949e; font-size: 11px; }
  .google-desk-acct.is-primary { border-color: #2d6cdf; color: #e6edf3; background: rgba(45, 108, 223, 0.1); }
  .google-desk-acct button { background: none !important; border: none !important; color: #6e7681;
    cursor: pointer; font-size: 10px; padding: 2px 4px !important; }
  .google-desk-acct button:hover { color: #58a6ff; }
  .google-desk-acct button:last-child:hover { color: #f85149; }

  /* WO-DESK-PICKER-GRANTS: searchable desk picker — the free-text desk field is gone. The user
     searches by friendly name OR id and picks a real desk; the exact id (the same ids /agents and
     the backend key grants on) lands in the hidden #google-bind-target. No <select>; mobile-safe. */
  .desk-picker { position: relative; flex: 1; min-width: 160px; }
  .desk-picker .desk-picker-input { width: 100%; box-sizing: border-box;
    background: #0d1117; border: 1px solid #30363d; color: #e6edf3; border-radius: 7px;
    padding: 7px 9px; font-size: 13px; }
  .desk-picker .desk-picker-input::placeholder { color: #6e7681; }
  .desk-picker .desk-picker-input:focus { outline: none; border-color: #2d6cdf; }
  .desk-picker-confirm { margin-top: 5px; font-size: 11px; color: #8b949e; line-height: 1.4; }
  .desk-picker-confirm b { color: #e6edf3; font-weight: 600; }
  .desk-picker-confirm code { background: #21262d; padding: 1px 5px; border-radius: 4px; color: #58a6ff; font-size: 11px; }
  .desk-picker-results { position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: var(--z-dropdown);
    max-height: 240px; overflow-y: auto; -webkit-overscroll-behavior: contain; overscroll-behavior: contain;
    background: #161b22; border: 1px solid #30363d; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.45); padding: 4px; }
  .desk-picker-opt { display: flex; justify-content: space-between; align-items: center; gap: 8px;
    width: 100%; text-align: left; background: none !important; border: none !important;
    color: #e6edf3 !important; padding: 7px 9px !important; border-radius: 6px; cursor: pointer; font-size: 13px; }
  .desk-picker-opt:hover, .desk-picker-opt.is-active { background: #21262d !important; }
  .desk-picker-opt.is-selected .desk-picker-label { color: #58a6ff; }
  .desk-picker-label { color: #e6edf3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .desk-picker-id { color: #6e7681; font-size: 11px; flex: none;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  .desk-picker-empty { padding: 8px 10px; color: #6e7681; font-size: 12px; }

  /* WO-IMAP-EMAIL-CONNECTORS (task 396): Yahoo/iCloud/Outlook/custom IMAP accounts. Reuses the
     Google multi-account card chrome (.google-acct/.conn-row are already mobile-safe) — an
     `auth_error` account gets a warn border + an inline reconnect prompt instead of disappearing. */
  .imap-acct { border: 1px solid #21262d; border-radius: 9px; padding: 8px 10px; margin: 8px 0; }
  .imap-acct.imap-acct-error { border-color: #f0883e; background: rgba(240, 136, 62, 0.08); }
  .imap-preset-hint { color: #8b949e; font-size: 11px; margin: 4px 0 8px; }
  .imap-reconnect-row { display: none; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
  .imap-reconnect-row.on { display: flex; }

  /* per-agent chat */
  .chatpanel { display: none; margin: 0 20px 24px; background: #161b22; border: 1px solid #21262d;
               border-radius: 10px; overflow: hidden; position: relative; }
  .chatpanel.on { display: block; }
  /* WO-CHAT-DRAG-DROP (task 234): drop-zone affordance shown while a file is dragged over the
     chat panel. pointer-events:none so it never intercepts the dragover/drop itself — those
     listeners live on .chatpanel and rely on native bubbling from whatever's underneath. */
  .chat-dropzone { display: none; position: absolute; inset: 0; z-index: var(--z-raised); align-items: center; justify-content: center;
                    background: rgba(31, 111, 235, 0.15); border: 2px dashed #1f6feb; border-radius: 10px; pointer-events: none; }
  .chat-dropzone.on { display: flex; }
  .chat-dropzone-inner { background: #0d1117; color: #58a6ff; border: 1px solid #1f6feb; border-radius: 8px;
                          padding: 14px 22px; font-size: 14px; font-weight: 600; }
  .chatpanel h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: #8b949e;
                  margin: 0; padding: 10px 14px; border-bottom: 1px solid #21262d; display: flow-root; }

  /* WO-RAIL-3 BOARD: request tracker now lives in the right rail's Board pane. */
  .desk-board { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; background: #161b22; }
  .desk-board-head { display: flex; align-items: center; justify-content: space-between; gap: 8px;
                     padding: 10px 14px; border-bottom: 1px solid #21262d; flex: 0 0 auto; }
  .desk-board-title { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: #8b949e; margin: 0; }
  .desk-board-actions { display: flex; gap: 8px; align-items: center; }
  .desk-board-body { flex: 1 1 auto; display: flex; gap: 10px; align-items: stretch;
                     overflow-x: auto; padding: 12px 14px 80px; }
  .board-empty { color: #8b949e; font-size: 13px; padding: 20px 0; }
  .board-retry { background: #21262d; border: 1px solid #30363d; border-radius: 8px; padding: 14px; margin: 12px 0; }
  .board-retry button { margin-top: 8px; }
  .board-status-section { flex: 1 1 0; min-width: 220px; display: flex; flex-direction: column; gap: 8px;
                          background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 8px; }
  .board-status-header { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
                         padding: 6px 8px; border-radius: 6px; display: flex;
                         justify-content: space-between; align-items: center; }
  .board-status-header.still_need { background: rgba(210, 153, 34, 0.15); color: #d29922; }
  .board-status-header.blocked   { background: rgba(248, 81, 73, 0.15); color: #f85149; }
  .board-status-header.waiting_on { background: rgba(88, 166, 255, 0.15); color: #58a6ff; }
  .board-status-header.have      { background: rgba(46, 160, 67, 0.15); color: #3fb950; }
  .board-column-body { flex: 1 1 auto; display: flex; flex-direction: column; gap: 8px; min-height: 40px; }
  .board-empty-col { color: #6e7681; font-size: 12px; padding: 10px 0; text-align: center; font-style: italic; }
  .board-row { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; border-radius: 6px;
               border: 1px solid #30363d; background: #161b22; cursor: pointer; }
  .board-row:hover { background: #1c2129; border-color: #8b949e; }
  .board-row.have .board-title { text-decoration: line-through; color: #8b949e; }
  .board-card-header { display: flex; justify-content: space-between; align-items: center; gap: 6px; }
  .board-status-pill { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
                       padding: 2px 6px; border-radius: 10px; border: 1px solid; background: transparent;
                       cursor: pointer; color: #8b949e; }
  .board-status-pill.still_need { color: #d29922; border-color: #d29922; }
  .board-status-pill.waiting_on { color: #58a6ff; border-color: #58a6ff; }
  .board-status-pill.have      { color: #3fb950; border-color: #3fb950; }
  .board-status-pill.blocked   { color: #f85149; border-color: #f85149; }
  .board-status-pill option { background: #0d1117; color: #c9d1d9; }
  .board-main { min-width: 0; }
  .board-title { font-size: 13px; color: #e6edf3; word-break: break-word; }
  .board-meta { font-size: 11px; color: #8b949e; margin-top: 2px; }
  .board-note { font-size: 11px; color: #6e7681; margin-top: 2px; font-style: italic;
                display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                line-clamp: 2; overflow: hidden; }
  .board-edit-row { background: #21262d; border: 1px solid #30363d; border-radius: 6px; padding: 8px; }
  .board-edit-row input, .board-edit-row select, .board-edit-row textarea {
    width: 100%; box-sizing: border-box; background: #0d1117; border: 1px solid #30363d; color: #e6edf3;
    border-radius: 4px; padding: 5px 7px; font-size: 12px; margin-bottom: 6px; }
  .board-edit-row textarea { resize: vertical; min-height: 44px; }
  .board-edit-actions { display: flex; gap: 6px; justify-content: flex-end; }
  .board-btn { background: #21262d; border: 1px solid #30363d; color: #c9d1d9; border-radius: 4px;
               padding: 4px 10px; font-size: 11px; cursor: pointer; }
  .board-btn.primary { background: #1f6feb; border-color: #1f6feb; color: #fff; }
  .board-btn:hover { background: #30363d; }
  .board-btn.primary:hover { background: #388bfd; }
  .board-add-btn { margin-left: auto; }
  .board-settings { display: flex; align-items: center; gap: 6px; font-size: 11px; color: #8b949e; }
  .board-settings input[type="checkbox"] { accent-color: #1f6feb; }

  /* WO-CHAT-WINDOW-TALLER (task #187): 280px fixed showed only a few lines and hid the rest of
     the conversation behind a tiny scrollbar. Tie the height to the viewport instead of a fixed
     px value so the box stays comfortably readable on any monitor, with a floor for short
     windows and a cap so it never crowds out the rest of the page. */
  .chatlog { max-height: calc(100vh - 260px); min-height: 360px; overflow: auto; overscroll-behavior: contain; padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
  /* WO-CHAT-SCROLL-BOTTOM (task 354): floating jump-to-newest control. .chatlog-wrap shrink-wraps
     to exactly #chatlog's own box (desktop) or fills the flex row (mobile, see the ≤759px
     override below), so `position:absolute; bottom` sits right at the scrollable area's edge —
     just above the composer — in both layouts. */
  .chatlog-wrap { position: relative; min-height: 0; }
  .chat-jump-bottom { position: absolute; right: 16px; bottom: 12px; width: 40px; height: 40px;
    border-radius: 50%; background: #1f6feb; color: #fff; border: 1px solid #30363d;
    display: none; align-items: center; justify-content: center; font-size: 18px; line-height: 1;
    cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,.45); z-index: var(--z-raised); padding: 0; }
  .chat-jump-bottom.show { display: flex; }
  .chat-jump-bottom:hover { background: #388bfd; }
  .chat-jump-badge { position: absolute; top: -4px; right: -4px; min-width: 17px; height: 17px;
    padding: 0 4px; border-radius: 999px; background: #f85149; color: #fff; font-size: 10px;
    font-weight: 700; line-height: 17px; text-align: center; box-shadow: 0 0 0 2px #161b22; }
  /* min-width:0 (not the flex-item default of auto) — without it some engines size a column-
     flex child's cross axis off its text's min-content, which with word-break:break-word can
     collapse to a single glyph and wrap the whole message one character per line
     (WO-INSANE-BRAIN-POLISH req 4). */
  .bubble { max-width: 80%; min-width: 0; padding: 7px 11px; border-radius: 12px; font-size: 13px; }
  .bubble .btext { white-space: pre-wrap; word-break: break-word; }
  .bubble.you { align-self: flex-end; background: #1c2d4a; color: #cce0ff; }
  .bubble.bot { align-self: flex-start; background: #1b2129; }
  .bubble.sys { align-self: center; background: transparent; color: #6e7681; font-size: 12px; }
  /* WO-DURABLE-TURNS (task 358): a turn that failed loudly (crash mid-run) — never a silent
     vanish, always a one-tap way back in. */
  .bubble.turn-failed { border: 1px solid #f0883e; color: #f0883e; }
  .bubble.turn-failed .turn-retry-btn { margin-top: 6px; }
  .bubble .meta { display: block; font-size: 10px; color: #8b949e; margin-top: 3px; }
  /* AI-working indicator: shown as a bubble while a desk/manager is generating a reply */
  .bubble.typing { display: flex; align-items: center; padding: 10px 13px; }
  .bubble.typing .dots span { display: inline-block; opacity: .2; animation: blink 1.2s infinite; font-size: 10px; }
  .bubble.typing .dots span:nth-child(2) { animation-delay: .2s; }
  .bubble.typing .dots span:nth-child(3) { animation-delay: .4s; }
  @keyframes blink { 0%, 80%, 100% { opacity: .2; } 40% { opacity: 1; } }
  /* WO-CHAT-THINKING-COLLAPSIBLE: live "thinking / working" trace shown WHILE a reply is
     generating (task #198) — a step-by-step of tool calls / reasoning beneath the dots. */
  .bubble.typing.think-live { flex-direction: column; align-items: stretch; gap: 4px; }
  .bubble.typing .think-head { display: flex; align-items: center; gap: 8px; }
  .think-steps-live { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
  .think-steps-live .think-line { font-size: 11px; color: #8b949e; white-space: pre-wrap; word-break: break-word; }
  .think-steps-live .think-line.err { color: #f0883e; }
  /* Once the reply lands, the trace COLLAPSES to a small tap-to-expand chip (default collapsed —
     the conversation stays short; the full step-by-step is one tap away). */
  .think-block { margin-top: 4px; }
  .think-toggle { display: inline-flex; align-items: center; gap: 5px; background: #21262d; color: #8b949e;
                  border: 1px solid #30363d; border-radius: 20px; padding: 3px 10px; font-size: 11px;
                  cursor: pointer; min-height: 26px; }
  .think-toggle:hover { color: #e6edf3; border-color: #444c56; }
  .think-toggle .think-caret { display: inline-block; transition: transform .15s; }
  .think-block.expanded .think-toggle .think-caret { transform: rotate(90deg); }
  .think-steps { display: none; flex-direction: column; gap: 3px; margin-top: 6px; padding: 8px 10px;
                 background: #0d1117; border: 1px solid #21262d; border-radius: 8px; }
  .think-block.expanded .think-steps { display: flex; }
  .think-steps .think-line { font-size: 11px; color: #8b949e; white-space: pre-wrap; word-break: break-word; }
  .think-steps .think-line.err { color: #f0883e; }
  /* ── IB-02 · A TOOL CALL IS A CHIP, NOT A WALL OF TEXT ────────────────────────────────────
     Deliberately the SAME object as the thinking chip above (.think-toggle's pill, its caret, its
     sizes) — a second visual language for "there is more behind this line" would make the
     transcript busier, which is the opposite of what this spec is for. The differences are the 🔧
     and, on a failure, the red the spec asks for (which still needs a tap: nothing auto-opens).
     The body is a capped scroller, because what is behind the tap is exactly the two thousand
     characters that must never be in the message body. */
  .tool-chips { display: flex; flex-direction: column; gap: 4px; margin: 0 0 6px; }
  .tool-chip { display: flex; flex-direction: column; align-items: flex-start; max-width: 100%; min-width: 0; }
  .tool-chip-toggle { display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
                      background: #21262d; color: #8b949e; border: 1px solid #30363d;
                      border-radius: 20px; padding: 3px 10px; font-size: 11px; cursor: pointer;
                      min-height: 26px; text-align: left; }
  .tool-chip-toggle:hover { color: #e6edf3; border-color: #444c56; }
  .tool-chip-toggle .think-caret { display: inline-block; transition: transform .15s; flex: 0 0 auto; }
  .tool-chip.expanded .tool-chip-toggle .think-caret { transform: rotate(90deg); }
  /* The summary is one line and stays one line — it is a label, not a paragraph. */
  .tool-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tool-chip-hint { flex: 0 0 auto; opacity: .65; }
  .tool-chip.err .tool-chip-toggle { background: #2d1a17; border-color: #6e2b25; color: #f0883e; }
  .tool-chip.err .tool-chip-toggle:hover { color: #ffa657; border-color: #8d3a31; }
  .tool-chip-body { display: none; flex-direction: column; gap: 8px; margin-top: 6px; width: 100%;
                    max-width: 100%; min-width: 0; padding: 8px 10px; background: #0d1117;
                    border: 1px solid #21262d; border-radius: 8px; }
  .tool-chip.expanded .tool-chip-body { display: flex; }
  .tool-chip-step { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
  .tool-chip-step + .tool-chip-step { padding-top: 8px; border-top: 1px solid #21262d; }
  .tool-chip-name { font-size: 11px; color: #e6edf3; font-weight: 600; overflow-wrap: anywhere; }
  .tool-chip-name .tool-chip-outcome { font-weight: 400; color: #8b949e; }
  .tool-chip-step.err .tool-chip-name .tool-chip-outcome { color: #f0883e; }
  .tool-chip-key { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: #6e7681; }
  .tool-chip-pre { margin: 0; font-size: 11px; line-height: 1.45; color: #8b949e;
                   font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                   white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere;
                   max-height: 220px; overflow: auto; -webkit-overflow-scrolling: touch; }
  .tool-chip-note { font-size: 10px; color: #6e7681; font-style: italic; }
  /* ── TIB-088 ─────────────────────────────────────────────────────────────────────────────
     Requirement #4: the thinking box counts up WHILE the desk works, collapsed, with the current
     step's first line showing — so a long think reads as progress instead of a hang. It is the
     same chip the finished reply carries (.think-block above), rendered live, which is why it
     reuses those classes rather than growing a parallel set. */
  .think-live .think-block { margin-top: 2px; }
  .think-live .think-current { font-size: 11px; color: #8b949e; margin-top: 3px;
                               white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .think-live .think-current.err { color: #f0883e; }
  /* Requirement #5: the beat says what it is DOING next to the clock, not just that a clock runs. */
  .think-elapsed { font-variant-numeric: tabular-nums; }
  /* ── IB-02 · THE CLOCK AND STOP ARE PINNED, NOT CARRIED ───────────────────────────────────
     They used to ride in the working bubble, which is a message: it grows with every step and
     scrolls with the conversation, so on a long turn the one control he might need urgently was
     the first thing off the top of the screen — and the moment the answer started streaming the
     bubble was removed outright, taking Stop with it while the turn was still running.
     This bar is not a message. It is the row directly above the composer, in the same flex column,
     so it cannot be scrolled and cannot be pushed off. It carries three things and no fourth: the
     elapsed time, the current beat, and Stop. It exists only while a turn runs. */
  .turn-bar { display: none; align-items: center; gap: 10px; padding: 6px 14px;
              border-top: 1px solid #21262d; background: #12181f; }
  .turn-bar.on { display: flex; }
  .turn-bar .turn-bar-clock { flex: 0 0 auto; font-size: 12px; color: #58a6ff;
                              font-variant-numeric: tabular-nums; }
  /* The beat is the part allowed to lose room — the clock and Stop are never squeezed by it. */
  .turn-bar .turn-bar-beat { flex: 1 1 auto; min-width: 0; font-size: 12px; color: #8b949e;
                             white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .turn-bar .turn-bar-stop { flex: 0 0 auto; background: transparent; color: #e6edf3;
                             border: 1px solid #4a5568; border-radius: 6px; padding: 0 12px;
                             min-height: 30px; font-size: 12px; cursor: pointer; }
  .turn-bar .turn-bar-stop:hover { border-color: #f85149; color: #ff7b72; }
  .turn-bar .turn-bar-stop[disabled] { opacity: .6; cursor: default; }
  /* Requirement #2: a partial answer STAYS, and wears one plain line saying where it stopped.
     Deliberately understated — an amber rule and small text under the words, never a card that
     replaces them, never a red box that reads as "this reply is garbage". */
  .stop-notice { margin-top: 7px; padding-top: 6px; border-top: 1px solid #3d2f1b; }
  .stop-line { font-size: 11px; color: #d29922; display: flex; align-items: baseline; gap: 6px;
               flex-wrap: wrap; }
  .stop-line .stop-dot { color: #d29922; }
  /* Requirement #6: the provider's own words are one tap away, never in the main line. */
  .stop-detail-toggle { background: none; border: none; color: #6e7681; cursor: pointer;
                        font-size: 11px; padding: 0 2px; min-height: 24px; text-decoration: underline;
                        text-underline-offset: 2px; }
  .stop-detail-toggle:hover { color: #8b949e; }
  .stop-detail { display: none; margin-top: 5px; padding: 7px 9px; background: #0d1117;
                 border: 1px solid #21262d; border-radius: 8px; font-size: 11px; color: #8b949e;
                 font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                 white-space: pre-wrap; word-break: break-word; max-height: 180px; overflow: auto; }
  .stop-notice.open .stop-detail { display: block; }
  /* Requirement #3: a retry APPENDS. The first attempt's partial keeps its own block above, and
     the retry's output sits below it behind a labelled rule, so both are readable. */
  .retry-sep { margin-top: 9px; padding-top: 7px; border-top: 1px dashed #30363d;
               font-size: 11px; color: #6e7681; }
  /* per-response actions: Copy + Play/Stop (Web Speech API) */
  .bactions { display: flex; gap: 4px; margin-top: 5px; }
  .bact { background: none; border: none; color: #8b949e; cursor: pointer; font-size: 12px;
          padding: 2px 5px; border-radius: 4px; line-height: 1.4; }
  .bact:hover { color: #e6edf3; background: #21262d; }
  .bact.playing { color: #58a6ff; }
  /* WO-TTS-NEURAL-LAG-FIX (task 309): unmistakable "it registered" feedback the instant a
     neural-voice tap starts synthesis — a static ⏳ read as "stuck" and invited a second tap
     that then truncated playback (see toggleSpeech()'s tap-grace-window fix). */
  .bact.tts-loading { color: #58a6ff; animation: bact-pulse 0.9s ease-in-out infinite; }
  @keyframes bact-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
  /* T89: inline chat actions — proposed next-actions rendered as one-tap buttons under a bot
     bubble. Destructive gets a visibly distinct (red-tinted) treatment so a phone glance can tell
     the safe row from the one that needs a confirm before it fires. */
  .chat-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
  .chat-action-btn { background: #1c2d4a; color: #cce0ff; border: 1px solid #2a3f63; border-radius: 14px;
                      padding: 5px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer; }
  .chat-action-btn:hover { background: #24406e; }
  .chat-action-btn:disabled { opacity: 0.6; cursor: default; }
  .chat-action-btn.chat-action-destructive { background: #3a1f22; color: #ffb4ab; border-color: #6e2b30; }
  .chat-action-btn.chat-action-destructive:hover { background: #4a262b; }
  .attach-row { padding: 0 14px; display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px; }
  .attach-row:empty { display: none; }
  .attach-chip { display: inline-flex; align-items: center; gap: 6px; background: #1c2d4a; color: #cce0ff;
                 border: 1px solid #2a3f63; border-radius: 20px; padding: 3px 10px; font-size: 12px; margin: 6px 0; }
  .attach-chip button { background: none; border: none; color: #cce0ff; cursor: pointer; font-size: 13px; padding: 0; }
  /* WO-CHAT-MULTI-IMAGE (task 256): image-first queued attachment — a large square thumbnail
     with the filename demoted to a small truncated caption underneath (never wider than the
     thumbnail itself), plus a floating ✕ to remove it before send. */
  .attach-thumb-item { position: relative; width: 96px; flex: 0 0 auto; margin: 6px 0; }
  .attach-thumb-item img { width: 96px; height: 96px; border-radius: 10px; object-fit: cover;
                            display: block; border: 1px solid #30363d; cursor: zoom-in; }
  .attach-thumb-item .attach-thumb-name { display: block; font-size: 10px; color: #8b949e; margin-top: 3px;
                            white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 96px; }
  .attach-thumb-item .attach-thumb-remove { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px;
                            border-radius: 50%; background: #0d1117; color: #e6edf3; border: 1px solid #30363d;
                            cursor: pointer; font-size: 11px; line-height: 1; display: flex; align-items: center;
                            justify-content: center; padding: 0; }
  .attach-thumb-item .attach-thumb-remove:hover { background: #21262d; border-color: #f85149; color: #f85149; }
  /* ── IB-02 · PART 3 — A COMPOSER THAT FITS A THUMB ────────────────────────────────────────
     ONE row: the two icon buttons on the left, the text field taking every remaining pixel, Send
     on the right. `align-items: flex-end` keeps the icons and Send on the bottom edge as the field
     grows upward, so a five-line message pushes the box up, not the buttons around.
     `--composer-line`/`--composer-lines` are the whole growth rule in one place: the field is one
     line when empty and stops at five, after which it scrolls inside itself (autoGrowInput). */
  .chatform { --composer-lines: 5; --composer-font: 16px; --composer-line: 1.4; --composer-chrome: 16px;
              display: flex; align-items: flex-end; gap: 8px; padding: 10px 14px;
              border-top: 1px solid #21262d; }
  /* Auto-growing message field (task 26): a textarea that starts one row tall and grows with the
     text up to a max, then scrolls. resize:none because the auto-grow JS owns the height.
     IB-02 item 15: 16px at EVERY width, not just on the phone media query — anything smaller and
     iOS zooms the whole page the moment the field is tapped, and the page never zooms back. */
  /* `--composer-chrome` is this box's own padding + borders — 7px×2 + 1px×2 here, restated in the
     phone block where the padding is bigger. It is a variable so the one-line floor and the
     five-line ceiling below are both exactly what they say at every width, rather than a pixel
     count that drifts as soon as the padding changes. Desktop keeps the 38px row it has always
     had (16 × 1.4 + 16 = 38.4): the font went up for iOS's sake, the row did not. */
  .chatform textarea { flex: 1 1 auto; min-width: 0; background: #0d1117; color: #e6edf3;
                    border: 1px solid #30363d; border-radius: 6px; padding: 7px 10px; font: inherit;
                    font-size: var(--composer-font); line-height: var(--composer-line);
                    resize: none; overflow-y: auto;
                    /* one line + padding + border — an EMPTY box reserves one line, never four */
                    min-height: calc(var(--composer-font) * var(--composer-line) + var(--composer-chrome));
                    max-height: calc(var(--composer-font) * var(--composer-line) * var(--composer-lines) + var(--composer-chrome)); }
  .chatform button { background: #1f6feb; color: #fff; border: none; border-radius: 6px; padding: 7px 14px;
                     cursor: pointer; flex: 0 0 auto; }
  /* Item 14: nothing to send, so Send says so — quiet, and not tappable. */
  .chatform button[type="submit"][disabled] { background: #21262d; color: #6e7681; cursor: default; }
  /* WO-DESK-CHAT-LIVE-STEERING: Send relabels "Add" while THIS desk has a turn in flight
     (updateSendButtonState) but is never disabled — a send while busy steers into the live turn
     instead of being refused, so the composer can never hard-lock mid-turn. */
  .chatform .chat-attach { flex: 0 0 auto; width: 28px; height: 28px; display: inline-flex;
                           align-items: center; justify-content: center; background: #21262d;
                           color: #e6edf3; border: 1px solid #30363d; border-radius: 6px;
                           padding: 0; font-size: 14px; cursor: pointer; }
  /* WO-MOBILE-CHAT-HEADER (task 264): 📎 now fires the native picker directly (no popup menu) —
     .attach-wrap is just a flex-item wrapper now, kept for layout stability. */
  .attach-wrap { flex: 0 0 auto; }
  /* WO-CHAT-MIC (task 280): mic sits next to the paperclip, same idle look; .listening flips it
     red + pulsing while SpeechRecognition is actively capturing. */
  .chatform .chat-mic { flex: 0 0 auto; width: 28px; height: 28px; display: inline-flex;
                        align-items: center; justify-content: center; background: #21262d;
                        color: #e6edf3; border: 1px solid #30363d; border-radius: 6px;
                        padding: 0; cursor: pointer; font-size: 14px; }
  .chatform .chat-mic:hover { border-color: #58a6ff; }
  .chatform .chat-mic.listening { background: #3a1418; border-color: #f85149; color: #ff7b72;
                                   animation: micPulse 1.2s ease-in-out infinite; }
  @keyframes micPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(248, 81, 73, 0.55); }
    50%      { box-shadow: 0 0 0 6px rgba(248, 81, 73, 0); }
  }

  /* ── Tool Rail: pinned right rail on desktop, slide-out drawer on mobile ────────────────
     Tabs: Info (record card), Board (requests), Files. Collapsible on desktop. */
  .chatpanel-wrapper { flex: 1; min-width: 0; }
  .chatpanel { margin: 0 20px 24px 0; }
  .tool-rail { flex: 0 0 auto; width: var(--tool-rail-w); background: #161b22; border-left: 1px solid #21262d;
               border-radius: 0 10px 10px 0; display: none; flex-direction: column;
               margin-right: 20px; margin-bottom: 24px; overflow: hidden; }
  .tool-rail.on { display: flex; }
  .tool-rail-resize { display: none; }
  .chat-drawer-resize { display: none; }
  .tool-rail-header { display: flex; align-items: center; justify-content: space-between;
                      padding: 10px 14px; border-bottom: 1px solid #21262d; background: #0d1117; }
  .tool-rail-title { font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
                     color: #8b949e; margin: 0; }
  .tool-rail-close { display: none; background: none; border: none; color: #8b949e; cursor: pointer;
                     font-size: 16px; padding: 4px 8px; border-radius: 4px; line-height: 1; }
  .tool-rail-close:hover { color: #e6edf3; background: #21262d; }
  .tool-rail-collapse { background: none; border: none; color: #8b949e; cursor: pointer;
                        font-size: 14px; padding: 4px 8px; border-radius: 4px; line-height: 1; }
  .tool-rail-collapse:hover { color: #e6edf3; background: #21262d; }
  /* TIB-075 §3 · min-height:0 so the scrolling pane is what gives when the rail is short. Without
     it the body's content sets a floor the flex algorithm honours, and the dock below it — the tab
     row — is pushed past the bottom of the rail. The tabs are the way OUT of a tab, so they are the
     last thing on this page allowed to leave the screen. */
  .tool-rail-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px; display: flex; flex-direction: column; }
  .tool-rail-pane { display: none; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  .tool-rail-pane.active { display: flex; }
  /* Bottom dock: pinned strip at the bottom of the right rail. Tabs sit above the Google quick-access row. */
  .tool-rail-dock { flex: none; display: flex; flex-direction: column; border-top: 1px solid #21262d;
                    background: #0d1117; }
  /* TIB-075 §3 · six tabs in a rail dragged narrow used to be six tabs with the last two clipped
     off the edge, unreachable. The row scrolls sideways inside its own dock instead — the same
     treatment the phone drawer has had since T97, now at every width. */
  .tool-rail-tabs { display: flex; gap: 2px; padding: 0 12px; background: #0d1117;
                    overflow-x: auto; scrollbar-width: none; }
  .tool-rail-tabs::-webkit-scrollbar { display: none; }
  .tool-rail-tab { background: none; border: none; color: #8b949e; font-size: 12px; font-weight: 600;
                   padding: 8px 12px; cursor: pointer; border-bottom: 2px solid transparent; position: relative;
                   flex: 0 0 auto; white-space: nowrap; }
  .tool-rail-tab:hover { color: #c9d1d9; }
  .tool-rail-tab.active { color: #e6edf3; border-bottom-color: #58a6ff; }
  .tool-rail-tab-badge { position: absolute; top: 5px; right: 2px; min-width: 14px; height: 14px; padding: 0 4px;
                         border-radius: 7px; background: #f85149; color: #fff; font-size: 9px; font-weight: 700;
                         display: none; align-items: center; justify-content: center; }
  .tool-rail-tab-badge.on { display: flex; }
  .tool-rail-google { display: flex; align-items: center; justify-content: center; gap: 6px;
                      padding: 6px 10px; border-top: 1px solid #21262d; }
  .tool-rail-google:empty { display: none; }
  .trg-btn { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
             border-radius: 6px; background: #161b22; border: 1px solid #30363d; color: #c9d1d9;
             font-size: 13px; line-height: 1; text-decoration: none; cursor: pointer; transition: transform .08s, border-color .1s, box-shadow .1s; }
  .trg-btn:hover { transform: translateY(-1px); }
  /* WO-LAUNCHPAD-BRAND-ICONS: real Google product marks (same /brand assets as the launchpad). */
  .trg-btn .trg-ic { width: 17px; height: 17px; object-fit: contain; display: block; pointer-events: none; }
  .trg-btn.cal { color: #4285f4; }
  .trg-btn.cal:hover { border-color: #4285f4; box-shadow: 0 0 10px rgba(66,133,244,.35); }
  .trg-btn.drv { color: #34a853; }
  .trg-btn.drv:hover { border-color: #34a853; box-shadow: 0 0 10px rgba(52,168,83,.35); }
  .trg-btn.mail { color: #ea4335; }
  .trg-btn.mail:hover { border-color: #ea4335; box-shadow: 0 0 10px rgba(234,67,53,.35); }
  .trg-btn.dis { opacity: .45; cursor: default; }
  .trg-btn.dis:hover { transform: none; border-color: #30363d; box-shadow: none; }
  /* Empty state card */
  .tool-rail-empty { color: #8b949e; font-size: 13px; padding: 20px 0; text-align: center; }
  .tool-rail-empty button { background: #1f6feb; color: #fff; border: none; border-radius: 6px;
                             padding: 8px 16px; cursor: pointer; font-size: 12px; margin-top: 12px; }
  .tool-rail-empty button:hover { background: #388bfd; }
  /* Collapsed state: 36px edge strip with vertical icons */
  .tool-rail.collapsed { width: 36px; }
  .tool-rail.collapsed .tool-rail-header { padding: 10px 4px; justify-content: center; }
  .tool-rail.collapsed .tool-rail-title { display: none; }
  .tool-rail.collapsed .tool-rail-close { display: none; }
  .tool-rail.collapsed .tool-rail-dock { border-top: none; }
  .tool-rail.collapsed .tool-rail-tabs { flex-direction: column; padding: 8px 0; gap: 8px; }
  .tool-rail.collapsed .tool-rail-tab { padding: 8px; font-size: 14px; border-bottom: none;
                                         border-left: 2px solid transparent; }
  .tool-rail.collapsed .tool-rail-tab.active { border-left-color: #58a6ff; color: #e6edf3; }
  .tool-rail.collapsed .tool-rail-google { display: none; }
  .tool-rail.collapsed .tool-rail-body { display: none; }
  .tool-rail.collapsed .tool-rail-collapse { transform: rotate(180deg); }

  /* ── WO-DESK-INFO-SIDEBAR (task T33): the Info tab's own furniture ─────────────────────────
     The tab used to hold only the (often absent) record card. It now leads with the chat controls
     that were crowding the chat header — the live context meter + Compact, Attachments, New chat —
     followed by Pinned Context. Everything here is scoped under the rail so nothing leaks into the
     chat panel's styling. */
  .info-tools { display: flex; flex-direction: column; gap: 10px; margin-bottom: 14px; }
  .info-ctx { background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 9px 10px; }
  .info-ctx-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .info-ctx-label { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: #8b949e; font-weight: 600; }
  /* The number (e.g. "~8.6K ctx"). Colour is set inline by renderChatSizeMeter against the
     warn/red thresholds, so only the shape lives here. */
  .chat-size-meter { font-size: 11px; font-weight: 600; border-radius: 999px; padding: 2px 8px;
                     display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
  .info-ctx-bar { margin-top: 7px; height: 6px; border-radius: 999px; background: #21262d; overflow: hidden; }
  .info-ctx-fill { height: 100%; width: 0; border-radius: 999px; background: #3fb950; transition: width .25s ease, background .25s ease; }
  .info-ctx-actions { display: flex; gap: 6px; margin-top: 8px; }
  .info-btn { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 6px;
              padding: 0 10px; min-height: 36px; font-size: 12px; font-weight: 500; cursor: pointer;
              display: inline-flex; align-items: center; justify-content: center; gap: 5px; flex: 1 1 auto; }
  .info-btn:hover { background: #30363d; border-color: #484f58; }
  .info-btn:disabled { opacity: .55; cursor: default; }
  /* toggleAttachments() flips .active on #attachments-toggle — same "panel is open" affordance
     the old .attachments-toggle.active rule gave it before the move. */
  .info-btn.active { background: #1c2d4a; border-color: #1f6feb; color: #58a6ff; }
  .info-actions { display: flex; gap: 6px; }

  /* Pinned Context: chips Brandon pinned to THIS thread. They are also a protected prefix in the
     assembled prompt (see promptAssembly's `pins` section) — the UI says so in the empty state. */
  .pin-section { border-top: 1px solid #21262d; padding-top: 12px; margin-bottom: 14px; }
  .pin-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
  .pin-title { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: #c9d1d9; font-weight: 600; }
  .pin-add { background: none; border: 1px dashed #30363d; color: #58a6ff; border-radius: 6px;
             padding: 4px 8px; font-size: 11px; cursor: pointer; min-height: 30px; }
  .pin-add:hover { border-color: #1f6feb; background: #161b22; }
  .pin-chips { display: flex; flex-direction: column; gap: 6px; }
  .pin-chip { display: flex; align-items: flex-start; gap: 6px; background: #161b22;
              border: 1px solid #30363d; border-left: 3px solid #1f6feb; border-radius: 7px;
              padding: 6px 7px; font-size: 12px; color: #e6edf3; }
  .pin-chip.dragging { opacity: .45; }
  .pin-chip.drop-target { border-top: 2px solid #58a6ff; }
  .pin-drag { cursor: grab; color: #6e7681; font-size: 13px; line-height: 1.5; user-select: none;
              padding: 0 1px; flex: 0 0 auto; }
  .pin-drag:active { cursor: grabbing; }
  .pin-ic { background: none; border: none; padding: 0; cursor: pointer; font-size: 13px;
            line-height: 1.5; flex: 0 0 auto; }
  /* The chip body is the click-to-reference target (req #4) — a click drops "re: …" in the composer. */
  .pin-text { flex: 1 1 auto; min-width: 0; cursor: pointer; text-align: left; background: none;
              border: none; color: inherit; font: inherit; padding: 0; line-height: 1.45;
              overflow-wrap: anywhere; }
  .pin-text:hover { color: #58a6ff; }
  .pin-edit-input { flex: 1 1 auto; min-width: 0; background: #0d1117; border: 1px solid #1f6feb;
                    border-radius: 5px; color: #e6edf3; font: inherit; padding: 3px 5px; }
  .pin-act { background: none; border: none; color: #6e7681; cursor: pointer; font-size: 12px;
             line-height: 1.5; padding: 0 2px; flex: 0 0 auto; }
  .pin-act:hover { color: #e6edf3; }
  .pin-act.pin-x:hover { color: #f85149; }
  .pin-empty { color: #8b949e; font-size: 11.5px; line-height: 1.5; padding: 6px 0 2px; }
  .pin-empty b { color: #c9d1d9; }

  /* ── WO-CAP-DESK-MEMORY (task T31): DESK MEMORY ────────────────────────────────────────────
     What this desk knows PERMANENTLY — not just in this thread. Deliberately built from the pin
     furniture above (same rows, same actions) with a green rail instead of blue, so the difference
     reads at a glance: blue = pinned to this conversation, green = remembered forever. */
  .mem-chip { border-left-color: #2ea043; }
  .mem-text { flex: 1 1 auto; min-width: 0; line-height: 1.45; overflow-wrap: anywhere; }
  .mem-kind { color: #7ee787; font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
              flex: 0 0 auto; padding-top: 1px; }
  .pin-act.mem-promote:hover { color: #7ee787; }
  /* The floating highlight → Pin action. Positioned in viewport coordinates by
     showPinSelectionButton(); above every chat surface but under the modals. */
  .pin-selection-btn { position: fixed; z-index: var(--z-dropdown); background: #1f6feb; color: #fff;
                       border: 1px solid #388bfd; border-radius: 999px; padding: 6px 12px;
                       font-size: 12px; font-weight: 600; cursor: pointer;
                       box-shadow: 0 4px 14px rgba(0,0,0,.45); }
  .pin-selection-btn:hover { background: #388bfd; }

  /* WO-CAP-GMAIL-ACTIONS (task T28): THE APPROVALS CARD — one card for every write capability.
     A desk that wants to move a file, trash a thread or send an email cannot; it proposes, and the
     proposal waits here. Styled deliberately LOUDER than the rest of the rail (amber) because it is
     the one pane item that represents something not yet done that only Brandon can finish. Approve
     is red-ish rather than the usual blue: the tap makes a real change and must never read as the
     safe default. */
  .act-section { border-top: 1px solid #21262d; padding-top: 12px; margin-bottom: 14px; }
  .act-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
  .act-title { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: #c9d1d9; font-weight: 600; }
  .act-title.waiting { color: #d29922; }
  .act-count { background: #341a00; border: 1px solid #9e6a03; color: #e3b341; border-radius: 999px;
               padding: 1px 7px; font-size: 10.5px; font-weight: 700; }
  .act-list { display: flex; flex-direction: column; gap: 8px; }
  .act-card { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #30363d;
              border-radius: 7px; padding: 8px 9px; font-size: 12px; color: #e6edf3; }
  .act-card.pending { border-left-color: #d29922; background: #1a1710; }
  .act-card.applied { border-left-color: #3fb950; }
  .act-card.failed  { border-left-color: #f85149; }
  .act-card.rejected, .act-card.expired { border-left-color: #6e7681; opacity: .72; }
  /* IB-127: collapsed by default — a one-line disclosure button standing in for the full card
     until tapped. Real <button> (not a div+onclick) so it is keyboard- and screen-reader-reachable
     the same as Approve/Reject below it. */
  .act-toggle { display: flex; align-items: center; gap: 6px; width: 100%; background: transparent;
                border: none; padding: 0; margin: 0; color: inherit; font: inherit; text-align: left;
                cursor: pointer; }
  .act-chevron { flex: 0 0 auto; width: 10px; font-size: 10px; color: #8b949e; }
  .act-icon { flex: 0 0 auto; font-size: 13px; line-height: 1; }
  .act-short { flex: 1 1 auto; font-weight: 600; overflow-wrap: anywhere; }
  .act-when { flex: 0 0 auto; font-size: 10.5px; color: #6e7681; white-space: nowrap; }
  .act-detail { margin-top: 6px; }
  .act-tool { font-size: 10.5px; letter-spacing: .04em; text-transform: uppercase; color: #8b949e;
              font-weight: 600; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
  .act-cap { color: #6e7681; font-weight: 500; text-transform: none; letter-spacing: 0; }
  .act-summary { line-height: 1.45; margin: 3px 0 0; overflow-wrap: anywhere; }
  /* The full body of a proposed email. Brandon is approving THIS text, so it is shown verbatim and
     scrolls rather than being trimmed to a preview that could hide the last paragraph. */
  .act-body { margin-top: 6px; background: #0d1117; border: 1px solid #21262d; border-radius: 6px;
              padding: 6px 8px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
              font-size: 11px; line-height: 1.5; color: #adbac7; white-space: pre-wrap;
              max-height: 190px; overflow: auto; overflow-wrap: anywhere; }
  .act-meta { margin-top: 5px; font-size: 10.5px; color: #6e7681; }
  .act-meta.err { color: #f85149; }
  .act-btns { display: flex; gap: 6px; margin-top: 8px; }
  .act-btn { flex: 1 1 auto; border-radius: 6px; padding: 6px 10px; font-size: 11.5px; font-weight: 600;
             min-height: 32px; cursor: pointer; border: 1px solid #30363d; background: #21262d; color: #e6edf3; }
  .act-btn:disabled { opacity: .55; cursor: default; }
  .act-btn.approve { background: #4a1d1d; border-color: #b62324; color: #ff9a95; }
  .act-btn.approve:hover:not(:disabled) { background: #6e1f1f; color: #ffd7d5; }
  .act-btn.reject:hover:not(:disabled) { background: #30363d; }
  .act-empty { color: #8b949e; font-size: 11.5px; line-height: 1.5; padding: 6px 0 2px; }

  /* WO-FILING-CLERK: the Filing tab — drop zone, proposal cards, recently filed.
     Deliberately built from the same tokens as the Approvals card above (same borders, same greys,
     the same amber for "waiting on you"), because a filing proposal IS an approvals proposal: it
     lands in that card, and the two views should read as one mechanism seen from two places. */
  .fil-drop { border: 2px dashed #30363d; border-radius: 10px; padding: 18px 12px; text-align: center;
              color: #8b949e; font-size: 12px; line-height: 1.5; cursor: pointer; background: #0d1117;
              transition: border-color .12s, background .12s, color .12s; }
  .fil-drop:hover { border-color: #4a5568; color: #adbac7; }
  .fil-drop.over { border-color: #10b981; background: #0c2b22; color: #6ee7b7; }
  .fil-drop.busy { border-style: solid; border-color: #d29922; color: #e3b341; cursor: progress; }
  .fil-drop-big { display: block; font-size: 22px; margin-bottom: 6px; }
  .fil-drop-hint { display: block; margin-top: 5px; font-size: 10.5px; color: #6e7681; }
  /* The privacy line. It is not decoration — it is the reason this feature is allowed to point at
     tax paper at all, so it is always visible above the drop zone rather than in a tooltip. */
  .fil-local { display: flex; align-items: center; gap: 6px; font-size: 10.5px; color: #6ee7b7;
               background: #0c2b22; border: 1px solid #10b981; border-radius: 6px;
               padding: 5px 8px; margin-bottom: 8px; line-height: 1.45; }
  .fil-local.warn { color: #e3b341; background: #1a1710; border-color: #9e6a03; }
  .fil-local.err { color: #ff9a95; background: #2b1414; border-color: #b62324; }
  .fil-card { background: #161b22; border: 1px solid #30363d; border-left: 3px solid #d29922;
              border-radius: 7px; padding: 9px 10px; margin-top: 8px; font-size: 12px; color: #e6edf3; }
  .fil-card.filed { border-left-color: #3fb950; }
  .fil-card.ask { border-left-color: #f85149; }
  .fil-orig { font-size: 10.5px; color: #6e7681; display: flex; align-items: center; gap: 6px;
              flex-wrap: wrap; margin-bottom: 6px; }
  .fil-engine { color: #58a6ff; }
  .fil-conf { margin-left: auto; font-weight: 700; }
  .fil-conf.hi { color: #3fb950; } .fil-conf.mid { color: #e3b341; } .fil-conf.lo { color: #f85149; }
  .fil-label { display: block; font-size: 10px; letter-spacing: .05em; text-transform: uppercase;
               color: #8b949e; font-weight: 600; margin: 8px 0 3px; }
  .fil-input, .fil-select { width: 100%; box-sizing: border-box; background: #0d1117; color: #e6edf3;
              border: 1px solid #30363d; border-radius: 6px; padding: 7px 8px; font-size: 12px;
              font-family: ui-monospace, SFMono-Regular, Menlo, monospace; min-height: 34px; }
  .fil-input:focus, .fil-select:focus { outline: none; border-color: #58a6ff; }
  .fil-select { font-family: inherit; }
  /* The audit trail: the exact text behind each field. This is what makes a proposal checkable
     rather than something Brandon can only rubber-stamp. */
  .fil-why { margin-top: 7px; font-size: 10.5px; color: #6e7681; line-height: 1.6; }
  /* TIB-020 · "matched 3 past Nevada State Bank statements". Deliberately the one line on this card
     that is not grey: a name that came from documents Brandon already labelled is a different claim
     from a cold guess, and silence about the difference is why nobody trusts it. */
  .fil-learned { color: #7ee787; }
  .fil-sig { display: inline-block; background: #0d1117; border: 1px solid #21262d; border-radius: 4px;
             padding: 1px 5px; margin: 2px 3px 0 0; color: #8b949e; }
  .fil-sig b { color: #adbac7; font-weight: 600; }
  .fil-btns { display: flex; gap: 6px; margin-top: 9px; }
  .fil-btn { flex: 1 1 auto; border-radius: 6px; padding: 7px 10px; font-size: 11.5px; font-weight: 600;
             min-height: 34px; cursor: pointer; border: 1px solid #30363d; background: #21262d; color: #e6edf3; }
  .fil-btn:disabled { opacity: .55; cursor: default; }
  .fil-btn.file { background: #0c2b22; border-color: #10b981; color: #6ee7b7; }
  .fil-btn.file:hover:not(:disabled) { background: #0f3a2e; color: #a7f3d0; }
  .fil-btn.drop:hover:not(:disabled) { background: #30363d; }
  .fil-note { margin-top: 7px; font-size: 11px; color: #8b949e; line-height: 1.5; }
  .fil-note.err { color: #ff9a95; }
  .fil-recent { display: flex; flex-direction: column; gap: 5px; }
  .fil-recent-row { display: flex; align-items: baseline; gap: 7px; font-size: 11.5px; color: #adbac7;
                    padding: 5px 7px; background: #0d1117; border: 1px solid #21262d; border-radius: 6px; }
  .fil-recent-name { overflow-wrap: anywhere; }
  .fil-recent-when { margin-left: auto; font-size: 10px; color: #6e7681; white-space: nowrap; }
  .fil-msg { width: 100%; box-sizing: border-box; background: #0d1117; color: #e6edf3;
             border: 1px solid #30363d; border-radius: 6px; padding: 7px 8px; font-size: 12px;
             min-height: 56px; resize: vertical; line-height: 1.5; }
  .fil-msg:focus { outline: none; border-color: #58a6ff; }
  /* WO-CLERK-ORGANIZE: the reorg half of the tab — tidying files that are ALREADY in Drive. Same
     card language as the intake pile on purpose: it is the same clerk making the same kind of
     proposal, and a second visual vocabulary would suggest a second approval mechanism. A card
     with nothing to propose goes green and quiet rather than disappearing, so "already right" is
     a visible answer. */
  .fil-org-line { font-size: 10.5px; color: #8b949e; line-height: 1.5; margin-bottom: 7px; }
  .fil-org-line.warn { color: #e3b341; }
  .fil-org-pick { display: flex; gap: 6px; align-items: stretch; }
  .fil-org-pick .fil-select { flex: 1 1 auto; }
  .fil-org-pick .fil-btn { flex: 0 0 auto; }
  .fil-card.ok { border-left-color: #3fb950; opacity: .8; }
  .fil-arrow { color: #58a6ff; }

  /* T58: the RECEIPTS tab. It deliberately reuses the filing tab's whole visual vocabulary — the
     same .fil-drop, .fil-card, .fil-input, .fil-btn, .fil-sig, .fil-note — because it is the same
     clerk making the same kind of proposal onto the same Approvals card, and a second visual
     language would imply a second approval mechanism. Only what is genuinely NEW to booking money
     gets its own rules below.

     MOBILE-FIRST, and for a real reason rather than a preference: a receipt is read standing at a
     counter with a phone in one hand. So the field grid is ONE column by default and only splits
     into two where there is width for it, the money field is large and right-aligned with a numeric
     keypad, and every control clears 34px. */
  .rcp-grid { display: grid; grid-template-columns: 1fr; gap: 0 8px; }
  @media (min-width: 420px) { .rcp-grid { grid-template-columns: 1fr 1fr; } }
  .rcp-grid .rcp-wide { grid-column: 1 / -1; }
  /* The amount. It is the field the entry cannot exist without, so it is the biggest thing on the
     card and it is monospace + right-aligned, which is how money is read down a column. */
  .rcp-money { font-size: 15px; font-weight: 700; text-align: right; letter-spacing: .02em; }
  .rcp-money.miss { border-color: #f85149; color: #ff9a95; }
  /* The "I am not guessing this" line. One per field the clerk refuses to invent — the entity most
     often. Amber rather than red: it is a question, not an error. */
  .rcp-ask { display: flex; gap: 6px; align-items: flex-start; margin-top: 6px; font-size: 10.5px;
             line-height: 1.5; color: #e3b341; background: #1a1710; border: 1px solid #9e6a03;
             border-radius: 6px; padding: 5px 7px; }
  .rcp-ask.hard { color: #ff9a95; background: #2b1414; border-color: #b62324; }
  .rcp-ask-ic { flex: 0 0 auto; }
  /* The entity picker. Three buttons rather than a <select>, because there are exactly three, it is
     the field most often corrected, and a thumb hits a 34px button faster than it works a dropdown. */
  .rcp-ents { display: flex; gap: 5px; }
  .rcp-ent { flex: 1 1 0; min-height: 34px; border-radius: 6px; cursor: pointer; font-size: 11.5px;
             font-weight: 600; border: 1px solid #30363d; background: #0d1117; color: #8b949e; }
  .rcp-ent:hover { border-color: #4a5568; color: #adbac7; }
  .rcp-ent.on { background: #0c2b22; border-color: #10b981; color: #6ee7b7; }
  /* A booked entry in the "Recently booked" list: money leads, then vendor, then the entity chip. */
  .rcp-entry { display: flex; align-items: baseline; gap: 7px; font-size: 11.5px; color: #adbac7;
               padding: 5px 7px; background: #0d1117; border: 1px solid #21262d; border-radius: 6px; }
  .rcp-amt { font-weight: 700; color: #e6edf3; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  .rcp-vendor { overflow-wrap: anywhere; }
  .rcp-chip { margin-left: auto; font-size: 9.5px; font-weight: 700; letter-spacing: .04em;
              text-transform: uppercase; padding: 1px 5px; border-radius: 4px; white-space: nowrap;
              background: #161b22; border: 1px solid #30363d; color: #8b949e; }
  .rcp-chip.none { color: #e3b341; border-color: #9e6a03; }
  .rcp-when { font-size: 10px; color: #6e7681; white-space: nowrap; }

  /* CLERK-SCAN: the mobile document scanner that sits ON TOP of the drop zone above.
     Two rules drive every size in here:
       · one-handed. Every control a thumb has to hit while holding a phone over a piece of paper
         is at least 56px tall and lives in the BOTTOM third of the screen, because the top of a
         6" phone is out of reach of the thumb holding it.
       · the paper is the interface. The camera fills the screen edge to edge and every control
         floats over it; a scanner that puts chrome around a small preview makes people frame the
         page badly, and a badly framed page is the one thing the corner detector cannot fix. */
  .fil-actions { display: flex; gap: 8px; margin-bottom: 8px; }
  .fil-scan { flex: 1 1 auto; display: none; align-items: center; justify-content: center; gap: 8px;
              min-height: 56px; border-radius: 12px; border: 1px solid #10b981; background: #0c2b22;
              color: #6ee7b7; font-size: 15px; font-weight: 700; cursor: pointer; }
  .fil-scan.on { display: flex; }
  .fil-scan:hover { background: #0f3a2e; color: #a7f3d0; }
  .fil-scan-ic { font-size: 19px; }

  .scan-overlay { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #000;
                  flex-direction: column; touch-action: none; overscroll-behavior: contain; }
  .scan-overlay.on { display: flex; }
  /* On a phone the sheet IS the screen; the desktop block at the bottom turns it into a panel. */
  .scan-sheet { flex: 1 1 auto; min-height: 0; width: 100%; display: flex; flex-direction: column; }
  .scan-head { flex: 0 0 auto; display: flex; align-items: center; gap: 10px; padding: 10px 12px;
               padding-top: calc(10px + env(safe-area-inset-top)); background: rgba(13,17,23,.86);
               color: #e6edf3; font-size: 13px; font-weight: 600; }
  .scan-head .scan-x { margin-left: auto; width: 44px; height: 44px; border-radius: 50%; border: 0;
                       background: rgba(48,54,61,.8); color: #e6edf3; font-size: 18px; cursor: pointer; }
  .scan-pagecount { font-size: 11px; font-weight: 600; color: #6ee7b7; background: #0c2b22;
                    border: 1px solid #10b981; border-radius: 999px; padding: 3px 9px; }
  /* The privacy line again, in the viewfinder. It is repeated here on purpose: this is the moment
     the camera is actually on, and it is the only moment where "does this photo leave the phone"
     is a live question. It never does — the answer is in front of the lens. */
  .scan-private { flex: 0 0 auto; font-size: 10.5px; color: #6ee7b7; background: rgba(12,43,34,.85);
                  padding: 5px 12px; text-align: center; line-height: 1.4; }
  .scan-stage { flex: 1 1 auto; position: relative; min-height: 0; display: flex;
                align-items: center; justify-content: center; overflow: hidden; }
  .scan-stage video, .scan-stage canvas { max-width: 100%; max-height: 100%; display: block; }
  #scan-video { width: 100%; height: 100%; object-fit: cover; }
  #scan-live { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
  #scan-edit { touch-action: none; }
  .scan-hint { position: absolute; left: 0; right: 0; top: 12px; text-align: center; font-size: 12px;
               color: #e6edf3; text-shadow: 0 1px 4px #000; pointer-events: none; padding: 0 16px; }
  .scan-err { position: absolute; left: 16px; right: 16px; top: 50%; transform: translateY(-50%);
              background: #2b1414; border: 1px solid #b62324; color: #ff9a95; border-radius: 10px;
              padding: 14px; font-size: 13px; line-height: 1.55; text-align: center; }
  /* The controls: bottom third, thumb-sized, high contrast against whatever the camera sees. */
  .scan-controls { flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
                   gap: 12px; padding: 14px 12px; padding-bottom: calc(14px + env(safe-area-inset-bottom));
                   background: rgba(13,17,23,.92); flex-wrap: wrap; }
  .scan-btn { min-height: 56px; min-width: 96px; flex: 1 1 auto; border-radius: 12px; padding: 0 16px;
              font-size: 15px; font-weight: 700; cursor: pointer; border: 1px solid #30363d;
              background: #21262d; color: #e6edf3; }
  .scan-btn:disabled { opacity: .5; cursor: default; }
  .scan-btn.go { background: #0c2b22; border-color: #10b981; color: #6ee7b7; }
  .scan-btn.go:hover:not(:disabled) { background: #0f3a2e; color: #a7f3d0; }
  /* The shutter. Round, 76px, centred — the one control that must be hittable without looking. */
  .scan-shutter { flex: 0 0 auto; width: 76px; height: 76px; border-radius: 50%; border: 4px solid #fff;
                  background: #10b981; color: #fff; font-size: 26px; cursor: pointer; }
  .scan-shutter:disabled { opacity: .55; }
  .scan-modes { display: flex; gap: 0; border: 1px solid #30363d; border-radius: 12px; overflow: hidden;
                flex: 0 0 auto; }
  .scan-mode { min-height: 56px; padding: 0 18px; border: 0; background: #161b22; color: #8b949e;
               font-size: 14px; font-weight: 600; cursor: pointer; }
  .scan-mode.on { background: #0c2b22; color: #6ee7b7; }
  .scan-size { flex: 1 1 100%; text-align: center; font-size: 11.5px; color: #8b949e; line-height: 1.5; }
  .scan-size b { color: #6ee7b7; }
  .scan-thumbs { display: flex; gap: 6px; overflow-x: auto; padding: 2px 0; flex: 1 1 100%; }
  .scan-thumb { flex: 0 0 auto; width: 42px; height: 56px; border-radius: 5px; border: 1px solid #30363d;
                background: #fff; object-fit: cover; }
  /* Desktop: the scanner still works off a webcam, but the drop zone above is the primary path and
     the sheet is a centred panel rather than a full-bleed phone viewfinder. */
  @media (min-width: 760px) {
    .scan-overlay { padding: 24px; background: rgba(0,0,0,.75); }
    .scan-overlay.on { display: flex; align-items: center; }
    .scan-sheet { width: 100%; max-width: 620px; margin: 0 auto; max-height: 100%;
                  display: flex; flex-direction: column; background: #0d1117;
                  border: 1px solid #30363d; border-radius: 14px; overflow: hidden; }
  }

  /* WO-ACCOUNTS-CARD (task T35): the right-rail Accounts tab. Each connected Google account is a
     roomy, spaced card — avatar + name + email, then a readable Gmail/Calendar/Drive chip row with
     status dots. The desk's bound/primary (★) account is accented blue. The pane uses the vertical
     whitespace: cards stack at the top with gaps, the Browse folders / Add account actions pin to
     the bottom (margin-top:auto) so the empty area is filled instead of cramped at the top. */
  .rail-accounts-content { display: flex; flex-direction: column; gap: 12px; flex: 1 1 auto;
                           min-height: 0; overflow-y: auto; padding: 2px; }
  .rail-acct { background: #0d1117; border: 1px solid #21262d; border-radius: 10px;
               padding: 14px; display: flex; flex-direction: column; gap: 12px;
               transition: border-color .15s ease; }
  .rail-acct:hover { border-color: #30363d; }
  /* The bound/primary account is the one a tool call lands on by default — accent it blue. */
  .rail-acct.primary { background: #0d1f3a; border-color: #1f6feb;
                       box-shadow: inset 0 0 0 1px rgba(31,111,235,.25); }
  .rail-acct-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
  .rail-acct-av { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
                  background: #21262d; color: #8b949e; display: flex; align-items: center;
                  justify-content: center; font-size: 14px; font-weight: 700; }
  .rail-acct.primary .rail-acct-av { background: #1f6feb; color: #fff; }
  .rail-acct-id { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .rail-acct-name { font-size: 13px; font-weight: 600; color: #e6edf3; display: flex;
                    align-items: center; gap: 6px; min-width: 0; }
  .rail-acct.primary .rail-acct-name { color: #58a6ff; }
  .rail-acct-name .rail-acct-label { white-space: nowrap; overflow: hidden;
                                     text-overflow: ellipsis; min-width: 0; }
  .rail-acct-star { color: #58a6ff; font-size: 12px; flex: 0 0 auto; }
  .rail-acct-kind { font-size: 9.5px; text-transform: uppercase; letter-spacing: .04em;
                    color: #6e7681; border: 1px solid #21262d; border-radius: 4px;
                    padding: 1px 5px; flex: 0 0 auto; }
  .rail-acct.primary .rail-acct-kind { color: #79c0ff; border-color: #1f3a6a; }
  .rail-acct-email { font-size: 11.5px; color: #8b949e; white-space: nowrap; overflow: hidden;
                     text-overflow: ellipsis; }
  .rail-acct-services { display: flex; flex-wrap: wrap; gap: 8px; }
  .rail-acct-svc { display: inline-flex; align-items: center; gap: 6px; background: #161b22;
                   border: 1px solid #21262d; border-radius: 8px; padding: 6px 9px; font-size: 11px;
                   color: #c9d1d9; text-decoration: none; line-height: 1; }
  .rail-acct-svc:hover { border-color: #388bfd; background: #1c2129; }
  .rail-acct.primary .rail-acct-svc { background: rgba(13,17,23,.5); border-color: #1f3a6a; }
  .rail-acct-svc .svc-ic { font-size: 13px; }
  .rail-acct-svc .svc-ic.gmail { color: #ea4335; }
  .rail-acct-svc .svc-ic.calendar { color: #4285f4; }
  .rail-acct-svc .svc-ic.drive { color: #34a853; }
  .rail-acct-svc .svc-label { font-weight: 500; }
  .rail-acct-svc .vs-dot { width: 7px; height: 7px; }
  /* Actions pin to the bottom of the pane, filling the vertical whitespace below the cards. */
  .rail-acct-actions { display: flex; gap: 8px; margin-top: auto; padding-top: 6px; flex-wrap: wrap; }
  .rail-acct-actions button { flex: 1 1 0; background: #21262d; border: 1px solid #30363d;
                              color: #e6edf3; border-radius: 8px; padding: 9px 10px; font-size: 12px;
                              font-weight: 500; cursor: pointer; }
  .rail-acct-actions button:hover { background: #30363d; border-color: #58a6ff; }

  /* WO-CAP-BANK-READ (task T36): linked BANKS, in the same Accounts pane, as their own green-tinted
     card family so a bank is never mistaken for a Google account at a glance. Everything here is
     READ-ONLY status — institution, the accounts' last 4, link health — plus the one destructive
     action Brandon owns: Disconnect. There is no amount, no transfer control, and nothing an agent
     can click: money movement does not exist anywhere in this build. */
  .rail-bank-head { display: flex; align-items: center; gap: 8px; font-size: 11px;
                    text-transform: uppercase; letter-spacing: .05em; color: #6e7681;
                    margin-top: 4px; }
  .rail-bank-head .rail-bank-ro { text-transform: none; letter-spacing: 0; font-size: 10.5px;
                                  color: #3fb950; border: 1px solid #1b4721; border-radius: 4px;
                                  padding: 1px 6px; }
  .rail-bank { background: #0d1117; border: 1px solid #21262d; border-radius: 10px; padding: 14px;
               display: flex; flex-direction: column; gap: 10px; }
  .rail-bank:hover { border-color: #2ea043; }
  .rail-bank-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
  .rail-bank-av { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; background: #12261a;
                  color: #3fb950; display: flex; align-items: center; justify-content: center;
                  font-size: 15px; }
  .rail-bank-id { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .rail-bank-name { font-size: 13px; font-weight: 600; color: #e6edf3; white-space: nowrap;
                    overflow: hidden; text-overflow: ellipsis; }
  .rail-bank-sub { font-size: 11.5px; color: #8b949e; white-space: nowrap; overflow: hidden;
                   text-overflow: ellipsis; }
  .rail-bank-accts { display: flex; flex-wrap: wrap; gap: 6px; }
  .rail-bank-acct { display: inline-flex; align-items: center; gap: 6px; background: #161b22;
                    border: 1px solid #21262d; border-radius: 8px; padding: 5px 8px; font-size: 11px;
                    color: #c9d1d9; line-height: 1; }
  .rail-bank-acct .mask { color: #6e7681; font-variant-numeric: tabular-nums; }
  .rail-bank-warn { font-size: 11.5px; color: #f0883e; }
  .rail-bank-acts { display: flex; justify-content: flex-end; }
  .rail-bank-acts button { background: transparent; border: 1px solid #30363d; color: #8b949e;
                           border-radius: 8px; padding: 6px 10px; font-size: 11.5px; cursor: pointer; }
  .rail-bank-acts button:hover { border-color: #f85149; color: #f85149; }
  .rail-bank-empty { font-size: 11.5px; color: #6e7681; padding: 2px 0 0; }

  /* Mobile drawer trigger: hidden on desktop */
  .tool-rail-toggle { display: none; }
  /* Mobile drawer backdrop */
  .tool-rail-backdrop { display: none; }

  /* Selected desk tints the chat panel its TYPE colour (task 19, Part C): background +
     header bar shift so it's obvious which desk's chat is open (Manager → green chat bg). */
  .chatpanel[data-type="manager"]   { background:#0e1a13; border-color:#1c5330; }
  .chatpanel[data-type="coder"]     { background:#0d1622; border-color:#1f3a5f; }
  .chatpanel[data-type="routine"]   { background:#150e22; border-color:#3a2a5f; }
  .chatpanel[data-type="marketing"] { background:#1a1608; border-color:#574a1a; }
  .chatpanel[data-type="office"]    { background:#1a0f10; border-color:#57282a; }
  .chatpanel[data-type="manager"] h2,   .chatpanel[data-type="manager"] .chatform   { border-color:#1c5330; }
  .chatpanel[data-type="coder"] h2,     .chatpanel[data-type="coder"] .chatform     { border-color:#1f3a5f; }
  .chatpanel[data-type="routine"] h2,   .chatpanel[data-type="routine"] .chatform   { border-color:#3a2a5f; }
  .chatpanel[data-type="marketing"] h2, .chatpanel[data-type="marketing"] .chatform { border-color:#574a1a; }
  .chatpanel[data-type="office"] h2,    .chatpanel[data-type="office"] .chatform    { border-color:#57282a; }
  .chatpanel[data-type="manager"] h2   { color:#56d364; }
  .chatpanel[data-type="coder"] h2     { color:#79c0ff; }
  .chatpanel[data-type="routine"] h2   { color:#c8a6ff; }
  .chatpanel[data-type="marketing"] h2 { color:#e3b341; }
  .chatpanel[data-type="office"] h2    { color:#ff7b72; }
  /* WO-25: Specialists chat tint + per-desk custom-colour heading for the selected specialist. */
  .chatpanel[data-type="specialists"]    { background:#12161b; border-color:#2b333b; }
  .chatpanel[data-type="specialists"] h2, .chatpanel[data-type="specialists"] .chatform { border-color:#2b333b; }
  .chatpanel[data-type="specialists"] h2 { color:#adbac7; }

  /* ── WO-RECORD-CARDS · shared desk record cards ───────────────────────────
     ONE stylesheet for the shared, schema-driven card framework. Each entity schema carries a
     class PREFIX (`css`): the Mechanic's vehicles keep `vehicle-*` (so every shipped rule and
     selector below matches exactly what it always matched — zero regression), the generic desk
     records use `record-*`. The rules are GROUPED rather than duplicated, so the two prefixes can
     never drift visually. Mobile-first + dark theme, same as the prototype. */
  .mechanic-garage { display: flex; flex-direction: column; gap: 12px; padding: 16px 0; }

  /* Vehicle card - compact state */
  .vehicle-card,
  .record-card {
    background: #161b22;
    border: 1px solid #30363d;
    border-radius: 8px;
    cursor: pointer;
    transition: border-color 0.2s ease;
    overflow: hidden;
  }
  .vehicle-card:hover,
  .record-card:hover { border-color: #58a6ff; }

  /* Card header - always visible */
  .vehicle-header,
  .record-header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    min-height: 56px;
  }

  /* Vehicle icon */
  .vehicle-icon,
  .record-icon {
    flex: 0 0 auto;
    width: 40px;
    height: 40px;
    background: #21262d;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
  }

  /* Vehicle info */
  .vehicle-info,
  .record-info { flex: 1; min-width: 0; }
  .vehicle-name,
  .record-name { font-size: 14px; font-weight: 600; color: #e6edf3; margin: 0 0 2px 0; }
  .vehicle-subtitle,
  .record-subtitle { font-size: 12px; color: #8b949e; margin: 0; }

  /* Due count chip */
  .vehicle-due-chip,
  .record-due-chip {
    flex: 0 0 auto;
    font-size: 11px;
    padding: 3px 8px;
    border-radius: 12px;
    font-weight: 600;
    background: #1f2937;
    color: #9ca3af;
    border: 1px solid #30363d;
  }
  /* TIB-010: a past-due chip is amber like due-soon, not red. Nothing in this app turns red for
     being late — red is for a thing that is broken, and a date that slipped is not that. */
  .vehicle-due-chip.past-due,
  .record-due-chip.past-due { background: #3a2a1a; color: #d29922; border-color: #d29922; }
  .vehicle-due-chip.due-soon,
  .record-due-chip.due-soon { background: #3a2a1a; color: #d29922; border-color: #d29922; }

  /* Expandable content - tabs + panes */
  .vehicle-detail,
  .record-detail {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.25s ease;
    border-top: 1px solid transparent;
  }
  .vehicle-card.expanded .vehicle-detail,
  .record-card.expanded .record-detail { max-height: 800px; border-top-color: #21262d; }

  /* Tabs container */
  .vehicle-tabs,
  .record-tabs {
    display: flex;
    gap: 2px;
    border-bottom: 1px solid #30363d;
    background: #0d1117;
  }
  .vehicle-tab,
  .record-tab {
    flex: 0 0 auto;
    padding: 10px 16px;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    color: #8b949e;
    font-size: 13px;
    cursor: pointer;
    white-space: nowrap;
    min-height: 40px;
    min-width: 44px;
    touch-action: manipulation;
  }
  .vehicle-tab:hover,
  .record-tab:hover { color: #c9d1d9; }
  .vehicle-tab.active,
  .record-tab.active { color: #e6edf3; border-bottom-color: #58a6ff; }

  /* Tab panes */
  .vehicle-pane,
  .record-pane { display: none; padding: 16px; }
  .vehicle-pane.active,
  .record-pane.active { display: block; }

  /* Details pane */
  .vehicle-detail-row,
  .record-detail-row {
    display: flex;
    gap: 12px;
    padding: 8px 0;
    border-bottom: 1px solid #161b22;
    font-size: 13px;
  }
  .vehicle-detail-row:last-child,
  .record-detail-row:last-child { border-bottom: none; }
  .vehicle-detail-key,
  .record-detail-key { color: #8b949e; min-width: 100px; flex: 0 0 auto; }
  .vehicle-detail-value,
  .record-detail-value { color: #c9d1d9; flex: 1; }

  /* Maintenance list */
  .maintenance-list { display: flex; flex-direction: column; gap: 8px; }
  .maintenance-entry {
    background: #0d1117;
    border: 1px solid #21262d;
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 12px;
  }
  .maintenance-header { display: flex; justify-content: space-between; margin-bottom: 4px; }
  .maintenance-what { font-weight: 600; color: #e6edf3; }
  .maintenance-date { color: #8b949e; }
  .maintenance-details { display: flex; gap: 12px; color: #8b949e; margin-top: 4px; }
  .maintenance-notes { color: #c9d1d9; font-style: italic; margin-top: 4px; }

  /* Due reminders list */
  .due-list { display: flex; flex-direction: column; gap: 8px; }
  .due-entry {
    background: #0d1117;
    border: 1px solid #21262d;
    border-radius: 6px;
    padding: 10px 12px;
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    transition: background 0.2s;
  }
  .due-entry:hover { background: #161b22; }
  .due-entry.completed { opacity: 0.6; }
  .due-entry.completed .due-label { text-decoration: line-through; }
  .due-checkbox {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border: 2px solid #6e7681;
    border-radius: 3px;
    cursor: pointer;
  }
  .due-entry.completed .due-checkbox {
    background: #3fb950;
    border-color: #3fb950;
  }
  .due-checkbox::after {
    content: '✓';
    display: none;
    color: #fff;
    font-size: 12px;
    line-height: 14px;
    text-align: center;
  }
  .due-entry.completed .due-checkbox::after { display: block; }
  .due-content { flex: 1; min-width: 0; }
  .due-label { font-size: 13px; color: #e6edf3; margin: 0 0 2px 0; }
  .due-date { font-size: 11px; color: #8b949e; margin: 0; }
  .due-entry.past-due { border-color: #d29922; }
  .due-entry.past-due .due-date { color: #d29922; font-weight: 600; }
  .due-entry.due-soon { border-color: #d29922; }
  .due-entry.due-soon .due-date { color: #d29922; font-weight: 600; }
  .due-delete {
    flex: 0 0 auto;
    background: none;
    border: none;
    color: #8b949e;
    cursor: pointer;
    padding: 4px;
    min-width: 32px;
    min-height: 32px;
  }
  .due-delete:hover { color: #f85149; }

  /* WO-RECORD-CARDS: the checklist flavour of the middle tab (the Accountant's Documents —
     "Still need" first, then "Have"). It reuses the due-entry row shape above so a checklist item
     and a deadline are the same touch target on a phone; only the checked state differs (a have
     item is ticked, NOT struck through — it is a possession, not a completed chore). */
  .check-list { display: flex; flex-direction: column; gap: 8px; }
  .check-entry {
    background: #0d1117;
    border: 1px solid #21262d;
    border-radius: 6px;
    padding: 10px 12px;
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    transition: background 0.2s;
  }
  .check-entry:hover { background: #161b22; }
  .check-entry.have .due-checkbox { background: #3fb950; border-color: #3fb950; }
  .check-entry.have .due-checkbox::after { display: block; }
  .check-entry.have .due-date { color: #3fb950; }
  .record-section-title {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #8b949e;
    margin: 4px 0 2px 0;
  }
  .record-section-title:not(:first-child) { margin-top: 12px; }

  /* The strip-level "+ Add <entity>" button (schema-declared; the Mechanic has none because its
     cards come from chat capture). Sits under the cards rather than stretching like a tab-pane
     action, and stays a 36px+ touch target on a phone. */
  .record-add-btn { flex: 0 0 auto; align-self: flex-start; margin: 4px 0 0 0; }

  /* Action buttons */
  .vehicle-actions,
  .record-actions { display: flex; gap: 8px; margin-top: 12px; }
  .vehicle-btn,
  .record-btn {
    flex: 1;
    background: #21262d;
    color: #c9d1d9;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 8px 12px;
    font-size: 12px;
    cursor: pointer;
    min-height: 36px;
    touch-action: manipulation;
  }
  .vehicle-btn:hover,
  .record-btn:hover { background: #30363d; }
  .vehicle-btn:disabled,
  .record-btn:disabled { opacity: 0.5; cursor: default; }

  /* Empty state (per-entity class, so a desk can word its own "nothing here yet") */
  .mechanic-garage-empty,
  .desk-records-empty {
    text-align: center;
    padding: 32px 16px;
    color: #6e7681;
    font-size: 13px;
  }

  .chatpanel[data-color="white"] h2 { color:#ffffff; }
  .chatpanel[data-color="brown"] h2 { color:#c88a5e; }
  .chatpanel[data-color="teal"] h2  { color:#5eead4; }
  .chatpanel[data-color="rose"] h2  { color:#f9a8d4; }
  .chatpanel[data-color="slate"] h2 { color:#cbd5e1; }
  .chatpanel[data-color="violet"] h2 { color:#c4b5fd; }
  .chatpanel[data-color="gold"] h2 { color:#f0c045; }
  .chatpanel[data-color="emerald"] h2 { color:#6ee7b7; }
  .chatpanel[data-color="cyan"] h2 { color:#22d3ee; }
  .chatpanel[data-color="glm"] h2 { color:#f87171; }
  .chatpanel[data-color="kimi"] h2 { color:#f87171; }
  .chatpanel[data-color="claude"] h2 { color:#ffa657; }

  /* Bot-response meta: provider + model PILLS (task 19, Part A) + escalate tag. */
  .bubble .bmeta { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin-top: 5px; }
  .bubble .bmeta .mpill, .bubble .bmeta .prov { font-size: 10px; }
  .bmeta-sub { font-size: 10px; color: #8b949e; }
  /* IB-139 · the receipt (lane/tokens/cost/brief/remembered) is honest but it is also five chips on
     every single reply. `.bmeta-rest` carries them; `display:contents` makes the wrapper invisible
     to layout so wide screens are pixel-identical to before this — the toggle button only exists at
     phone width (below). */
  .bmeta-rest { display: contents; }
  .bmeta-toggle { display: none; }
  .tag-esc { font-size: 10px; color: #56d364; font-weight: 600; }
  /* WO-20: per-message token counter + cost chips next to the model pill */
  .tok { font-size: 10px; color: #8b949e; font-variant-numeric: tabular-nums; }
  .tok-est { color: #d29922; font-weight: 600; }
  .tok-cost { font-size: 10px; padding: 1px 6px; border-radius: 20px; font-weight: 600; font-variant-numeric: tabular-nums; }
  .tok-metered { background: #3a2a12; color: #f0883e; }   /* real $ — metered draw */
  .tok-quota { background: #12261a; color: #56d364; }     /* Claude Max plan quota — not $ */
  /* TIB-042 · the footer's lane chip + the two honest absences. `tok-unknown` is deliberately plain
     and grey: an unavailable number should look like a missing number, never like a small one. */
  .tok-lane { font-size: 10px; padding: 1px 6px; border-radius: 20px; font-weight: 600; background: #161b22; color: #8b949e; }
  .tok-lane.lane-api { background: #3a2a12; color: #f0883e; }
  .tok-lane.lane-box { background: #12261a; color: #56d364; }
  .tok-lane.lane-local { background: #161b22; color: #58a6ff; }
  .tok-lane.lane-unknown { background: #21262d; color: #8b949e; }
  .tok-unknown { color: #8b949e; font-style: italic; background: none; }
  .tok-cached { font-size: 10px; color: #6e7681; font-variant-numeric: tabular-nums; }
  /* WO-LABELS: the desk's resolved model pill, shown next to the chat header title */
  #chat-title-model { margin-left: 8px; vertical-align: middle; }
  /* WO-MOBILE-CHAT-HEADER-2 (fix/mobile-chat-header): the short desk-name title is phone-only —
     hidden on every width by default; the ≤430px block reveals it (and hides the full title +
     model pill). Keeps desktop + tablet (≥431px) on the unchanged full header. */
  #chat-title-text-mobile { display: none; }
  /* T24 (feat/mobile-chat-header): the slim IB strip is phone-only.
     Base = hidden; revealed only inside a full-screen .chatpanel.on chat by the ≤759px block. */
  .chat-ib-strip { display: none; }
  /* WO-MOBILE-CHAT-HEADER (task 264): the running token total moved OUT of the header row (it was
     crowding chat-title-text/model/attachments-toggle/close on a phone) into its own thin footer
     strip just above the composer, styled as a small pill instead of a floated text label.
     WO-20 originally put it in the header. */
  .chat-footer { padding: 4px 14px 0; display: flex; justify-content: flex-end; }
  .chat-usage { font-size: 10px; font-weight: 400; text-transform: none; letter-spacing: 0;
                color: #8b949e; font-variant-numeric: tabular-nums;
                background: #0d1117; border: 1px solid #21262d; border-radius: 20px; padding: 2px 10px; }
  .chat-usage b { color: #adbac7; font-weight: 600; }
  .prov-subscription { background: #12261a; color: #56d364; }

  /* WO-MOBILE-FIX (task 218): close/back control for the chat panel header. */
  .chat-close {
    float: right;
    margin-left: 8px;
    background: #21262d;
    color: #e6edf3;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 2px 9px;
    font-size: 13px;
    line-height: 1.6;
    cursor: pointer;
    font-weight: 600;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .chat-close:hover { background: #30363d; }

  /* WO-MOBILE-CHAT-HEADER-2 (fix/mobile-chat-header): phone-only ⋯ overflow for the desk chat
     sub-header. The wrap is display:none on EVERY width by default — only the ≤430px block
     reveals it — so desktop/tablet never see a duplicate of the secondary controls. The
     dropdown mirrors the existing header .overflow-menu look (same chips, dividers, 44px rows). */
  .chat-overflow-wrap { position: relative; display: none; }
  .chat-overflow-btn { background: #21262d; color: #e6edf3; border: 1px solid #30363d;
                       border-radius: 6px; cursor: pointer; font-size: 16px; line-height: 1; }
  .chat-overflow-menu { display: none; position: absolute; right: 0; top: calc(100% + 6px); z-index: var(--z-dropdown);
                        flex-direction: column; gap: 4px; min-width: 200px; padding: 6px;
                        background: #161b22; border: 1px solid #30363d; border-radius: 10px;
                        box-shadow: 0 8px 28px rgba(0,0,0,.55); }
  .chat-overflow-menu.on { display: flex; }
  .chat-overflow-menu .chat-ov-item { width: 100%; text-align: left; background: #21262d; color: #e6edf3;
                                      border: 1px solid #30363d; border-radius: 7px; padding: 9px 11px;
                                      font-size: 14px; min-height: 44px; cursor: pointer; }
  .chat-overflow-menu .chat-ov-item:hover { background: #30363d; border-color: #484f58; }
  .chat-overflow-menu .chat-ov-info { font-size: 12px; color: #c9d1d9; padding: 6px 11px;
                                      display: flex; align-items: center; gap: 6px; }
  .chat-overflow-menu .chat-ov-sep { border-top: 1px solid #30363d; margin: 4px 2px; }
  .chat-overflow-menu .chat-ov-empty { font-size: 12px; color: #8b949e; padding: 8px 11px; }

  /* IB-97 · coder status tabs inside the chat panel */
  /* IB-137 · a fixed-height row that never reflows: every tab is flex:1 1 0 so Chat + all six
     coder tabs always sum to exactly the row's width — there is nothing left to overflow, so no
     scroll is needed at 390/375/320px. (overflow-x:auto stays on as a safety net for a fleet
     bigger than six; scrollbar-width:auto, not none, so a genuinely overflowing strip SHOWS that
     it scrolls instead of just cutting the last tab off.) height (not min-height) pins the row so
     a desk starting/stopping — which only ever changes a dot's class — can never move it. */
  .coder-status-tabs { display: flex; gap: 2px; padding: 0 8px; border-bottom: 1px solid #21262d;
                       background: rgba(13,17,23,.5); height: 40px; flex: 0 0 auto;
                       overflow-x: auto; overflow-y: hidden; scrollbar-width: auto; }
  .cst-tab { flex: 1 1 0; min-width: 34px; padding: 0 2px; background: none;
             border: none; border-bottom: 2px solid transparent; color: #8b949e; font-size: 13px;
             font-weight: 600; cursor: pointer; display: flex; align-items: center;
             justify-content: center; gap: 4px; overflow: hidden; white-space: nowrap; }
  .cst-tab.cst-chat { flex: 1.6 1 0; min-width: 44px; }
  .cst-num { overflow: hidden; text-overflow: ellipsis; }
  .cst-tab:hover { color: #c9d1d9; }
  .cst-tab.active { color: #e6edf3; border-bottom-color: #f85149; }
  /* The dot is the ONLY state signal on a coder tab's face — colour, fill AND shape all agree so
     idle/building/needs-attention read without opening the panel or being able to see colour:
     idle is a hollow grey ring, building is a solid pulsing circle, needs-attention is a solid
     diamond (never animated, so it doesn't compete with a genuinely busy desk's motion). */
  .cst-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; box-sizing: border-box;
             border: 1.5px solid #6e7681; background: transparent; }
  .cst-dot.cst-idle { border-color: #6e7681; background: transparent; }
  .cst-dot.cst-building { border-radius: 50%; border-color: #3fb950; background: #3fb950;
                           animation: cc-pulse 1.4s ease-in-out infinite; }
  .cst-dot.cst-attention { border-radius: 2px; transform: rotate(45deg); border-color: #f85149;
                            background: #f85149; }
  .coder-status-pane { flex: 1 1 auto; min-height: 360px; display: none; flex-direction: column;
                       padding: 14px; gap: 10px; overflow: hidden; }
  .coder-status-pane.on { display: flex; }
  .chat-pane { display: block; }
  .chat-pane.off { display: none; }
  .csp-header { font-size: 15px; font-weight: 700; color: #e6edf3; }
  .csp-meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
  .csp-state { font-size: 13px; font-weight: 600; color: #adbac7; }
  .csp-idle { color: #8b949e; font-style: italic; }
  .csp-dead { color: #f85149; font-weight: 700; }
  .csp-working { color: #3fb950; font-weight: 700; }
  .csp-blocked { color: #f85149; font-weight: 700; }
  /* IB-1425 · the Resolve control on a blocked coder's own tab — this is the phone-reachable
     action the coder-card detail sheet's Retry/Approve/Close/Ask-Manager row now hangs off of,
     so it gets the same 44px thumb target every other phone-tap control in this file uses. */
  .csp-resolve-btn { min-height: 44px; padding: 6px 14px; border-radius: 8px; border: 1px solid #f85149;
                      background: #4a1d1d; color: #ff9a92; font-size: 13px; font-weight: 700; }
  .csp-last { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
              color: #c9d1d9; background: #0d1117; border: 1px solid #21262d; border-radius: 8px;
              padding: 10px 12px; white-space: pre-wrap; word-break: break-word; }
  .csp-last:empty { display: none; }
  .csp-last-label { color: #6e7681; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
                    margin-bottom: 4px; }
  .csp-task-id { font-size: 12px; font-weight: 700; color: #58a6ff; }
  .csp-task-title { font-size: 13px; color: #e6edf3; }
  .csp-branch { font-size: 12px; color: #adbac7; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  .csp-log { flex: 1 1 auto; min-height: 120px; background: #0d1117; border: 1px solid #21262d;
             border-radius: 8px; padding: 0; overflow: hidden; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
             font-size: 12px; line-height: 1.5; white-space: pre-wrap; }
  .csp-log-empty { color: #6e7681; font-style: italic; padding: 10px 12px; }
  .csp-log-line { margin: 1px 0; }

  /* IB-144 · coder tab = terminal you can talk to */
  .csp-terminal-wrap { flex: 1 1 auto; min-height: 120px; position: relative; display: flex;
                       flex-direction: column; background: #0d1117; border: 1px solid #21262d;
                       border-radius: 8px; overflow: hidden; }
  .csp-terminal { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
                  padding: 10px 12px; background: #010409; color: #c9d1d9;
                  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
                  font-size: 13px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
                  -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
  .csp-line { display: block; padding: 1px 0 1px 10px; text-indent: -10px;
              border-left: 3px solid transparent; }
  .csp-line.csp-line-user { border-left-color: #58a6ff; color: #e6edf3; }
  .csp-line.csp-line-error { border-left-color: #f85149; color: #f85149; font-weight: 700; }
  .csp-line.csp-line-warn { border-left-color: #d29922; color: #d29922; }
  .csp-line.csp-line-info { border-left-color: #3fb950; color: #3fb950; }
  .csp-truncated-notice { color: #d29922; font-style: italic; padding: 4px 12px; border-bottom: 1px dashed #30363d; }
  .csp-jump-live { display: none; position: absolute; right: 14px; bottom: 14px;
                   background: #1f6feb; color: #fff; border: none; border-radius: 16px;
                   padding: 7px 14px; font: inherit; font-size: 12px; font-weight: 700;
                   cursor: pointer; z-index: var(--z-raised); box-shadow: 0 2px 8px rgba(0,0,0,.4); }
  .csp-jump-live.show { display: inline-block; }
  .csp-jump-live:hover { background: #388bfd; }
  .csp-composer { flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px; }
  .csp-composer-status { min-height: 18px; font-size: 12px; color: #8b949e; }
  .csp-composer-status.csp-status-error { color: #f85149; }
  .csp-composer-status.csp-status-ok { color: #3fb950; }
  .csp-form { display: flex; align-items: flex-end; gap: 8px;
              padding: 8px 10px; background: #161b22; border: 1px solid #30363d; border-radius: 8px; }
  .csp-form textarea { flex: 1 1 auto; min-width: 0; background: #0d1117; color: #e6edf3;
                       border: 1px solid #30363d; border-radius: 6px; padding: 9px 11px;
                       font: inherit; font-size: 14px; line-height: 1.4; resize: none;
                       min-height: 44px; max-height: 154px; overflow-y: hidden; }
  .csp-form textarea:focus { outline: none; border-color: #58a6ff; }
  .csp-form button { flex: 0 0 auto; background: #1f6feb; color: #fff; border: none;
                     border-radius: 6px; padding: 0 16px; font: inherit; font-size: 14px;
                     font-weight: 600; cursor: pointer; min-height: 44px; min-width: 64px; }
  .csp-form button:hover { background: #388bfd; }
  .csp-form button:disabled { background: #21262d; color: #6e7681; cursor: default; }

  /* WO-ATTACHMENT-VIEWER: Attachments panel styles.
     WO-DESK-INFO-SIDEBAR (task T33): the .attachments-toggle BUTTON styles that used to sit here
     are gone — that button (and New chat, which borrowed the same class) moved into the rail's
     Info tab and is styled as an .info-btn there. Only the panel itself still lives in the chat. */
  .attachments-panel {
    border-bottom: 1px solid #21262d;
    background: #0d1117;
  }
  .attachments-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 14px;
    border-bottom: 1px solid #1b2129;
  }
  .attachments-title {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #8b949e;
  }
  .attachments-close {
    background: none;
    border: none;
    color: #8b949e;
    font-size: 18px;
    cursor: pointer;
    padding: 0;
    line-height: 1;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .attachments-close:hover { color: #e6edf3; }

  .attachments-loading, .attachments-empty, .attachments-error {
    padding: 20px 14px;
    text-align: center;
    font-size: 12px;
    color: #6e7681;
  }
  .attachments-error { color: #f85149; }

  .attachments-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
    padding: 12px 14px;
    max-height: 200px;
    overflow-y: auto;
  }
  /* Responsive: 3 columns on tablet, 2 on mobile */
  @media (max-width: 1024px) {
    .attachments-grid { grid-template-columns: repeat(3, 1fr); }
  }
  @media (max-width: 760px) {
    .attachments-grid { grid-template-columns: repeat(2, 1fr); }
  }

  /* WO-VA-COMMAND-CARD (task 262): persistent VA status card pinned above the VA Advisor's
     chat log — deadline pill, combined-rating meter, collapsible ratings table, next
     appointment, and a claims/actions checklist. Dark-theme palette reuses the same
     green/amber/red the task badges already use (.b-done/.b-dep-merged/.b-blocked). */
  .va-status-card {
    border-bottom: 1px solid #21262d;
    background: #0d1117;
    padding: 12px 14px;
    font-size: 12px;
    color: #c9d1d9;
  }
  .va-card-head {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #8b949e;
    margin-bottom: 8px;
  }
  .va-row { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; flex-wrap: wrap; }
  .va-pill { font-size: 11px; padding: 2px 8px; border-radius: 20px; font-weight: 600; white-space: nowrap; }
  .va-pill-green { background: #133a1f; color: #3fb950; }
  .va-pill-amber { background: #3a2f0d; color: #d29922; }
  .va-pill-red   { background: #3a1a1a; color: #f85149; }
  .va-pill-gray  { background: #1f2937; color: #9ca3af; }
  .va-deadline-label { color: #8b949e; }
  .va-note { color: #6e7681; font-size: 11px; margin: 2px 0 8px; }
  .va-rating-row { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
  .va-rating-nums { font-weight: 600; color: #e6edf3; white-space: nowrap; }
  .va-meter {
    flex: 1 1 auto; height: 6px; border-radius: 4px; background: #21262d; overflow: hidden; min-width: 60px;
  }
  .va-meter-fill { height: 100%; background: #3fb950; border-radius: 4px; }
  .va-toggle {
    background: none; border: none; color: #58a6ff; font-size: 11px; cursor: pointer;
    padding: 2px 0; margin-bottom: 4px; text-align: left;
  }
  .va-toggle:hover { text-decoration: underline; }
  .va-conditions { margin: 0 0 8px; }
  .va-cond-row { display: flex; justify-content: space-between; gap: 8px; padding: 2px 0; color: #c9d1d9; }
  .va-section-label {
    font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
    color: #8b949e; margin: 8px 0 4px;
  }
  .va-appt { padding: 4px 0; }
  .va-appt-main { color: #e6edf3; }
  .va-appt-sub { color: #8b949e; font-size: 11px; }
  .va-appts-more { margin: 0 0 4px; }
  .va-empty { color: #6e7681; font-style: italic; }
  .va-checklist { display: flex; flex-direction: column; gap: 3px; }
  .va-check-row { display: flex; align-items: flex-start; gap: 6px; }
  .va-check-dot {
    display: inline-flex; align-items: center; justify-content: center;
    width: 14px; height: 14px; border-radius: 3px; border: 1px solid #30363d;
    font-size: 10px; color: #3fb950; flex: 0 0 auto; margin-top: 1px;
  }
  .va-check-row.va-done { color: #6e7681; text-decoration: line-through; }
  .va-check-row.va-done .va-check-dot { border-color: #3fb950; }
  .va-footer { color: #6e7681; font-size: 10px; margin-top: 8px; }

  .attach-item {
    display: flex;
    flex-direction: column;
    gap: 4px;
    cursor: pointer;
    border-radius: 6px;
    padding: 8px;
    background: #161b22;
    border: 1px solid #21262d;
    transition: background .15s, border-color .15s;
  }
  .attach-item:hover { background: #21262d; border-color: #30363d; }

  .attach-thumb {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: 4px;
    background: #0d1117;
  }
  .attach-icon {
    width: 100%;
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 32px;
    color: #8b949e;
    background: #0d1117;
    border-radius: 4px;
  }

  .attach-meta {
    font-size: 10px;
    color: #8b949e;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .attach-name {
    font-weight: 500;
    color: #e6edf3;
  }
  .attach-size {
    color: #6e7681;
  }
  /* IB-1434 · compact by default (mirrors .work-tasks-panel's cap+scroll): an unbounded grid of
     large thumbnails was pushing the Work section below the fold. #files-toggle flips .expanded. */
  #files-panel .body { max-height: 220px; overflow: auto; overscroll-behavior: contain; }
  #files-panel.expanded .body { max-height: none; overflow: visible; }
  /* WO-FILES-LIBRARY-CARD: Per-desk Files card + global Library styles */
  .files-loading, .files-empty, .files-error {
    padding: 20px 14px;
    text-align: center;
    font-size: 12px;
    color: #6e7681;
  }
  .files-error { color: #f85149; }
  .files-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 8px;
    padding: 12px 14px;
  }
  .files-list {
    padding: 12px 14px;
  }
  .file-card {
    display: flex;
    flex-direction: column;
    gap: 4px;
    cursor: pointer;
    border-radius: 6px;
    padding: 8px;
    background: #161b22;
    border: 1px solid #21262d;
    transition: background .15s, border-color .15s;
  }
  .file-card:hover { background: #21262d; border-color: #30363d; }
  .file-thumb {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: 4px;
    background: #0d1117;
  }
  .file-icon {
    width: 100%;
    aspect-ratio: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 32px;
    color: #8b949e;
    background: #0d1117;
    border-radius: 4px;
  }
  .file-meta {
    font-size: 10px;
    color: #8b949e;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .file-name {
    font-weight: 500;
    color: #e6edf3;
  }
  .file-size {
    color: #6e7681;
  }
  .file-date {
    color: #484f58;
  }
  .file-desk {
    font-size: 9px;
    color: #58a6ff;
    margin-top: 2px;
  }
  .file-actions {
    display: flex;
    gap: 4px;
    margin-top: 4px;
  }
  .file-action-btn {
    background: #21262d;
    border: 1px solid #30363d;
    border-radius: 4px;
    padding: 2px 6px;
    font-size: 9px;
    color: #8b949e;
    cursor: pointer;
  }
  .file-action-btn:hover { background: #30363d; color: #e6edf3; }
  /* WO-FILES-LIBRARY-CARD: File count chip in sidebar. WO-184: lives on its own .ln2b line
     now (see agentRow), so it no longer needs margin-left:auto to push right on a shared line. */
  .agent .file-chip {
    display: inline-block;
    font-size: 9px;
    color: #8b949e;
    background: #1b2129;
    border-radius: 10px;
    padding: 1px 5px;
  }
  .agent .ln2b { display: flex; margin-top: 2px; min-width: 0; }
  .agent .file-chip.has-files {
    color: #58a6ff;
    background: #0d2847;
  }
  /* WO-UNREAD-BADGES (task 323): attention-coloured unread dot + count, next to the desk name.
     Small and fixed-size so it never pushes the name into ellipsis on a narrow rail. */
  .agent .unread-badge {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 15px;
    height: 15px;
    padding: 0 4px;
    border-radius: 8px;
    background: #f85149;
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
  }
  /* Total-unread-desks pill on the Agents rail header (req 5, optional summary). */
  .rail h2 .unread-total {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 5px;
    margin-left: 6px;
    border-radius: 9px;
    background: #f85149;
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    vertical-align: middle;
  }
  /* WO-FILES-LIBRARY-CARD: Global Library modal/view */
  .library-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 14px;
    border-bottom: 1px solid #21262d;
  }
  .library-search {
    flex: 1;
    background: #0d1117;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 6px 10px;
    font-size: 12px;
    color: #e6edf3;
  }
  .library-search::placeholder { color: #6e7681; }
  .library-filter {
    display: flex;
    gap: 4px;
  }
  .library-filter-btn {
    background: #21262d;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 4px 8px;
    font-size: 11px;
    color: #8b949e;
    cursor: pointer;
  }
  .library-filter-btn.active {
    background: #1f6feb;
    border-color: #1f6feb;
    color: #fff;
  }
  /* WO-15 sticky-tier bar: the desk's PINNED tier (set by ↑/↓), shown above the composer. */
  .sticky-bar { display: flex; align-items: center; gap: 8px; padding: 6px 14px; font-size: 11px;
    color: #8b949e; border-top: 1px solid #21262d; }
  .sticky-bar .pin { color: #58a6ff; font-weight: 600; }
  .sticky-bar .sticky-clear { margin-left: auto; background: #21262d; color: #e6edf3; border: 1px solid #30363d;
    border-radius: 5px; padding: 2px 8px; cursor: pointer; font-size: 11px; }
  .sticky-bar .sticky-clear:hover { background: #30363d; }
  /* up/down re-run controls on a bot bubble: step one rung UP/DOWN the llama→Sonnet→Opus ladder. */
  .bact.bact-rerun { font-weight: 700; }
  /* WO-STEP-CAP (task 435): the Continue affordance on a reply that hit the step cap mid-task.
     Styled as a clear primary action (not a bare icon) so a resume is the obvious next step. */
  .bact.bact-continue { color: #58a6ff; border: 1px solid rgba(88,166,255,.5); border-radius: 6px;
    padding: 1px 8px; font-weight: 600; }
  .bact.bact-continue:hover { color: #e6edf3; background: rgba(88,166,255,.15); }
  .bact:disabled { opacity: .3; cursor: default; }
  .bact:disabled:hover { color: #8b949e; background: none; }
  /* WO-ADVISOR-CALENDAR-WRITE: confirm-before-write card. A gated tool call (destructive —
     calendar create/update/delete, gmail send, drive write, …) halts the loop and proposes the
     action instead of running it; this renders that proposal with an explicit Confirm/Cancel so
     nothing ever writes without the human saying so, on desktop OR mobile (44px+ touch targets). */
  .gate-card { margin-top: 8px; padding: 10px 12px; background: #1c1f26; border: 1px solid #d29922;
    border-radius: 10px; }
  .gate-title { font-size: 12px; font-weight: 700; color: #d29922; margin-bottom: 6px; }
  .gate-tool { font-size: 10px; color: #8b949e; margin-bottom: 6px; font-family: monospace; }
  .gate-field { display: flex; gap: 6px; font-size: 12px; padding: 2px 0; }
  .gate-field .gate-k { color: #8b949e; min-width: 72px; flex-shrink: 0; }
  .gate-field .gate-v { color: #e6edf3; word-break: break-word; }
  .gate-actions { display: flex; gap: 8px; margin-top: 10px; }
  .gate-btn { flex: 1; min-height: 40px; border-radius: 8px; font-size: 13px; font-weight: 600;
    cursor: pointer; border: 1px solid transparent; }
  .gate-btn.gate-confirm { background: #238636; color: #fff; }
  .gate-btn.gate-confirm:hover { background: #2ea043; }
  .gate-btn.gate-cancel { background: #21262d; color: #e6edf3; border-color: #30363d; }
  .gate-btn.gate-cancel:hover { background: #30363d; }
  .gate-btn:disabled { opacity: .5; cursor: default; }
  .gate-cancelled { margin-top: 8px; font-size: 12px; color: #8b949e; font-style: italic; }
  @media (max-width: 480px) {
    .gate-btn { min-height: 44px; font-size: 14px; }
  }

  /* WO-PRINT-BRIDGE (task 390): print action button (chat bubbles, My Day, file attachments) +
     the picker/confirm sheet + inline job-status pill. Reuses .gate-* for the confirm card so it
     looks/feels identical to every other confirm-before-write flow in this app. */
  .bact.bact-print, .pd-print-btn { }
  .print-sheet { max-width: 420px; }
  .print-none { padding: 16px 4px; text-align: center; color: #8b949e; font-size: 13px; }
  .print-field { margin: 12px 0; }
  .print-field label { display: block; font-size: 11px; color: #8b949e; text-transform: uppercase;
    letter-spacing: .04em; margin-bottom: 6px; }
  .print-select { width: 100%; background: #0d1117; border: 1px solid #30363d; border-radius: 8px;
    color: #e6edf3; padding: 10px 10px; font-size: 14px; }
  .print-select option[data-offline="1"] { color: #8b949e; }
  .print-copies { display: flex; align-items: center; gap: 10px; }
  .print-copies button { width: 36px; height: 36px; border-radius: 8px; border: 1px solid #30363d;
    background: #21262d; color: #e6edf3; font-size: 16px; cursor: pointer; }
  .print-copies span { min-width: 24px; text-align: center; font-size: 14px; font-weight: 600; }
  .print-status { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: #8b949e;
    margin-left: 4px; }
  .print-status.sent { color: #3fb950; }
  .print-status.failed, .print-status.expired { color: #f85149; }
  .print-status.printing, .print-status.queued { color: #d29922; }

  /* WO-CALENDAR-VIEW (task 236): the header Calendar button opens a real, connected calendar
     panel — agenda/day/week over the existing calendar_* tools. Create reuses the .gate-* proposal
     card above (confirm-before-write). Mobile-first: full-screen sheet under 600px. */
  .cal-sheet { max-width: 560px; }
  .cal-tabs { display: flex; align-items: center; gap: 6px; margin: 12px 0 6px; flex-wrap: wrap; }
  .cal-tab { padding: 6px 12px; border-radius: 999px; border: 1px solid #30363d; background: #21262d;
    color: #8b949e; font-size: 12px; font-weight: 600; cursor: pointer; }
  .cal-tab.active { background: #1f6feb; border-color: #1f6feb; color: #fff; }
  .cal-add-btn { margin-left: auto; padding: 6px 12px; border-radius: 999px; border: 1px solid #238636;
    background: #238636; color: #fff; font-size: 12px; font-weight: 600; cursor: pointer; }
  .cal-range { font-size: 11px; color: #8b949e; margin-bottom: 10px; text-transform: uppercase; letter-spacing: .04em; }
  .cal-loading, .cal-empty, .cal-error { padding: 20px 4px; color: #8b949e; font-size: 13px; text-align: center; }
  .cal-error { color: #f85149; }
  .cal-connect { padding: 16px 4px; text-align: center; }
  .cal-connect .hint { margin-bottom: 10px; }
  .cal-list { display: flex; flex-direction: column; gap: 6px; max-height: 60vh; overflow: auto; }
  .cal-ev { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; background: #161b22;
    border: 1px solid #21262d; border-radius: 10px; }
  .cal-ev-time { flex: 0 0 auto; min-width: 96px; font-size: 11px; color: #8b949e; padding-top: 2px; }
  .cal-ev-body { flex: 1 1 auto; min-width: 0; }
  .cal-ev-title { font-size: 13px; color: #e6edf3; font-weight: 600; }
  .cal-ev-loc { font-size: 11px; color: #8b949e; margin-top: 2px; }
  .cal-ev-link { flex: 0 0 auto; font-size: 11px; color: #58a6ff; text-decoration: none; align-self: center; }
  .cal-add-form { margin: 10px 0 14px; padding: 12px; background: #161b22; border: 1px solid #30363d; border-radius: 10px; }
  .cal-add-form input, .cal-add-form textarea { width: 100%; background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; color: #e6edf3; padding: 8px 10px; font-size: 13px; margin-bottom: 8px; font-family: inherit; }
  .cal-f-row { display: flex; gap: 8px; }
  .cal-f-row label { flex: 1 1 0; font-size: 11px; color: #8b949e; }
  .cal-f-actions { display: flex; gap: 8px; }
  .cal-f-actions .gate-btn { flex: 1; }
  @media (max-width: 600px) {
    #calendar-modal { padding: 0; }
    #calendar-modal .sheet.cal-sheet {
      position: fixed; inset: 0; max-width: none; margin: 0; border-radius: 0; border: none;
      height: 100vh; height: 100dvh; overflow-y: auto;
      padding: calc(14px + env(safe-area-inset-top)) calc(14px + env(safe-area-inset-right))
               calc(14px + env(safe-area-inset-bottom)) calc(14px + env(safe-area-inset-left));
    }
    .cal-list { max-height: none; }
    .cal-f-row { flex-direction: column; gap: 4px; }
  }

  /* WO-FOLDER-ACCESS-CARD (task 444): granted-folder chip strip + browse card. The browse button
     now lives in the right-rail Accounts tab instead of the composer. Reuses the .gate-* / .cal-*
     visual language so it feels like every other confirm/browse flow in this app. Mobile-first
     full-screen sheet under 600px (same #calendar-modal trick). */
  .folder-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 14px; }
  .folder-chip { display: inline-flex; align-items: center; gap: 6px; background: #0d2847;
    color: #58a6ff; border: 1px solid #1f6feb; border-radius: 999px; padding: 3px 6px 3px 10px;
    font-size: 11px; max-width: 220px; }
  .folder-chip .fc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .folder-chip .fc-node { color: #8b949e; font-size: 10px; }
  .folder-chip .fc-revoke { flex: 0 0 auto; background: none; border: none; color: #8b949e;
    cursor: pointer; font-size: 13px; line-height: 1; padding: 2px; border-radius: 50%; }
  .folder-chip .fc-revoke:hover { color: #f85149; background: rgba(248,81,73,.12); }
  .folder-sheet { max-width: 480px; }
  /* WO-DRIVE-FOLDER-PICKER: the SOURCE selector (Mac | Google Drive) above the device row.
     Reuses the .folder-device pill language so the two rows read as one control stack. */
  .folder-sources { display: flex; gap: 8px; margin: 14px 0 2px; flex-wrap: wrap; }
  .folder-source { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px;
    border-radius: 999px; border: 1px solid #30363d; background: #161b22; color: #8b949e;
    font-size: 12px; font-weight: 600; cursor: pointer; }
  .folder-source.active { background: #21262d; border-color: #58a6ff; color: #e6edf3; }
  .folder-account { font-size: 11px; color: #6e7681; margin: 8px 0 2px; }
  /* WO-MULTI-ACCOUNT-GOOGLE: the ACCOUNT SELECTOR row at the top of the Google Drive source.
     Brandon holds several Google accounts (Personal, Sublime, BCW, …) — this row lists them and
     selecting one browses THAT account's Drive. A pill carries: ● when the desk already holds a
     folder in that account, ★ when it is the desk's primary, and a dimmed/disabled state when
     Drive is switched off for it (browsing it would dead-end). */
  .folder-accounts { display: flex; gap: 6px; margin: 12px 0 2px; flex-wrap: wrap; align-items: center; }
  .folder-accounts .fa-label { font-size: 10px; color: #6e7681; text-transform: uppercase;
    letter-spacing: .04em; margin-right: 2px; width: 100%; }
  .folder-acct { display: inline-flex; align-items: center; gap: 5px; padding: 5px 11px;
    border-radius: 999px; border: 1px solid #30363d; background: #161b22; color: #8b949e;
    font-size: 11px; font-weight: 600; cursor: pointer; max-width: 100%; }
  .folder-acct.active { background: #21262d; border-color: #58a6ff; color: #e6edf3; }
  .folder-acct:disabled { opacity: .45; cursor: not-allowed; }
  .folder-acct .fa-dot { width: 6px; height: 6px; border-radius: 50%; background: #30363d; flex: 0 0 auto; }
  .folder-acct .fa-dot.has-folder { background: #3fb950; }
  .folder-acct .fa-star { color: #d29922; font-size: 10px; }
  .folder-acct .fa-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .folder-devices { display: flex; gap: 8px; margin: 12px 0 6px; flex-wrap: wrap; }
  .folder-device { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px;
    border-radius: 999px; border: 1px solid #30363d; background: #21262d; color: #8b949e;
    font-size: 12px; font-weight: 600; cursor: pointer; }
  .folder-device.active { background: #1f6feb; border-color: #1f6feb; color: #fff; }
  .folder-device .fd-dot { width: 7px; height: 7px; border-radius: 50%; background: #f85149; flex: 0 0 auto; }
  .folder-device .fd-dot.online { background: #3fb950; }
  .folder-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; font-size: 12px;
    color: #8b949e; margin: 4px 0 10px; word-break: break-all; }
  .folder-crumbs .fcr-seg { background: none; border: none; color: #58a6ff; cursor: pointer;
    font-size: 12px; padding: 2px 3px; }
  .folder-crumbs .fcr-seg:hover { text-decoration: underline; }
  .folder-crumbs .fcr-sep { color: #484f58; }
  .folder-body { max-height: 50vh; overflow: auto; display: flex; flex-direction: column; gap: 4px; }
  .folder-row { display: flex; align-items: center; gap: 10px; padding: 9px 8px; border-radius: 8px;
    background: #161b22; border: 1px solid #21262d; }
  .folder-row .fr-icon { flex: 0 0 auto; font-size: 16px; width: 20px; text-align: center; }
  .folder-row .fr-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; font-size: 13px; color: #e6edf3; cursor: pointer; }
  .folder-row .fr-name.is-dir:hover { color: #58a6ff; }
  .folder-row .fr-size { flex: 0 0 auto; font-size: 10px; color: #6e7681; }
  .folder-row .fr-grant { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 8px;
    border: 1px solid #30363d; background: #21262d; color: #e6edf3; font-size: 15px; cursor: pointer; }
  .folder-row .fr-grant.granted { background: #238636; border-color: #238636; color: #fff; }
  .folder-row .fr-grant:disabled { opacity: .5; cursor: default; }
  .folder-empty, .folder-loading, .folder-error, .folder-offline { padding: 20px 4px; text-align: center;
    color: #8b949e; font-size: 13px; }
  .folder-error { color: #f85149; }
  .folder-footnote { margin-top: 12px; text-align: center; font-size: 11px; color: #6e7681; }
  @media (max-width: 600px) {
    #folder-modal { padding: 0; }
    #folder-modal .sheet.folder-sheet {
      position: fixed; inset: 0; max-width: none; margin: 0; border-radius: 0; border: none;
      height: 100vh; height: 100dvh; overflow-y: auto;
      padding: calc(14px + env(safe-area-inset-top)) calc(14px + env(safe-area-inset-right))
               calc(14px + env(safe-area-inset-bottom)) calc(14px + env(safe-area-inset-left));
    }
    .folder-body { max-height: none; }
    .folder-row .fr-grant { width: 36px; height: 36px; }
  }

  /* WO-PLAN-MY-DAY (task 238): morning goal-set + day time-block planner. Mobile-first full-
     screen sheet under 600px (same #calendar-modal trick above), desktop = centered panel. The
     timeline is a two-column row (hour labels + absolutely-positioned blocks over hourlines,
     60px/hour, 6am-9pm) with a live red now-line — see renderPlanDayTimeline()/renderPlanDayNowLine(). */
  .planday-sheet { max-width: 640px; }
  .pd-date { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 10px; }
  .pd-goals-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
  .pd-all-goals-link { font-size: 12px; color: #58a6ff; text-decoration: none; white-space: nowrap; }
  .pd-all-goals-link:hover { text-decoration: underline; }
  .pd-goals { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; padding: 10px 12px;
    background: #161b22; border: 1px solid #30363d; border-radius: 10px; }
  .pd-goal-row { display: flex; align-items: center; gap: 8px; }
  .pd-goal-row input[type=checkbox] { flex: 0 0 auto; width: 16px; height: 16px; }
  .pd-goal-input { flex: 1 1 auto; min-width: 0; background: #0d1117; border: 1px solid #30363d; border-radius: 8px;
    color: #e6edf3; padding: 7px 9px; font-size: 13px; font-family: inherit; }
  /* WO-PLANDAY-GOAL-DETAIL: expand caret + notes cascade, same visual language as goals.html's
     .caret/.gdetail so the two goal UIs feel like one system. */
  .pd-goal-caret { flex: 0 0 auto; width: 22px; height: 22px; border: none; background: transparent; color: #6e7681;
    cursor: pointer; font-size: 11px; display: grid; place-items: center; transition: transform .15s ease; }
  .pd-goal-caret.collapsed { transform: rotate(-90deg); }
  .pd-goal-caret:disabled { opacity: .3; cursor: default; }
  .pd-goal-caret.has-notes { color: #58a6ff; position: relative; }
  .pd-goal-caret.has-notes::after { content: ''; position: absolute; top: 2px; right: 2px; width: 6px; height: 6px;
    border-radius: 50%; background: #58a6ff; }
  .pd-goal-detail { margin: 2px 0 2px 24px; }
  .pd-goal-notes { width: 100%; box-sizing: border-box; background: #0d1117; border: 1px solid #30363d; border-radius: 8px;
    color: #adbac7; padding: 7px 9px; font-size: 12px; font-family: inherit; resize: vertical; min-height: 44px; }
  .pd-add-row { display: flex; justify-content: flex-end; margin-bottom: 8px; }

  /* IB-127 · My Day items: the open-ended "today's jobs" list — carry-over age, job readiness,
     the day's clock. Lines and taps only, same visual language as .pd-goal-* above — no card, no
     modal, nothing that covers the screen (it lives inside the same #planday-modal sheet). */
  .pd-items-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-top: 14px; }
  .mdi-summary { font-size: 12px; color: #8b949e; }
  .mdi-summary.over { color: #f0883e; font-weight: 600; }
  .pd-items { display: flex; flex-direction: column; gap: 6px; margin: 6px 0 8px; padding: 10px 12px;
    background: #161b22; border: 1px solid #30363d; border-radius: 10px; }
  .mdi-empty { padding: 6px 2px; color: #6e7681; font-size: 12px; }
  .mdi-row { display: flex; align-items: flex-start; gap: 8px; padding: 4px 0; }
  .mdi-row input[type=checkbox] { flex: 0 0 auto; width: 16px; height: 16px; margin-top: 3px; }
  .mdi-main { flex: 1 1 auto; min-width: 0; }
  .mdi-text-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
  .mdi-text { color: #e6edf3; font-size: 13px; }
  .mdi-row.done .mdi-text { text-decoration: line-through; opacity: .6; }
  .mdi-carried { font-size: 10px; color: #d29922; background: #2b2111; border: 1px solid #57450f;
    border-radius: 999px; padding: 1px 7px; white-space: nowrap; }
  .mdi-estimate, .mdi-drive { font-size: 10px; color: #8b949e; white-space: nowrap; }
  .mdi-missing { margin-top: 3px; font-size: 12px; color: #f0883e; }
  .mdi-missing-chip { background: #2b1a10; border: 1px solid #57391a; color: #f0883e; border-radius: 999px;
    padding: 1px 8px; font-size: 11px; cursor: pointer; margin: 2px 4px 0 0; font-family: inherit; }
  .mdi-caret { flex: 0 0 auto; width: 22px; height: 22px; border: none; background: transparent; color: #6e7681;
    cursor: pointer; font-size: 11px; display: grid; place-items: center; transition: transform .15s ease; }
  .mdi-caret.collapsed { transform: rotate(-90deg); }
  .mdi-drop { flex: 0 0 auto; background: transparent; border: none; color: #6e7681; font-size: 11px;
    cursor: pointer; text-decoration: underline; padding: 3px 2px; font-family: inherit; }
  .mdi-detail { margin: 4px 0 4px 24px; display: flex; flex-direction: column; gap: 6px; }
  .mdi-detail-row { display: flex; gap: 6px; }
  .mdi-detail input, .mdi-detail textarea { flex: 1 1 auto; min-width: 0; background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; color: #adbac7; padding: 6px 9px; font-size: 12px; font-family: inherit; box-sizing: border-box; }
  .mdi-detail textarea { resize: vertical; min-height: 36px; }
  .mdi-material-chip { background: #21262d; border: 1px solid #30363d; color: #adbac7; border-radius: 999px;
    padding: 1px 8px; font-size: 11px; cursor: pointer; margin: 2px 4px 2px 0; font-family: inherit; }
  .mdi-material-chip.on-hand { color: #3fb950; border-color: #275c33; text-decoration: line-through; }
  .pd-item-add-row { display: flex; gap: 6px; margin-bottom: 10px; }
  .pd-item-add-input { flex: 1 1 auto; min-width: 0; background: #0d1117; border: 1px solid #30363d; border-radius: 8px;
    color: #e6edf3; padding: 8px 10px; font-size: 13px; font-family: inherit; }
  .pd-item-add-btn { flex: 0 0 auto; background: #21262d; border: 1px solid #30363d; color: #e6edf3;
    border-radius: 8px; padding: 8px 14px; font-size: 13px; cursor: pointer; font-family: inherit; }

  .pd-block-form { margin: 6px 0 14px; padding: 12px; background: #161b22; border: 1px solid #30363d; border-radius: 10px; }
  .pd-block-form select, .pd-block-form input { width: 100%; box-sizing: border-box; background: #0d1117; border: 1px solid #30363d;
    border-radius: 8px; color: #e6edf3; padding: 8px 10px; font-size: 13px; margin-bottom: 8px; font-family: inherit; }
  .pdf-remind-label { display: block; font-size: 11px; color: #8b949e; margin-bottom: 8px; }
  .pd-delete { background: #3a1a1a !important; color: #f0883e !important; border-color: #57391a !important; }
  .pd-timeline-wrap { max-height: 60vh; overflow-y: auto; overflow-x: hidden; }
  .pd-loading, .pd-empty-hint { padding: 14px 4px; color: #8b949e; font-size: 12px; text-align: center; }
  .pd-timeline-row { display: flex; position: relative; }
  .pd-hours { flex: 0 0 46px; position: relative; }
  .pd-hour-lb { position: absolute; right: 6px; top: -7px; font-size: 10px; color: #6e7681; white-space: nowrap; }
  .pd-timeline { flex: 1 1 auto; min-width: 0; position: relative; border-left: 1px solid #21262d; }
  .pd-hourline { position: absolute; left: 0; right: 0; border-top: 1px solid #21262d; height: 0; }
  .pd-now { position: absolute; left: 0; right: 0; height: 2px; background: #f85149; z-index: var(--z-raised); pointer-events: none; }
  .pd-now::before { content: ''; position: absolute; left: -3px; top: -4px; width: 8px; height: 8px; border-radius: 50%; background: #f85149; }
  .pd-block { position: absolute; left: 6px; right: 6px; border-left: 3px solid #58a6ff; background: #1c2128;
    border-radius: 6px; padding: 0 8px; overflow: hidden; cursor: pointer; z-index: var(--z-raised);
    display: flex; align-items: center; gap: 6px; }
  .pd-block-fixed { cursor: default; opacity: .92; }
  .pd-block-time { flex: 0 0 auto; font-size: 10px; color: #8b949e; }
  .pd-block-label { flex: 1 1 auto; min-width: 0; font-size: 12px; color: #e6edf3; font-weight: 600;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .pd-block-start { flex: 0 0 auto; background: #a371f7; border: none; color: #fff; border-radius: 6px;
    padding: 2px 8px; font-size: 10px; line-height: 1.6; cursor: pointer; }
  @media (max-width: 600px) {
    #planday-modal { padding: 0; }
    #planday-modal .sheet.planday-sheet {
      position: fixed; inset: 0; max-width: none; margin: 0; border-radius: 0; border: none;
      height: 100vh; height: 100dvh; overflow-y: auto;
      padding: calc(14px + env(safe-area-inset-top)) calc(14px + env(safe-area-inset-right))
               calc(14px + env(safe-area-inset-bottom)) calc(14px + env(safe-area-inset-left));
    }
    .pd-timeline-wrap { max-height: none; }
    .mdi-row input[type=checkbox] { width: 20px; height: 20px; }
    .mdi-caret, .mdi-drop, .mdi-missing-chip, .mdi-material-chip { min-height: 30px; }
    .pd-item-add-input, .pd-item-add-btn { min-height: 40px; }
  }

  /* attachment thumbnail (iPhone photo upload) */
  .attach-thumb { width: 26px; height: 26px; border-radius: 5px; object-fit: cover; vertical-align: middle; }
  /* WO-CHAT-MULTI-IMAGE (task 256): an attached message's images render as a WRAP GRID of large
     image-first thumbnails (composer send + history replay both go through this), not one big
     inline picture — several images in one bubble lay out mobile-first, tap one for full-size. */
  .bubble-img-grid { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; max-width: 100%; }
  .bubble-img-grid img { width: 112px; height: 112px; border-radius: 8px; object-fit: cover;
    border: 1px solid #30363d; cursor: zoom-in; display: block; }
  /* WO-VISION-HARDEN (task 235): the PDF-attachment chip (bubble-img-grid's sibling for non-image
     attachments) — a small clickable download link so a sent/replayed PDF stays visible. */
  .bubble-file { display: inline-block; margin-top: 6px; padding: 4px 10px; border-radius: 8px;
    border: 1px solid #30363d; background: #161b22; color: #58a6ff; text-decoration: none; font-size: 13px; }
  .bubble-file:hover { border-color: #58a6ff; }
  /* WO-CHAT-MULTI-IMAGE (task 256): dismissible full-size viewer for a tapped thumbnail —
     there's no existing modal image viewer in the dashboard, so this is a simple overlay. */
  .img-viewer-overlay { display: none; position: fixed; inset: 0; z-index: var(--z-modal-raised); background: rgba(0,0,0,.88);
    align-items: center; justify-content: center; padding: 24px; cursor: zoom-out; }
  .img-viewer-overlay.on { display: flex; }
  .img-viewer-overlay img { max-width: 100%; max-height: 100%; border-radius: 8px; object-fit: contain; cursor: default; }
  /* WO-MODAL-CLOSE-BTN (task 471): safe-area-aware top offset — this overlay has no header bar
     to carry the inset (it's a bare floating X over the image), so the offset lives on the X
     itself, calc'd off env(safe-area-inset-top) so it clears the iOS status bar/notch. */
  .img-viewer-close { position: absolute; top: calc(14px + env(safe-area-inset-top)); right: calc(14px + env(safe-area-inset-right)); width: 44px; height: 44px; border-radius: 50%;
    background: #161b22; color: #e6edf3; border: 1px solid #30363d; font-size: 16px; cursor: pointer; }
  .img-viewer-close:hover { border-color: #f85149; color: #f85149; }

  /* IB-1434 · the kebab is the ONLY door to Settings/Coach/Focus/Voice/Pause/Auto-land/Restart
     Fleet/Merge on a desktop browser (IB-UX-1 moved those routes' visible buttons off the bar,
     but they still live only inside this menu) — it must render at every width, not just phone. */
  .overflow-wrap { position: relative; display: inline-flex; }
  /* WO-30 (task 67, item 2): the three ops buttons never sit on the bar itself now — they live in
     the ⋯ menu on all widths. !important beats the inline display the owner-gating JS sets. */
  header > .hdr-ops { display: none !important; }
  .overflow-btn { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 6px;
                  padding: 4px 10px; cursor: pointer; font-size: 16px; line-height: 1; }
  .overflow-menu { display: none; position: absolute; right: 0; top: calc(100% + 6px); z-index: var(--z-dropdown);
                   flex-direction: column; gap: 4px; min-width: 190px; padding: 6px;
                   background: #161b22; border: 1px solid #30363d; border-radius: 10px;
                   box-shadow: 0 8px 28px rgba(0,0,0,.55); }
  .overflow-menu.on { display: flex; }
  .overflow-menu button { width: 100%; text-align: left; background: #21262d; color: #e6edf3;
                          border: 1px solid #30363d; border-radius: 7px; padding: 9px 11px;
                          font-size: 14px; min-height: 44px; cursor: pointer; }
  /* WO-HEADER-INSANE-BRAIN: Coach/Goals/Dashboard/Library collapse in here (req 7) above a thin
     divider from the pre-existing ops actions (Set token / Provider accounts / …). The mini burn
     meter also relocates here (off the always-visible bar, req 3) — full width instead of a pill. */
  .overflow-menu hr { border: none; border-top: 1px solid #30363d; margin: 4px 2px; width: 100%; }
  .overflow-menu .usage-mini { width: 100%; box-sizing: border-box; justify-content: flex-start; }
  .overflow-section { display: block; font-size: 10px; font-weight: 700; letter-spacing: .06em;
                      text-transform: uppercase; color: #8b949e; padding: 6px 11px 2px; }

  /* desktop: rail becomes a fixed left column; main makes room; hamburger hidden */
  @media (min-width: 760px) {
    .hamb { display: none; }
    .rail { transform: translateX(0); box-shadow: none; }
    .backdrop { display: none !important; }
    .main { padding-left: var(--rail-w); }
    /* desktop collapse → icons-only column */
    body.rail-collapsed { --rail-w: 56px; }
    body.rail-collapsed .agent .who, body.rail-collapsed .agent .tc, body.rail-collapsed .rail h2 span.lbl,
    body.rail-collapsed .grp { display: none; }
    body.rail-collapsed .agent { justify-content: center; padding: 11px 0; }
    /* WO-HEADER-SIDEBAR-UX (task 452, req 4): in the icon-only collapsed rail the Dashboard button
       shrinks to its house icon, centered, matching the desk rows. */
    body.rail-collapsed .rail-dash { justify-content: center; padding: 11px 0; gap: 0; }
    body.rail-collapsed .rail-dash-lbl { display: none; }
  }

  /* ── WO-CUSTOM-LAYOUT (task 219): resizable sidebar + draggable/resizable dashboard cards ──
     Desktop-only (min-width:760px). Every rule here is either invisible by default (display:none
     until the desktop query enables it) or purely cosmetic on hover/drag — the DEFAULT rendered
     layout (sidebar width, card order, card sizes) never depends on this block; localStorage only
     ever adds override classes / inline sizes on top of it. */
  .rail-resize { display: none; }
  .layout-toolbar { display: none; }
  .card-ctrls { display: none; }
  .card-resize-handle { display: none; }
  /* TIB-075 §5 · the sentence the page leaves behind when it puts the default layout back by
     itself. Quiet by construction: one line, the colour of the other secondary text, beside the
     Reset button rather than in front of the board. The toolbar it sits in is desktop-only, which
     is also the only place a custom layout can be saved from. */
  .layout-fallback-note { display: inline; margin-right: auto; color: #8b949e; font-size: 11px;
                          line-height: 1.4; }
  @media (min-width: 760px) {
    .rail-resize { display: block; position: fixed; top: var(--header-h); bottom: 0; left: var(--rail-w);
                   width: 6px; margin-left: -3px; cursor: col-resize; z-index: var(--z-chrome-top); background: transparent; }
    .rail-resize:hover, body.rail-resizing .rail-resize { background: rgba(56,139,253,.35); }
    body.rail-collapsed .rail-resize { display: none; }
    body.rail-resizing { cursor: col-resize; user-select: none; }
    body.rail-resizing .rail, body.rail-resizing .main { transition: none; }
    .layout-toolbar { display: flex; justify-content: flex-end; align-items: center; gap: 10px;
                      padding: 0 24px; margin-top: -6px; }
    .layout-toolbar button { background: none; border: 1px solid #30363d; color: #8b949e; border-radius: 6px;
                              padding: 3px 9px; font-size: 11px; cursor: pointer; }
    .layout-toolbar button:hover { background: #21262d; color: #c9d1d9; }
    .wrap .panel > h2 { cursor: grab; }
    .wrap .panel > h2:active { cursor: grabbing; }
    .wrap .panel.card-dragging { opacity: .4; }
    /* IB-1435 · the Ops Dashboard's own panels get the same drag-to-reorder grab affordance
       as .wrap's cards (see initOpsGridDragReorder) — mouse/desktop only, native HTML5 drag
       is a no-op on touch so phone falls back to plain header taps, which is what we want. */
    .ops-grid .panel > h2 { cursor: grab; }
    .ops-grid .panel > h2:active { cursor: grabbing; }
    .ops-grid .panel.card-dragging { opacity: .4; }
    .card-ctrls { margin-left: auto; display: flex; align-items: center; gap: 4px; }
    .card-ctrls button { background: none; border: none; color: #8b949e; cursor: pointer; font-size: 12px;
                          padding: 2px 4px; line-height: 1; }
    .card-ctrls button:hover { color: #c9d1d9; }
    .wrap .panel.card-wide { grid-column: 1 / -1; }
    .wrap .panel.card-narrow { grid-column: span 1 !important; }
    .card-resize-handle { height: 8px; cursor: row-resize; position: relative; }
    .card-resize-handle::after { content: ''; position: absolute; left: 50%; top: 2px; width: 28px; height: 3px;
                                  margin-left: -14px; border-radius: 2px; background: #30363d; }
    .card-resize-handle:hover::after { background: #58a6ff; }
  }
  /* worker desks panel (spec 602) */
  .wdot { width: 8px; height: 8px; border-radius: 50%; background: #3fb950; box-shadow: 0 0 6px #3fb950; display: inline-block; }
  .wdot.off { background: #6e7681; box-shadow: none; }
  .wstate { font-size: 12px; color: #8b949e; }
  .wstate.busy { color: #58a6ff; }
  .wstate.blocked { color: #f85149; }
  /* app login indicator (spec 611) — WO-HEADER-INSANE-BRAIN (req 7) collapses this into the
     compact .avatar-wrap/.avatar-menu (defined above) instead of an inline name+role block. */
  .signin { background: #1f6feb !important; color: #fff !important; border-color: #1f6feb !important; }
  /* Restart Fleet button (task 14): owner-only, danger-tinted so it reads as a heavy action. */
  .restart-btn { background: #3a1a1a !important; color: #f0883e !important; border-color: #57391a !important; font-weight: 600; }
  .restart-btn:hover { background: #4d2020 !important; }
  /* Master ON/OFF switch (WO-19 · cost governor): owner-only. GREEN = running (click to Pause),
     AMBER = paused (click to Resume). Prominent in the top bar so the whole fleet's spend state
     is one glance + one click. */
  .pause-btn { background: #12261a !important; color: #56d364 !important; border-color: #1c5330 !important; font-weight: 700; }
  .pause-btn:hover { background: #163524 !important; }
  .pause-btn.paused { background: #3a2a10 !important; color: #e2c08d !important; border-color: #574a1a !important; }
  .pause-btn.paused:hover { background: #4a3614 !important; }
  /* PAUSED banner: full-width amber bar under the counts, impossible to miss when the fleet is off. */
  .pausebanner { margin: 4px 20px 0; padding: 10px 14px; border-radius: 8px; font-size: 13px; font-weight: 600;
                 border: 1px solid #574a1a; background: #2b2410; color: #e2c08d; display: flex; align-items: center; gap: 8px; }
  .pausebanner b { color: #f0c674; }
  /* WO-27 Claude-Max burn meter: a thin bar + % readout, colour-coded by cap proximity. */
  .burnbar { height: 8px; border-radius: 5px; background: #21262d; overflow: hidden; margin: 2px 0 5px; }
  .burnbar > span { display: block; height: 100%; background: #56d364; transition: width .4s; }
  .burnbar.warn > span { background: #e2c08d; }
  .burnbar.over  > span { background: #f85149; }
  #burn-meter .brow { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; color: #8b949e; }
  #burn-meter .bpct { font-weight: 700; color: #c9d1d9; }
  #burn-meter .capedit { margin-top: 6px; display: flex; gap: 5px; flex-wrap: wrap; align-items: center; }
  #burn-meter .capedit input { width: 92px; background: #0d1117; color: #c9d1d9; border: 1px solid #30363d; border-radius: 5px; padding: 3px 6px; font-size: 12px; }
  #burn-meter .capedit button { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; border-radius: 5px; padding: 3px 9px; font-size: 12px; cursor: pointer; }
  #burn-meter .capedit button:hover { background: #30363d; }
  .burn-badge-throttled { color: #f85149; font-weight: 700; }
  .burn-badge-warn { color: #e2c08d; font-weight: 700; }
  @media (max-width: 759px) { .pausebanner { margin: 4px 14px 0; } }
  /* Restart status banner: phase-tinted (busy/ok/warn/err). Full-width under the counts. */
  .opsbanner { margin: 4px 20px 0; padding: 9px 14px; border-radius: 8px; font-size: 13px; font-weight: 500;
               border: 1px solid #30363d; background: #161b22; color: #e6edf3; }
  .opsbanner.busy { background: #12243f; color: #79c0ff; border-color: #1f3a5f; }
  .opsbanner.ok   { background: #12261a; color: #56d364; border-color: #1c5330; }
  .opsbanner.warn { background: #2b2410; color: #e2c08d; border-color: #574a1a; }
  .opsbanner.err  { background: #3a1a1a; color: #f85149; border-color: #57391a; }
  @media (max-width: 759px) { .opsbanner { margin: 4px 14px 0; } }

  /* ── touch-first / PWA (spec 612) ─────────────────────────────────────────
     Phone-first refinements: respect the notch + home bar (safe-area insets),
     give every control a ≥44px tap target, and keep the chat input above the
     on-screen keyboard. Desktop is untouched (overrides live in the ≤759 query
     or are inset values that are 0 on a desktop). */
  header { padding-top: calc(14px + env(safe-area-inset-top));
           padding-left: calc(20px + env(safe-area-inset-left));
           padding-right: calc(20px + env(safe-area-inset-right)); }
  /* IB-01: the base header rule already carries sticky + the safe-area padding, so the phone
     header stays put while the board scrolls and clears the notch. The rail starts BELOW the
     header (top: var(--header-h), measured live in JS so it tracks header wrapping + the notch
     inset), which is what keeps the AGENTS sidebar off the status/context strip.
     IB-06: the full-screen chat panel and the tool-rail drawer are TAKEOVERS — they replace the
     board, so they cover the header on purpose, and each brings its own way out. See the
     stacking order at the top of this stylesheet. */

  @media (max-width: 759px) {
    /* IB-1418 · THE PHONE GLANCE. Top of the screen, above everything, no scroll no tap: how many
       coders are building and whether anything needs him or is stuck. The needs-you cards below
       the headline are built by the existing rightNowItemCard() and styled by the shared
       .rn-item-* rules elsewhere in this file (the same cards the Right Now cockpit uses) —
       reused here, not redefined, so they can never drift apart. */
    .phone-glance { display: block; margin: 10px 14px 4px; padding: 14px 16px; background: #161b22;
                    border: 1px solid #21262d; border-radius: 12px; }
    .pg-headline { font-size: 19px; font-weight: 700; color: #e6edf3; line-height: 1.3; }
    .pg-headline.pg-tone-amber { color: #d29922; }
    .pg-headline.pg-tone-red { color: #f85149; }
    .pg-needs { display: flex; flex-direction: column; gap: 8px; }
    .pg-needs:empty { display: none; }
    .pg-needs:not(:empty) { margin-top: 12px; }
    /* WO-CHAT-DECLUTTER (task 357): Ops dashboard cards stack single-column on a phone. */
    .ops-grid { grid-template-columns: 1fr; }
    /* IB-143: on a phone the ops drawer goes full-screen so it can never be confused with the
       desktop right-rail drawer; the sheet still leaves a 40px edge to tap for dismissal. */
    .ops-panel { inset: 0; width: 100%; justify-content: flex-end; }
    .ops-panel .sheet { width: calc(100% - 40px); }
    /* WO-PRODUCTION-BOARD: the 7 stage columns stack to a single column on a phone. */
    .production-columns { grid-template-columns: 1fr; }
    /* WO-CODING-BOARD: the four lanes stack to a single full-width column on a phone, and every
       box in the chain is bounded (max-width:100% + min-width:0) so a long branch name or SHA
       wraps inside its card instead of pushing the board sideways — no horizontal scroll. */
    .coding-lanes { grid-template-columns: 1fr; max-width: 100%; }
    .coding-scroll { overflow-x: hidden; padding: 10px 12px 28px; }
    .clane, .cbcard, .cb-meta { max-width: 100%; min-width: 0; }

    /* big, thumb-friendly tap targets */
    .hamb { min-width: 44px; min-height: 44px; font-size: 20px; }
    header button { min-height: 44px; padding: 8px 12px; }
    .agent { padding: 13px 16px; }                 /* taller rows in the slide-over */
    .rail { width: min(82vw, 320px); --rail-w: min(82vw, 320px);
            padding-left: env(safe-area-inset-left); }

    /* IB-UX-1 · the header is now two tight rows. On a narrow phone the explicit ordering forces a
       predictable stack so nothing overlaps:
         row 1: hamburger + brand + vitals + icon-only Dashboard/Manager + avatar/sign-in
         row 2: the action strip (tabs + coder/printer chips + fleet health control). */
    /* WO-MOBILE-FIX (task 218): this rule used to hard-set padding-top:10px, which clobbered the
       safe-area padding-top set above (header{padding-top:calc(14px + env(safe-area-inset-top))})
       since both rules match plain `header` and this one loads later in the cascade — the header
       rendered UNDER the iPhone status bar/notch and the hamburger became untappable. Keep the
       10px base but re-add the inset so the notch is always cleared on every phone width. */
    header { padding-top: calc(10px + env(safe-area-inset-top)); padding-bottom: 10px; }
    .hamb { order: 1; }
    .brand { order: 2; min-width: 0; flex: 1 1 auto; }
    .wordmark { font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .meta { order: 3; }
    .avatar-wrap, #signin { order: 5; }
    .vitals { order: 10; flex-basis: 100%; }
    .hdr-search { display: none; }
    #allbtn { order: 30; }
    header > .pause-btn { order: 31; }   /* scoped to the (CSS-hidden) bar copy only — not the overflow-menu twin */
    #managerbtn { order: 32; }
    .action-strip { order: 40; }
    /* Coder-card containers on mobile (chat-panel mount, Ops dashboard): one non-wrapping
       horizontally-scrolling row. The header cluster lives inside the nowrap action strip. */
    .coder-cards { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
                   scrollbar-width: none; }
    .coder-cards::-webkit-scrollbar { display: none; }
    /* Compact mobile tile (Ops dashboard / chat panel coders): ~half the height of the desktop
       104×100 square, with condensed fonts/padding. Content restacks HORIZONTAL — [●num] on the
       left, [task# timer] on the right — so a short, wide tile reads cleanly. */
    .coder-card { flex: 1 1 0; min-width: 0; width: auto; height: 50px; padding: 4px 6px;
                  flex-direction: row; justify-content: flex-start; align-items: center; gap: 5px;
                  border-radius: 6px; }
    /* IB-1345 · matches the 50px height set above — own declaration, same reasoning as the
       mobile-breakpoint override further down (an exact-string test reads that one as one unit). */
    .coder-card { max-height: 50px; overflow: hidden; }
    .coder-card .cc-left { flex-direction: row; align-items: center; gap: 4px; min-width: 0; }
    .coder-card .cc-num { font-size: 15px; }
    .coder-card .cc-dot { width: 6px; height: 6px; flex: 0 0 auto; }
    .coder-card .cc-right { align-items: flex-start; min-width: 0; gap: 0; }
    .coder-card .cc-task { font-size: 9px; text-align: left; }
    .coder-card .cc-timer { font-size: 8px; }
    .coder-card .cc-brain-tag { font-size: 6px; top: 2px; right: 3px; }
    /* TIB-055 · the 50px phone tile has no room for the reason line, and a truncated half-sentence
       would be worse than none. The brain tag stays; the reason is one tap away on the detail
       sheet, which is where the phone already sends every other explanation. */
    .coder-card .cc-why { display: none; }
    .coder-card .cc-lock { top: 2px; left: 3px; font-size: 8px; }
    .vpill { padding: 4px 7px; }

    /* counter cards: two readable columns that fill the width */
    .counts { padding: 14px; gap: 10px; }
    .count { flex: 1 1 calc(50% - 10px); min-width: 0; }
    .wrap { padding: 14px 14px 24px; gap: 18px; }
    .filterbar { padding: 0 14px; }

    /* chat: full-width, input one-handed, Send a big tap target, keyboard clear */
    .chatpanel { margin: 0 14px 16px; }
    /* WO-MOBILE-CHAT-FULLSCREEN (task 231): a phone chat is its own full-screen view, like every
       native chat app. IB-143 reserves the header region, so the panel covers the viewport UNDER
       the header (`top: var(--header-h)`). `height` is re-set in JS to `visualViewport`'s live
       height minus the header (see syncChatViewportBox) because iOS Safari does NOT shrink the
       LAYOUT viewport when the keyboard opens — only the VISUAL viewport shrinks — so a fixed box
       sized off vh/dvh would sit the input row under the keyboard. */
    .chatpanel.on {
      position: fixed; inset: 0; z-index: var(--z-modal); margin: 0; border-radius: 0; border: none;
      display: flex; flex-direction: column;
      height: calc(100vh - var(--header-h)); height: calc(100svh - var(--header-h)); height: calc(100dvh - var(--header-h));
      padding-top: env(safe-area-inset-top);
      padding-left: env(safe-area-inset-left);
      padding-right: env(safe-area-inset-right);
    }
    /* IB-143: full-screen phone panels (and their backdrops) stay below the reserved header. */
    .chatpanel.on, .tool-rail.drawer-open, .tool-rail-backdrop.on,
    .model-picker-menu, .model-picker-backdrop {
      top: var(--header-h) !important;
    }
    /* Back/close control moves to the top-LEFT (native "back" placement) via flex order, no DOM
       change needed. (Task T33: the attachments toggle that used to be pinned top-right here now
       lives in the rail's Info tab.) */
    .chatpanel.on h2 { display: flex; align-items: center; flex: 0 0 auto; padding: 12px 14px; }
    .chatpanel.on .chat-close { order: -1; float: none; margin-left: 0; margin-right: 10px;
                                 min-width: 44px; min-height: 44px; font-size: 18px; }
    .chatpanel.on .attachments-panel { flex: 0 0 auto; max-height: 40vh; overflow: auto; }
    /* WO-HEADER-HUD-POLISH (task 387, req 1): reveal the phone-only coder HUD mount ONLY while
       the chat panel is its full-screen .on state — the header strip underneath is unreachable
       there (see the comment on #chat-coders in the markup). flex:0 0 auto — NOT the base
       .coder-cards flex-basis:100% meant for the header's ROW flex-wrap, which would otherwise
       blow this COLUMN flex layout's main axis (height) up to fill the whole panel.
       WO-MOBILE-LAYOUT-WORLDCLASS (task 467): the mount is now ONE non-wrapping compact row (see
       the .coder-cards nowrap rule just above) sized to a single ~50px tile, not the old wrapping
       220px-tall grid — so the chatlog + composer get that vertical space back. flex-basis:auto
       keeps it off the column's main axis (height); the row's own height is the tile height. */
    .chatpanel.on #chat-coders { display: flex; flex-wrap: nowrap; flex: 0 0 auto; flex-basis: auto;
                                  max-height: 70px; overflow-x: auto; overflow-y: hidden;
                                  padding: 4px 14px 6px; }
    /* IB-1425 · #coder-status-tabs (the numbered Chat/1-6 strip, just above) and this mount's
       .strip-coders slot are the SAME six coders drawn twice, stacked, on a phone — the tab strip
       is the newer, denser control (fits six across with no scroll) so it stays; this glance-only
       mirror copy is redundant here and is hidden. #chat-coders' OTHER slot (.strip-printers,
       IB-137) is untouched — that's a different feature's cards, not part of this duplicate. */
    .chatpanel.on #chat-coders .strip-coders { display: none; }
    /* The chatlog is the ONE flexible row — it absorbs all leftover height so the header stays
       pinned at top and the composer stays pinned at bottom without any sticky/scroll tricks.
       -webkit-overflow-scrolling:touch gives old iOS Safari momentum (inertial) scrolling;
       overscroll-behavior:contain (already on .chatlog) stops a scroll-past-the-end from bubbling
       to and rubber-banding the (now-hidden) board behind it. */
    .chatlog-wrap { flex: 1 1 auto; min-height: 0; }
    .chatlog { flex: none; height: 100%; min-height: 0; max-height: none; -webkit-overflow-scrolling: touch; }
    .chatpanel.on .chatform { flex: 0 0 auto; }
    /* IB-124 · coder status pane fills the mobile chat column and its log scrolls. */
    #coder-status-pane { display: none; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; padding: 10px; gap: 8px; }
    #coder-status-pane.on { display: flex; }
    #coder-status-pane > .csp-terminal-wrap { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
    #coder-status-pane > .csp-terminal-wrap > .csp-terminal { flex: 1 1 auto; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch; }
    #coder-status-pane > .csp-composer { flex: 0 0 auto; }
    /* IB-144 · composer sits on the keyboard, last line stays visible at 390pt */
    #coder-status-pane > .csp-composer > .csp-form { padding: 8px 10px calc(8px + env(safe-area-inset-bottom)); gap: 8px; }
    #coder-status-pane > .csp-composer > .csp-form textarea { min-height: 44px; font-size: 16px; }
    #coder-status-pane > .csp-composer > .csp-form button { min-height: 44px; min-width: 64px; }
    /* WO-CHAT-SCROLL-BOTTOM (task 354): bigger thumb target + clear the composer's safe-area pad. */
    .chat-jump-bottom { width: 48px; height: 48px; right: 14px; bottom: calc(14px + env(safe-area-inset-bottom)); font-size: 20px; }
    /* Lock the board's own scroll while the full-screen chat is open so a swipe inside the chat
       can never rubber-band the page underneath into view. */
    body.chat-fs-lock { overflow: hidden; }
    /* WO-MOBILE-FIX (task 218): real thumb target for the chat close/back control. */
    .chat-close { min-width: 44px; min-height: 44px; font-size: 15px; }
    /* T24 (feat/mobile-chat-header): the SLIM Insane Brain strip — the one thin line of global
       context that survives into a full-screen desk chat (the real <header> is covered here).
       Brand mark + compact counts (desks online · alerts · active printers). flex:0 0 auto keeps
       it off the chatlog's main-axis height; one non-wrapping, ellipsized line. */
    .chatpanel.on .chat-ib-strip {
      display: flex; align-items: center; gap: 8px;
      flex: 0 0 auto; padding: 5px 14px; min-height: 26px;
      background: #0d1117; border-bottom: 1px solid #21262d;
      font-size: 11px; color: #8b949e;
      white-space: nowrap; overflow: hidden;
    }
    .chatpanel.on .chat-ib-strip .ib-brand {
      display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto;
      color: #e6edf3; font-weight: 700; font-size: 12px; letter-spacing: .01em;
    }
    .chatpanel.on .chat-ib-strip .ib-brand .ib-mark { font-size: 13px; line-height: 1; }
    .chatpanel.on .chat-ib-strip .ib-counts {
      display: inline-flex; align-items: center; gap: 9px; margin-left: auto;
      min-width: 0; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums;
    }
    .chatpanel.on .chat-ib-strip .ib-counts b { color: #e6edf3; font-weight: 600; }
    .chatpanel.on .chat-ib-strip .ib-counts .ib-sep { color: #30363d; }
    .chatpanel.on .chat-ib-strip .ib-counts .ib-alerts.has b { color: #f0883e; }
    /* IB-124 · polished mobile model picker: turn the small dropdown into a reliable bottom-sheet.
       - fixed overlay with a dark backdrop so a tap outside is unambiguous
       - close X lives in the sheet header, pushed below the status bar by env(safe-area-inset-top)
       - scrollable option list with overscroll-behavior:contain so scrolling options never
         bubbles to the page or accidentally dismisses the popup
       - body scroll is locked while open so swiping the backdrop cannot move the page underneath */
    .model-picker-btn { min-height: 32px; padding: 4px 10px; font-size: 11px; }
    .model-picker-menu {
      position: fixed; inset: 0; z-index: var(--z-modal-raised); display: none;
      align-items: flex-end; justify-content: center;
      padding: 0; background: transparent; border: none; border-radius: 0;
      box-shadow: none; min-width: auto;
    }
    .model-picker-menu.on { display: flex; }
    .model-picker-backdrop { display: block; position: fixed; inset: 0; background: rgba(0,0,0,.55); }
    .model-picker-sheet {
      position: relative; width: 100%; max-width: 430px; max-height: 80vh;
      background: #161b22; border: 1px solid #30363d; border-bottom: none;
      border-radius: 16px 16px 0 0; display: flex; flex-direction: column;
    }
    .model-picker-head {
      display: flex; align-items: center; justify-content: space-between; flex: 0 0 auto;
      padding: 12px 14px; padding-top: calc(12px + env(safe-area-inset-top));
      border-bottom: 1px solid #21262d;
    }
    .model-picker-options {
      flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px;
      overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
    }
    .model-picker-option { padding: 12px 14px; min-height: 48px; }
    body.model-picker-open { overflow: hidden; }
    /* WO-MOBILE-CHAT-BUBBLES (task 157): on a phone the desktop 80% cap squished bot
       replies into a narrow left column with a big empty gap on the right (long text
       wrapped early + hard to read). Let you/bot bubbles use ~the full width so text
       fills the column. sys stays centered; the .bmeta pill/token/time row already
       wraps via flex-wrap so tags still fit at mobile width.
       WO-MOBILE-CHAT-WIDTH-REGRESSION (task 162): fix collapsed width causing
       one-char-per-line rendering — add explicit width: auto so bubbles expand.
       WO-INSANE-BRAIN-POLISH (req 4): still regressed to one-char-per-line on phone, so also
       force min-width:0 here (belt-and-suspenders with the base .bubble rule above). */
    .bubble.you, .bubble.bot { width: auto; max-width: 100%; min-width: 0; }
    /* ── IB-02 · PART 4 — RECLAIM THE DEAD SPACE ──────────────────────────────────────────
       On a phone the composer sits at the BOTTOM of the viewport with only the safe-area inset
       beneath it. The 10px that used to be added on top of that inset was a band of nothing under
       the one row he types into, on the screen where vertical pixels are the scarcest thing there
       is; the row's own 10px of top padding is what separates it from the message above. */
    .chatform { padding: 8px 12px env(safe-area-inset-bottom); gap: 10px; background: #161b22; }
    /* One line when empty, still a 44px thumb target. */
    .chatform textarea { min-height: 44px; }
    .chatform button { min-height: 44px; min-width: 64px; font-size: 15px; font-weight: 600; }
    .chatform .chat-attach { width: 34px; height: 34px; font-size: 15px; }
    .chatform .chat-mic { width: 34px; height: 34px; font-size: 15px; }
    /* Item 18 · tighten the whole bottom stack — enough separation to read, no more. Each of
       these rows is usually EMPTY (no pinned tier, no attachment queued, no granted folder), and
       an empty row with padding is still a band of nothing between the last message and the box. */
    .sticky-bar:empty, .attach-row:empty, .folder-chips:empty, .chat-footer:empty { display: none; }
    .sticky-bar, .folder-chips { padding: 4px 12px; }
    .chat-footer { padding: 2px 12px 0; }
    .turn-bar { padding: 5px 12px; }
    .bact { min-width: 32px; min-height: 32px; font-size: 14px; }
    /* WO-CHAT-THINKING-COLLAPSIBLE: the "🧠 Thinking (tap to expand)" chip gets a real thumb
       target on mobile — it's a tap-to-toggle control, not just decoration. */
    .think-toggle { min-height: 36px; font-size: 12px; }

    /* WO-RAIL-3 BOARD: board lives in the right rail drawer; columns scroll horizontally. */
    .desk-board-head { padding: 10px 14px; }
    .desk-board-body { flex-wrap: nowrap; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch;
                       padding: 10px 14px calc(14px + env(safe-area-inset-bottom)); }
    .board-status-section { flex: 0 0 85vw; min-width: 85vw; scroll-snap-align: start; }
    .board-row { padding: 10px 12px; }
    .board-title { font-size: 14px; }

    /* Shared record cards (Mechanic vehicles + every generic desk record): edge-to-edge cards on
       a narrow phone, wider touch targets on the tab strip. Both class prefixes, one rule set. */
    .vehicle-card,
    .record-card { border-radius: 0; border-left: none; border-right: none; }
    .vehicle-header,
    .record-header { padding: 12px 14px; }
    .vehicle-pane,
    .record-pane { padding: 14px; }
    .vehicle-tab,
    .record-tab { padding: 10px 14px; font-size: 13px; }
    /* The Documents/Deadlines tab strip must scroll rather than wrap on a small phone. */
    .vehicle-tabs,
    .record-tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; }

    /* WO-TASK-ROW-EXPAND (task 203): thumb-friendly expandable task rows. */
    .trow { min-height: 44px; padding: 11px 14px; }
    .taction { min-height: 44px; padding: 0 14px; flex: 1 1 auto; }
    .drow { flex-wrap: wrap; }
    .drow .dk { min-width: 100%; }

    /* ══ IB-1626 · MOBILE PHASE 1 — "59% of the screen before the first card" ═══════════════════
       Measured on Brandon's phone: ten bands of chrome stacked before any RUNNING card, three of
       them truncated mid-word. Four jobs, all phone-only (this file's ≤759px query — desktop is
       untouched everywhere below), building on IB-1459's fixed slots/waiting-chip/printer-slot/
       Coding-first/collapsible-Tasks-strip, which stay exactly as that pass left them. */

    /* ── job 2 · FIVE BANDS BECOME ONE CHIP ROW ────────────────────────────────────────────────
       The .vitals counter chips, the not-working red strip, the .fleet-health chip, and the
       run-main-bar blue banner are hidden outright — their facts now render as chips inside
       .phone-glance's own headline (renderStatusChipRow(), dashboard.html), so nothing here is
       lost, just relocated into the one row. Equal-specificity class selectors below win over
       their base (non-≤759px) counterparts by source order — this block loads after all of them. */
    .vitals, .fleet-health, #not-working-bar, #run-main-bar { display: none; }
    /* job 3 · "the '7 jobs in production and 38 coding tasks on the board' line is already in the
       chips" — deleted outright rather than CSS-hidden-but-still-there, since it says nothing a
       phone still needs once the chip row exists. */
    .work-summary { display: none; }
    /* The headline becomes a wrapping row of small pill chips instead of one big sentence — see
       buildStatusChipRowHtml()/renderStatusChipRow() in dashboard.html for what fills it. */
    .pg-headline.status-chip-row { display: flex; flex-wrap: wrap; gap: 6px; font-size: 13px; font-weight: 600; color: inherit; }
    .status-chip { display: inline-flex; align-items: center; padding: 5px 10px; border-radius: 999px;
                    border: 1px solid #30363d; background: #0d1117; color: #c9d1d9; font: inherit;
                    white-space: nowrap; }
    button.status-chip { cursor: pointer; min-height: 40px; }
    button.status-chip:hover, button.status-chip:active { background: #1c2129; }
    .status-chip.sc-running { color: #3fb950; border-color: #2c7c40; }
    .status-chip.sc-amber { color: #d29922; border-color: #9e6a03; }
    .status-chip.sc-red, .status-chip.sc-warn { color: #f85149; border-color: #b62324; }
    .status-chip.sc-offmain { color: #58a6ff; border-color: #1f6feb; }

    /* ── job 3 (cont'd) · an empty lane renders nothing, not an empty box ─────────────────────── */
    .clane.clane-empty { display: none; }

    /* ── job 4 · THE FOUR NAV TILES LEAVE THE TOP ──────────────────────────────────────────────
       Work/Map/My Day/More used to be a static row (~95px with the wrap this file already forces
       at phone widths). #nav-toggle-btn is the one control that replaces them; #main-tabs becomes
       its dropdown (JS: toggleNavMenu(), dashboard.html) — same buttons, same onclicks, just
       repositioned, so getting to a tab is still one tap to open + one to pick, same cost the
       existing More menu already has. .action-strip gets position:relative here (phone-only) so
       the dropdown anchors to the strip rather than the sticky <header>'s own containing block. */
    .action-strip { position: relative; }
    .nav-toggle-btn { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto;
                       min-height: 44px; padding: 6px 10px; border-radius: 6px; background: #161b22;
                       border: 1px solid #30363d; color: #e6edf3; font-size: 12px; font-weight: 600;
                       cursor: pointer; order: -1; }
    .nav-toggle-btn:hover, .nav-toggle-btn:active { background: #1c2129; }
    .nav-toggle-caret { font-size: 10px; color: #8b949e; }
    #main-tabs.main-tabs { display: none; position: absolute; top: calc(100% + 6px); left: 0;
                            z-index: var(--z-dropdown); flex-direction: column; align-items: stretch;
                            gap: 2px; min-width: 200px; padding: 6px; background: #161b22;
                            border: 1px solid #30363d; border-radius: 8px;
                            box-shadow: 0 10px 28px rgba(0,0,0,.55); }
    #main-tabs.main-tabs.nav-open { display: flex; }
    #main-tabs.main-tabs .astrip-btn { flex-direction: row; justify-content: flex-start; width: 100%;
                                        min-height: 44px; }
    #main-tabs.main-tabs .astrip-lb { display: inline; font-size: 13px; }
    #main-tabs.main-tabs .more-tab-wrap { width: 100%; }
    #main-tabs.main-tabs .more-tab-wrap .astrip-btn { width: 100%; }
    /* The More sub-menu already anchors off .more-tab-wrap (IB-1434) — inside the now-absolute
       #main-tabs dropdown that's still true, it just opens a second layer beside/below the first. */
    #main-tabs.main-tabs .more-menu { z-index: var(--z-dropdown); }

    /* ── job 1 · CHROME COLLAPSES ON SCROLL ────────────────────────────────────────────────────
       body.chrome-collapsed is toggled by applyChromeCollapse() (dashboard.html) off the window
       scroll position. #not-working-bar/#run-main-bar are already display:none on phone (job 2,
       above) so they don't need a collapse rule of their own — only <header> and .phone-glance are
       still on-screen for this to affect. Each keeps its normal in-flow box (position:sticky still
       needs one to stick to) and is clamped to zero height instead of display:none, so the slide is
       an animated max-height transition, not a jump — "do it first and do it smoothly".
       A transition needs two concrete numbers to interpolate between — max-height:none (the
       normal, content-driven height both have today) can't animate straight to 0, it would just
       jump. The generous caps below sit well above any realistic expanded state (header's
       coder-live terminal is user-resizable) so normal, uncollapsed content is never clipped; they
       only start doing anything once body.chrome-collapsed clamps them to 0. */
    header { max-height: 100vh; transition: max-height .22s ease, padding .22s ease, border-width .22s ease, opacity .18s ease; }
    .phone-glance { max-height: 1200px; transition: max-height .22s ease, padding .22s ease, margin .22s ease, border-width .22s ease, opacity .18s ease; }
    body.chrome-collapsed header {
      max-height: 0; overflow: hidden; padding-top: 0; padding-bottom: 0; border-bottom-width: 0; opacity: 0;
    }
    body.chrome-collapsed .phone-glance {
      max-height: 0; overflow: hidden; padding: 0; margin: 0; border-width: 0; opacity: 0;
    }
  }

  /* ── Tool Rail: slide-out drawer on tablet/phone (<1000px) ───────────────────────────────
     The rail is hidden off-canvas by default; a header button slides it over the chat. */
  @media (max-width: 999px) {
    .chatpanel { margin: 0 14px 16px; }
    .tool-rail {
      position: fixed; top: var(--header-h); right: 0; bottom: 0; width: min(88vw, 360px); margin: 0;
      border-radius: 0; z-index: var(--z-modal-raised); transform: translateX(100%); transition: transform .2s ease;
      padding-top: env(safe-area-inset-top);
    }
    .tool-rail.drawer-open { transform: translateX(0); }
    .tool-rail-header { padding-top: calc(10px + env(safe-area-inset-top)); }
    .tool-rail-title { flex: 1; }
    /* The ✕ is the drawer's only in-panel way out, so it's a real thumb target here (44px), not
       the 28px desktop glyph. */
    .tool-rail-close { display: inline-flex; align-items: center; justify-content: center;
                       min-width: 44px; min-height: 44px; font-size: 18px; }
    .tool-rail-collapse { display: none; }
    .tool-rail-dock { padding-bottom: env(safe-area-inset-bottom); }

    /* T97 (mobile-desk-sidebar-fix): `collapsed` is a DESKTOP affordance — a 36px edge strip with
       its body hidden and vertical tabs. It is saved PER DESK in localStorage, so a desk Brandon
       collapsed on the Mac used to open on his phone as a 36px sliver pinned to the right edge,
       with no body, no ✕ and no way to widen it: the "sidebar is off-screen and unreachable" bug.
       Below 1000px there is no collapsed rail — the drawer is always the full panel. initToolRail
       also refuses to apply the class here; these rules are the belt to that braces, so a stale
       class from any path (a desktop→phone resize, a restored session) can't shrink the drawer. */
    .tool-rail.collapsed { width: min(88vw, 360px); }
    .tool-rail.collapsed .tool-rail-header { padding: 10px 14px;
                                             padding-top: calc(10px + env(safe-area-inset-top));
                                             justify-content: space-between; }
    .tool-rail.collapsed .tool-rail-title { display: block; }
    .tool-rail.collapsed .tool-rail-close { display: inline-flex; }
    .tool-rail.collapsed .tool-rail-body { display: flex; }
    .tool-rail.collapsed .tool-rail-dock { border-top: 1px solid #21262d; }
    .tool-rail.collapsed .tool-rail-tabs { flex-direction: row; padding: 0 12px; gap: 2px; }
    .tool-rail.collapsed .tool-rail-tab { padding: 8px 12px; font-size: 12px;
                                          border-left: none; border-bottom: 2px solid transparent; }
    .tool-rail.collapsed .tool-rail-tab.active { border-left-color: transparent;
                                                 border-bottom-color: #58a6ff; }
    .tool-rail.collapsed .tool-rail-google { display: flex; }

    /* Everything inside the drawer is thumb-sized. The rail's desktop metrics (30–36px rows, 32px
       tabs) are mouse metrics; on a phone the drawer IS the only place these controls exist, so
       Approve/Reject, + teach, + add note, Attachments and New chat all get a 44px target. The tab
       strip scrolls sideways rather than cramming six tabs into 343px (same treatment as the
       record/vehicle tabs above). */
    .tool-rail-tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
    .tool-rail-tabs::-webkit-scrollbar { display: none; }
    .tool-rail-tab { min-height: 44px; flex: 0 0 auto; }
    .tool-rail-body .info-btn,
    .tool-rail-body .pin-add,
    .tool-rail-body .act-btn,
    .tool-rail-body .act-toggle { min-height: 44px; }
    .trg-btn { width: 36px; height: 36px; }
    .tool-rail-backdrop {
      display: block; position: fixed; inset: 0; z-index: var(--z-modal-scrim); background: rgba(0,0,0,.45);
      opacity: 0; pointer-events: none; transition: opacity .2s ease;
    }
    .tool-rail-backdrop.on { opacity: 1; pointer-events: auto; }
    /* The drawer's ONLY entry point. It carries a 📋 mark (::before, so buildChatOverflow's
       `textContent` label stays just the desk name) and reads as a real button, not a chip. */
    .tool-rail-toggle {
      display: inline-flex; align-items: center; justify-content: center; gap: 5px;
      background: #21262d; color: #c9d1d9; border: 1px solid #30363d; border-radius: 8px;
      padding: 6px 11px; min-height: 38px; font-size: 12px; font-weight: 600; cursor: pointer;
      margin-left: auto; margin-right: 8px; max-width: 45%;
    }
    .tool-rail-toggle::before { content: '📋'; font-size: 14px; line-height: 1; }
    .tool-rail-toggle > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .tool-rail-toggle:hover { border-color: #58a6ff; color: #e6edf3; }
    .tool-rail-toggle:active { background: #30363d; }
  }

  /* ── phone top bar (task 19, Part B → WO-30 task 67 → WO-HEADER-INSANE-BRAIN): ≤430px ──────
     The ops controls already live in the ⋯ overflow menu on every width (WO-30). Here we just
     tighten the brand mark + give ⋯ a thumb-sized target. Manager chat, Pause + the agents ☰
     stay on the bar; the search box already dropped to its own row at ≤759px above. */
  @media (max-width: 430px) {
    /* Mobile-first declutter (390px iPhone): tighter header padding and smaller controls so
       nothing overlaps or gets cut off, while keeping every tap target usable. */
    header { padding-left: calc(10px + env(safe-area-inset-left));
             padding-right: calc(10px + env(safe-area-inset-right)); }
    .brand { gap: 6px; }
    .wordmark { font-size: 12px; }
    .ib-logo { width: 24px; height: 24px; }
    /* IB-1423 · HOTFIX: IB-UX-1 hid the ⋯ kebab at every width on the theory that its routes moved
       to the action strip — but Merge never got a strip tab, so #m-merge (its phone-only twin,
       already owner-gated by renderMergeTrayBtn) lost its only door on a phone. Bring the kebab
       back at this one breakpoint only; the ≤759px hide above still applies to tablet. */
    .overflow-wrap { display: inline-flex; order: 4; flex: 0 0 auto; }
    .overflow-btn { min-width: 44px; min-height: 44px; font-size: 20px; }
    /* Vitals: single scrolling row of compact pills so they never wrap onto the search row. */
    .vitals { gap: 3px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
    .vitals::-webkit-scrollbar { display: none; }
    .vpill { padding: 3px 5px; }
    .vpill .vn { font-size: 12px; }
    /* Search and bar buttons: less horizontal padding, same 44px tap height. */
    .hdr-search { min-height: 36px; }
    .cmdbox { padding: 7px 10px; font-size: 14px; }
    #allbtn, #managerbtn { width: 30px; height: 30px; padding: 0; }
    /* Action strip: one tidy horizontally-scrolling row of icon-only buttons + provider pills.
       No wrapping, no jammed buttons — just flick left/right if needed. */
    .astrip-lb { display: none; }             /* action-strip icon-only fallback (req 2) */
    .astrip-btn { min-width: 34px; min-height: 34px; padding: 4px 7px; justify-content: center; }
    .astrip-ic { font-size: 15px; }
    .action-strip { flex-wrap: nowrap; overflow-x: auto; gap: 4px; padding: 4px 0; scrollbar-width: none; }
    .action-strip::-webkit-scrollbar { display: none; }
    .action-strip .usage-bars { flex-basis: auto; flex-wrap: nowrap; gap: 4px; }
    .action-strip .usage-bars .ubar { padding: 3px 5px; min-height: 28px; font-size: 10px; }
    .action-strip .usage-bars .ubar-track { display: none; }
    /* WO-MOBILE-HUD-STRIP: the coder tiles + the wide (120px) printer card both live in
       #coder-cluster-cards and crowd this one scrolling row; the printer card's absolute-positioned
       %/badge/time/bar internals then overlap the coder brain tags into garbled text. Collapse the
       whole cluster into ONE compact status line (renderHudMobileStatus → "Coders n/4 · <printer>
       <pct>% · live/offline"): hide the head + card slot, reveal the line. One non-wrapping,
       ellipsized span — nothing inside it can collide. Desktop is untouched (the line is
       display:none above 430px). */
    .coder-cluster-head, .coder-cluster-cards, .printer-cluster { display: none; }
    .hud-mobile-status { display: inline-block; flex: 0 1 auto; min-width: 0; max-width: 100%;
                         padding: 4px 8px; font-size: 11px; font-weight: 600; color: #c9d1d9;
                         white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    /* WO-HEADER-HUD-POLISH (task 387, req 3): Merge stays on the strip at desktop/tablet but
       moves into the ⋯ kebab at phone width. (T46 retired the Brain strip button — brain
       selection now lives in Settings → Brains + each desk's Capabilities panel.) Console has
       moved into the kebab permanently under the new DEVELOPER section. */
    #astrip-merge { display: none !important; }
    /* Coder HUD strip: compact horizontal scroll, max ~4 cards visible at 390px. */
    .coder-cards { flex-wrap: nowrap; overflow-x: auto; gap: 5px; padding: 4px 0; scrollbar-width: none;
                   min-height: auto; }
    .coder-cards::-webkit-scrollbar { display: none; }
    /* TIB-081: `width: 72px` alone never took effect — the ≤759px rule above sets `flex: 1 1 0`
       (a zero basis, free to shrink) plus `min-width: 0`, and a flex basis beats a width. Inside
       the desk chat's own strip, four coder tiles next to a 120px printer card were measured at
       TEN pixels wide each: four unreadable slivers stacked above every conversation. The row is
       already nowrap + overflow-x:auto here, so the tile just needs to hold its size and let the
       row scroll — which is what that rule was written to do.
       The override is its OWN declaration rather than another property inside the size rule
       because task 498's suite reads that rule as an exact string ("coder cards: mobile
       breakpoint updated consistently"). Same specificity, later in source, so it still wins —
       and the sizes stay where that suite can read them. */
    .coder-card { width: 72px; height: 56px; padding: 3px 4px; gap: 2px; border-radius: 6px; }
    .coder-card { flex: 0 0 auto; }
    /* IB-1345 · matches the 56px height set above — own declaration on purpose, see the comment
       above this block. */
    .coder-card { max-height: 56px; overflow: hidden; }
    .coder-card.cc-bambu { width: 120px; height: 56px; padding: 0; }
    .printer-card { width: 124px; height: 70px; padding: 4px 5px 5px; }
    .printer-card.pc-idle, .printer-card.pc-offline { width: 124px; height: 36px; padding: 0 5px; }
    .cc-num { font-size: 16px; }
    .cc-brain-tag { top: 2px; right: 4px; font-size: 6px; }
    .cc-task { font-size: 8px; }
    .cc-timer { font-size: 7px; }
    .cc-dot { width: 6px; height: 6px; }
    .cc-lock { top: 2px; left: 4px; font-size: 8px; }
    /* Chat composer: compact icons, auto-growing textarea so typing on a phone is comfortable.
       IB-02 (part 4): no extra band under the row — the safe-area inset IS the bottom gap. */
    .chatform { padding: 8px 12px env(safe-area-inset-bottom); gap: 8px; }
    .chatform textarea { min-height: 44px; padding: 10px 12px; --composer-chrome: 22px; }
    .chatform button { min-height: 44px; min-width: 64px; }
    .chatform .chat-attach { width: 32px; height: 32px; font-size: 14px; }
    .chatform .chat-mic { width: 32px; height: 32px; font-size: 14px; }

    /* ── WO-30 · Provider-accounts modal on a phone ──────────────────────────
       Give the modal the width back (less side gutter) so cards go full-width,
       then re-stack each Accounts / Connectors / Assignment card so the badge +
       name + active dot sit on one clean line, the detail reads below it, and the
       action buttons wrap to their own row with real ≥44px tap targets. */
    /* IB-1437 · :not(.map-modal) — this padding is the WO-30 sheet gutter (Accounts/Connectors
       cards get room to breathe). .map-modal opts out and stays the full-bleed dedicated view its
       base rule already declares (padding: 0): same specificity + later source used to let this
       leak back in as ~12-70px of blank space above the map's own head, read on a 390px phone as
       "roughly 250 pixels of nothing" between the fleet line and the Brain Map heading. */
    .modal:not(.map-modal) { padding: 12px; padding-top: calc(12px + env(safe-area-inset-top)); }
    .sheet { padding: 16px 14px 22px; border-radius: 12px; }

    /* WO-TTS-VOICE-PICKER (task 213): real ≥44px tap targets on the Play button + rows. */
    .voice-play { width: 40px; height: 40px; }
    .voice-row { padding: 11px 12px; }

    /* Accounts + Desk/Tier assignment rows (both use .acct): full-width card,
       comfortable padding, badge/name/dot on top, detail below, acts on own row. */
    .acct { flex-wrap: wrap; align-items: flex-start; gap: 6px 10px; padding: 12px 14px; }
    .acct .who { flex: 1 1 auto; min-width: 0; }
    .acct .nm { font-size: 14px; line-height: 1.35; overflow-wrap: break-word; word-break: normal; }
    .acct .sub { font-size: 12px; line-height: 1.5; overflow-wrap: break-word; word-break: normal; }
    .acct .acts { flex-basis: 100%; width: 100%; margin-top: 4px; flex-wrap: wrap; gap: 8px; }
    .acct .acts button { flex: 1 1 auto; min-height: 44px; padding: 0 14px; font-size: 13px; }

    /* Routing rows: target on top line, brain badge + detail on second, Change button full-width below. */
    .routing-row { flex-wrap: wrap; gap: 4px 8px; padding: 12px 14px; }
    .routing-target { flex: 1 1 100%; font-size: 14px; line-height: 1.35; min-width: 0; }
    .routing-arrow { display: none; }
    .routing-brain { font-size: 12px; padding: 4px 9px; }
    .routing-detail { flex: 1 1; font-size: 12px; overflow-wrap: break-word; white-space: normal; }
    .routing-remove { flex: 1 1 100%; min-height: 44px; margin-top: 2px; }

    /* Brains cards: single column, full-width cards, thumb-sized actions. */
    .brains-grid { grid-template-columns: 1fr; gap: 12px; }
    .brain-card { padding: 12px; }
    .brain-actions button { min-height: 44px; font-size: 13px; }
    .brains-add { flex-direction: column; }
    .brains-add select, .brains-add input, .brains-add button { width: 100%; min-width: 0; min-height: 44px; }

    /* Connectors (Google) card: service line up top, action buttons on their own row. */
    .conn-row { flex-wrap: wrap; align-items: flex-start; gap: 6px 8px; padding: 10px 0; }
    .conn-svc { min-width: 0; }
    .conn-sub { flex: 1 1 100%; overflow-wrap: break-word; word-break: normal; }
    .conn-acts { flex-basis: 100%; width: 100%; margin-left: 0; margin-top: 4px; flex-wrap: wrap; gap: 8px; }
    .conn-acts button { flex: 1 1 auto; min-height: 44px; padding: 0 14px !important; font-size: 13px; }
    .conn-connect { min-height: 44px; }

    /* Vault card "Re-index now" — same thumb-sized target on a phone. */
    .vs-head { flex-wrap: wrap; gap: 6px; }
    .vs-reindex { min-height: 44px; margin-left: auto; }

    /* CAP-UI on a phone: the per-desk panel is already stacked and full-width (no matrix, no
       horizontal scroll) — just keep the toggle a real thumb target and drop the Switch-desk
       picker onto its own full-width row under the desk name. */
    .cap-toggle { width: 44px; height: 26px; }
    .cap-toggle::after { width: 22px; height: 22px; }
    .cap-toggle.on::after { transform: translateX(18px); }
    .deskcap-pickwrap { flex: 1 1 100%; }
    .deskcap-pick { width: 100%; }

    /* Settings hub: full-width sheet on a phone and slightly smaller tab text so tabs scroll
       instead of wrapping messily. */
    .settings-sheet { padding: 16px 12px 22px; }
    .settings-tabs { margin: 12px -12px 14px; padding: 0 12px; }
    .settings-tab { padding: 7px 10px; font-size: 12px; min-height: 38px; }

    /* Overview tab on mobile: stack cards in a single column for better readability. */
    .overview-metrics { grid-template-columns: 1fr; }
    .overview-health { grid-template-columns: 1fr 1fr; }

    /* WO-MOBILE-CHAT-HEADER-2 (fix/mobile-chat-header): the desk chat sub-header collapses to
       ONE clean non-wrapping row at phone width. The full "Chat · Label (role)" title + model pill
       are dropped (redundant on a phone) and the Info rail toggle moves into the ⋯ overflow.
       Desktop + tablet (≥431px) keep the full header.
       WO-DESK-INFO-SIDEBAR (task T33): the row is now just [✕] [desk name…] [⋯] — Attachments /
       New chat / Compact / Stop / the context meter no longer live in this header at all (they
       moved to the rail's Info tab), so the icon-square + hide-off-the-row rules they needed here
       are gone with them. buildChatOverflow still surfaces every one of them in the ⋯ menu. */
    .chatpanel.on #chat-title { flex-wrap: nowrap; gap: 6px; }
    .chatpanel.on #chat-title-text,
    .chatpanel.on #chat-title-model { display: none; }
    .chatpanel.on #chat-title-text-mobile {
      order: 1; flex: 1 1 auto; min-width: 0;
      display: inline-block;
      font-size: 15px; font-weight: 600; text-transform: none; letter-spacing: 0;
      color: #e6edf3; text-align: left;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    /* T97 (mobile-desk-sidebar-fix): the rail toggle used to be display:none !important here —
       the drawer's only trigger, hidden on the one width where the rail is a drawer, leaving the
       whole sidebar (model, context, Approvals, Desk memory) reachable only by knowing that a row
       inside the ⋯ menu opens it. It is back on the row as a 44px icon square, sized and shaped
       like the ✕ and ⋯ that flank it, so the phone header reads [✕] [desk name] [📋] [⋯]. The ⋯
       still lists it (buildChatOverflow) — that stays a second path, not the only one. */
    .chatpanel.on #tool-rail-toggle {
      order: 3; display: inline-flex; flex: none; width: 44px; min-width: 44px; height: 44px;
      min-height: 44px; padding: 0; margin: 0; max-width: none; border-radius: 8px;
    }
    .chatpanel.on #tool-rail-toggle::before { font-size: 18px; }
    /* icon-only on a phone: the desk name is already the title beside it */
    .chatpanel.on #tool-rail-toggle-text { display: none; }
    /* the phone-only ⋯ overflow (display:none in the base rule; revealed only here) */
    .chatpanel.on .chat-overflow-wrap { order: 4; display: inline-flex; position: relative; margin: 0; }
    .chatpanel.on .chat-overflow-btn {
      width: 44px; min-width: 44px; height: 44px; padding: 0; font-size: 18px; line-height: 1;
    }

    /* ── WO-MOBILE-SPACING-SCROLL (fix/mobile-spacing-scroll · layout fix #3) ─────────
       General ≤430px spacing + scroll pass. Desktop and tablet (≥431px) are UNTOUCHED —
       every rule below is scoped to this one phone media block.
       (1) The page never scrolls sideways: body clips stray overflow and every row that owns a
           horizontal scroll is capped to the viewport so its content can't stretch the page wider.
       (2) The composer stays pinned just above the keyboard with a real env(safe-area-inset-bottom)
           gap; the input + Send are always reachable while typing.
       (3) The chat message area and the Dashboard/Tasks list get consistent comfortable padding,
           and the Ops cards stop nesting their own scrollbox — one page scrollbar, not two.
       (4) The top counters (.vitals) and the model-pills row (.usage-bars) scroll inside a bounded
           row instead of crowding the icon buttons or riding off the right edge. */

    /* (1) No horizontal overflow / sideways scroll. body is the single horizontal clip; the
           scrollable rows + the flexible chat column are max-width:100% + min-width:0 so flex/grid
           children can shrink below their content's min-size and scroll WITHIN the row, never the
           page. min-width:0 is the key clamp — without it a nowrap flex child forces its parent
           (and ultimately body) wider than the viewport. */
    body { overflow-x: hidden; }
    .shell, .main { max-width: 100%; min-width: 0; }
    .vitals, .action-strip, .usage-bars, .coder-cards,
    .chatlog-wrap, .chatlog { max-width: 100%; min-width: 0; }
    /* Long unbreakable content (code blocks, URLs, base64 tokens) wraps or scrolls inside its
       bubble instead of forcing the column — and therefore the page — wider. */
    .bubble { max-width: 100%; min-width: 0; }
    .bubble .btext { overflow-wrap: anywhere; }
    .chatlog pre, .chatlog code, .chatlog img { max-width: 100%; overflow-x: auto; }

    /* (2) Composer pinned above the keyboard: it is the LAST, non-flexible (flex:0 0 auto) row of
           the full-height .chatpanel.on column, so the chatlog absorbs all leftover height and the
           form can never be pushed off-screen. Its bottom padding clears the home indicator / the
           raised keyboard via env(safe-area-inset-bottom); the column's horizontal safe-area insets
           already live on .chatpanel.on itself, so only the bottom gap is re-stated here.
           IB-02 (part 4, item 17): the bottom padding is now the inset ALONE. The +10px on top of
           it was the empty band under the composer — on a device with no inset (and every phone in
           portrait with the URL bar showing) it was simply ten dead pixels at the bottom of the
           screen, below the last control on it. */
    .chatpanel.on .chatform { flex: 0 0 auto; width: 100%;
                              padding-bottom: env(safe-area-inset-bottom); }
    /* IB-123: the chat pane itself is the flex column on a phone. Without this the log could grow
       past the fixed full-screen panel and push the composer below the keyboard/home indicator;
       here the log absorbs leftover height and the composer stays pinned at the bottom. */
    .chatpanel.on { overflow: hidden; }
    #chat-pane { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
    #chat-pane > .va-status-card, #chat-pane > .attachments-panel,
    #chat-pane > .sticky-bar, #chat-pane > .attach-row, #chat-pane > .chat-footer,
    #chat-pane > .folder-chips, #chat-pane > .turn-bar,
    #chat-pane > .chatform { flex: none; }
    #chat-pane > .chatlog-wrap { flex: 1 1 auto; min-height: 0; overflow: hidden; position: relative; }

    /* (3) Comfortable, consistent padding on the two main mobile views. The chatlog's side gutter
           lines up with the composer's (12px) so message bubbles and the input share one edge;
           bubbles already fill the width via max-width:100% above — nothing is jammed against
           the screen edge. */
    .chatlog { padding: 14px 12px; }
    /* No double scrollbar: the Ops Dashboard / Tasks cards stop being their own 360/480px nested
           scrollbox at phone width — their content flows into the single page scroll (the card
           border still frames it). Collapsed cards are unaffected. */
    .panel .body, .panel-tasks .body { max-height: none; overflow: visible; }

    /* (4) Top counters + model pills: the rows are already nowrap + overflow-x:auto further up
           this block; these clamps bound them so the R/Q/D pills and the GLM/Kimi/Nemotron meter
           pills scroll cleanly WITHIN their row instead of blowing it past the viewport. */
    .vitals, .action-strip .usage-bars { min-width: 0; max-width: 100%; }

    /* IB-141 · at phone width the Work board pane shrinks so the board + Tasks panel both fit above
       the fold; the Tasks body stays short and scrolls. */
    .work-pane.active { max-height: 240px; }
    .work-tasks-panel .panel-tasks .body { max-height: 180px; }
  }

  /* WO-HEADER-HUD-POLISH (task 387, req 3): #m-merge is the phone-only twin of the action-strip
     Merge button — hidden above 430px so it never duplicates the strip on tablet/desktop. (T46
     retired the Brain button and its #m-brain twin.) Console has moved into the kebab permanently,
     so #m-console stays visible at every width. !important beats the inline display the owner-
     gating JS sets on #m-merge. */
  @media (min-width: 431px) {
    #m-merge { display: none !important; }
  }

  /* ── WO-RIGHT-NOW-COCKPIT (task 346): the "one next action" home screen ────────────────────
     Mobile-first full-screen overlay, same trick as .focus-overlay/.morning-overlay/.shutdown-
     overlay above — deliberately CALM (neutral slate, no alarm red, no evening indigo): this is
     the everyday landing screen, not a pager alert. ONE headline, a short "waiting on you" list
     (never a wall of tasks — src/rightNow.js caps it), and a compact fleet-pulse strip. Checked
     on every boot (checkRightNow(), near init()) but only auto-opens once per browser session per
     distinct headline (sessionStorage) so it never nags on a tick()/reload with nothing new to
     say — reachable any time via the action-strip "Right Now" button (always force-opens fresh). */
  .rightnow-overlay { display: none; position: fixed; inset: 0; z-index: var(--z-modal); background: #0d1117;
                       flex-direction: column; align-items: center; justify-content: center;
                       padding: 24px; padding-top: calc(24px + env(safe-area-inset-top));
                       padding-bottom: calc(24px + env(safe-area-inset-bottom));
                       padding-left: calc(24px + env(safe-area-inset-left));
                       padding-right: calc(24px + env(safe-area-inset-right)); }
  .rightnow-overlay.on { display: flex; }
  .rn-card { display: flex; flex-direction: column; gap: 14px; width: 100%; max-width: 440px;
             max-height: calc(100vh - 48px); overflow-y: auto; /* WO-NOTIF-RENDER-DISMISS: many grouped cards can exceed the viewport */
             background: #161b22; border: 1px solid #30363d; border-radius: 16px; padding: 24px 22px; }
  .rn-kicker { font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: #8b949e; }
  /* WO-NOTIF-RENDER-DISMISS (task 443): the grouped cockpit. Each category is its own labeled
     section; each item is its OWN card — never several items mashed into one. Sections render in
     priority order (Needs your decision > VA deadline > Blocked > Commitments > Printer), so a
     loud printer can never bury a real to-do. */
  .rn-groups { display: flex; flex-direction: column; gap: 16px; }
  .rn-group { display: flex; flex-direction: column; gap: 6px; }
  .rn-group-h { display: flex; align-items: center; gap: 7px; font-size: 11px; font-weight: 700;
                letter-spacing: .06em; text-transform: uppercase; }
  .rn-group-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
  .rn-group-label { white-space: nowrap; }
  .rn-group-count { color: #6e7681; font-weight: 600; }
  /* WO-RIGHT-NOW-COMMITMENTS (task T17): per-section "Clear all", pushed to the right of its header. */
  .rn-group-clear { margin-left: auto; background: transparent; color: #8b949e; border: 1px solid #30363d;
                    border-radius: 6px; padding: 3px 8px; font-size: 10px; font-weight: 700; letter-spacing: .04em;
                    cursor: pointer; white-space: nowrap; }
  .rn-group-clear:hover { color: #e6edf3; }
  .rn-item { background: #0d1117; border: 1px solid #21262d; border-radius: 10px; padding: 11px 13px; }
  .rn-item-clickable { cursor: pointer; } /* IB-1450 · the whole card, not just the small Open pill, is a tap target */
  .rn-item-printer { opacity: .82; } /* demoted printer noise — present but visually quiet (works w/ #439 cap + #442 runout demote) */
  .rn-item-title { font-size: 15px; font-weight: 600; line-height: 1.35; color: #e6edf3; overflow-wrap: break-word; }
  .rn-item-detail { font-size: 13px; line-height: 1.45; color: #8b949e; white-space: pre-wrap; overflow-wrap: break-word;
                    max-height: 16vh; overflow-y: auto; margin-top: 5px; }
  .rn-item-img { width: 100%; max-height: 160px; object-fit: contain; border-radius: 6px; background: #000; margin-top: 8px; }
  .rn-item-acts { display: flex; gap: 8px; margin-top: 9px; }
  .rn-item-acts > * { font-size: 13px; font-weight: 600; border-radius: 7px; min-height: 34px; padding: 0 12px;
                      display: inline-flex; align-items: center; justify-content: center; cursor: pointer; text-decoration: none; }
  .rn-item-open { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; }
  .rn-item-open:hover { color: #58a6ff; }
  .rn-item-dismiss { background: transparent; color: #8b949e; border: 1px solid #30363d; }
  .rn-item-dismiss:hover { color: #e6edf3; }
  .rn-more { font-size: 12px; color: #6e7681; padding-left: 4px; }
  .rn-clear { font-size: 16px; color: #6e7681; padding: 18px 4px; text-align: center; }
  .rn-pulse { font-size: 12px; color: #6e7681; border-top: 1px solid #21262d; padding-top: 10px; }
  /* TIB-078 §5 · the quiet line on the panel a phone opens on. Deliberately the calmest thing in
     the cockpit: no dot, no count chip, no control — work that is waiting is not a decision. */
  .rn-queue-line { font-size: 12px; line-height: 1.45; color: #6e7681; padding-top: 6px; }
  .rn-acts { display: flex; gap: 8px; }
  .rn-acts button { flex: 1 1 auto; min-height: 46px; font-size: 14px; font-weight: 600; border-radius: 8px; border: none; cursor: pointer; }
  .rn-dismiss-all { background: #21262d; color: #e6edf3; border: 1px solid #30363d; }
  .rn-dismiss { background: #21262d; color: #c9d1d9; }

  /* WO-APPROVAL-TAKEOVER-ALERT (task 324), redesigned by IB-116 · "nothing takes the screen".
     This used to be a full-viewport backdrop (position:fixed; inset:0) that covered whatever view
     was open, with a centered pager-style card. Only money and a machine that could hurt someone or
     wreck a part can reach this panel at all now (src/notify/interruptAllowlist.js refuses every
     other type at the write) — and even those two "are a line he taps, not a sheet that lands on
     him". So the panel is now a corner-anchored, content-sized card: no inset:0, no backdrop, no
     centering. The rest of the dashboard stays visible and interactive underneath it. */
  #alert-takeover {
    display: none;
    position: fixed;
    top: calc(var(--header-h, 52px) + 8px);
    right: 8px;
    left: auto;
    z-index: var(--z-emergency);
    width: min(420px, calc(100vw - 16px));
    max-height: min(78vh, 720px);
    padding-bottom: calc(8px + env(safe-area-inset-bottom));
    padding-right: calc(8px + env(safe-area-inset-right));
  }
  #alert-takeover.show { display: block; }
  .at-card {
    width: 100%;
    background: #1a1114; border: 2px solid #f85149; border-radius: 14px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5), 0 0 18px rgba(248, 81, 73, 0.28);
    padding: 22px 20px; color: #e6edf3;
    animation: at-pulse 1.6s ease-in-out infinite;
  }
  @keyframes at-pulse { 0%, 100% { box-shadow: 0 0 24px rgba(248,81,73,0.25); } 50% { box-shadow: 0 0 46px rgba(248,81,73,0.55); } }
  @media (prefers-reduced-motion: reduce) { .at-card { animation: none; } }
  .at-kicker { font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: #f85149; margin-bottom: 8px; }
  .at-title { font-size: 19px; font-weight: 700; line-height: 1.35; margin-bottom: 10px; }
  .at-detail { font-size: 14px; line-height: 1.5; color: #c9d1d9; white-space: pre-wrap; overflow-wrap: break-word; max-height: 30vh; overflow-y: auto; margin-bottom: 16px; }
  .at-meta { font-size: 12px; color: #8b949e; margin-bottom: 16px; }
  .at-acts { display: flex; gap: 8px; flex-wrap: wrap; }
  .at-acts button { flex: 1 1 auto; min-height: 46px; font-size: 14px; font-weight: 600; border-radius: 8px; border: none; cursor: pointer; }
  .at-approve { background: #2ea043; color: #fff; }
  .at-view { background: #30363d; color: #e6edf3; }
  .at-dismiss { background: transparent; color: #8b949e; border: 1px solid #30363d !important; }
  .at-queue { font-size: 12px; color: #8b949e; text-align: center; margin-top: 10px; }
  /* WO-BAMBU-ALERT-FLOOD (task 439): a one-tap escape hatch shown only when a printer is in the
     queue — clears the whole bambu flood at once so a spamming printer can never trap Brandon in
     the takeover dismissing cards one at a time. */
  .at-clear-flood { width: 100%; margin-top: 10px; min-height: 40px; font-size: 13px; font-weight: 600; border-radius: 8px; border: 1px solid #f85149; cursor: pointer; background: transparent; color: #f85149; }
  /* WO-BAMBU-WATCHDOG (task 403): the captured camera frame a bambu-watchdog-alarm alert carries
     — mobile-first, capped height so a phone's takeover still fits the action buttons on screen. */
  .at-image { width: 100%; max-height: 220px; object-fit: contain; border-radius: 8px; background: #000; margin-bottom: 14px; }
  /* WO-DECISION-HOLD-GUARD (task 330, req 3) shipped a reply textarea on the takeover — "type your
     answer here". IB-116 removed it: only money and machine-safety cards reach this panel now, and
     "he cannot settle any of these by typing at them" — they are a line he taps (Approve/Dismiss/
     View), not a form. A real question from the Manager still holds the task and sets decision_hold;
     he answers it from the task thread when he goes looking, same as everything else that used to
     interrupt and now does not. */
  /* WO-BAMBU-RUNOUT-ALERT (task 442): the QUIET counterpart to the takeover — a small, calm,
     NON-blocking top-center toast for informational notices (filament runout today: the printer
     pauses + waits on its own, so this never hijacks the screen as a "Needs your decision"
     approval). Amber, not red (a heads-up, not an alarm); pinned top-center so it never collides
     with the bottom brain-toast; z-index 9000 sits FAR below the takeover's 99999 so a real
     decision always wins the screen. Hidden until .on (renderQuietAlerts toggles it). */
  .quiet-alert-toast {
    display: none;
    position: fixed; top: 12px; left: 50%; transform: translateX(-50%);
    z-index: var(--z-toast);
    max-width: min(92vw, 460px);
    background: #21180c; border: 1px solid #d29922; border-radius: 10px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
    padding: 10px 12px; color: #e3b341;
    font-size: 13px; line-height: 1.4;
    align-items: center; gap: 10px;
  }
  .quiet-alert-toast.on { display: flex; }
  .quiet-alert-toast .qat-text { flex: 1 1 auto; overflow-wrap: break-word; }
  .quiet-alert-toast .qat-dismiss {
    flex: 0 0 auto; min-height: 30px; padding: 0 10px;
    font-size: 12px; font-weight: 600; border-radius: 6px; cursor: pointer;
    background: transparent; color: #d29922; border: 1px solid #d29922;
  }
  /* WO-NOTIF-RENDER-DISMISS (task 443): the takeover is now a scrollable GROUPED PANEL, not a
     single rotating card — every queued alert renders as its own .at-item under a category header.
     The outer #alert-takeover-card keeps the red "pager alert" frame + pulse (id specificity wins
     over .at-card) but grows to hold a scrolling list so a flood never overflows the viewport. */
  #alert-takeover-card { max-width: 100%; max-height: min(78vh, 720px); overflow-y: auto; display: block; padding: 16px 16px; }
  @media (max-width: 540px) { #alert-takeover-card { max-height: 70vh; } }
  @media (max-width: 540px) { #alert-takeover { left: 8px; right: 8px; width: auto; } }
  .at-panel { display: flex; flex-direction: column; gap: 14px; }
  .at-globalbar { display: flex; align-items: center; justify-content: space-between; gap: 10px; position: sticky; top: -16px; margin: -16px -16px 0; padding: 12px 16px; background: #1a1114; border-bottom: 1px solid #3a2326; z-index: var(--z-raised); }
  .at-globalbar-title { font-size: 14px; font-weight: 700; color: #f85149; }
  .at-dismiss-all { background: transparent; color: #f85149; border: 1px solid #f85149; border-radius: 8px; padding: 8px 12px; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap; }
  .at-groups { display: flex; flex-direction: column; gap: 14px; }
  .at-group { display: flex; flex-direction: column; gap: 8px; }
  .at-group-header { display: flex; align-items: center; gap: 8px; }
  .at-group-label { font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: #c9d1d9; }
  .at-tone-red .at-group-label { color: #f85149; }
  .at-tone-amber .at-group-label { color: #e2c08d; }
  .at-tone-gray .at-group-label { color: #8b949e; }
  .at-group-count { background: #30363d; color: #e6edf3; border-radius: 10px; padding: 1px 8px; font-size: 11px; font-weight: 600; }
  .at-group-dismiss, .at-clear-flood { margin-left: auto; background: transparent; color: #8b949e; border: 1px solid #30363d; border-radius: 6px; padding: 4px 9px; font-size: 11px; font-weight: 600; cursor: pointer; white-space: nowrap; }
  .at-clear-flood { color: #f85149; border-color: #5a3030; margin-left: 6px; }
  .at-group-cards { display: flex; flex-direction: column; gap: 10px; }
  /* One alert = one card (was the old single takeover card). Calmer than the outer frame so the
     items read as a list, not a row of screaming pagers. */
  .at-item { background: #151012; border: 1px solid #3a2326; border-radius: 10px; padding: 13px 14px; }
  .at-item.at-cat-printer { background: #111319; border-color: #2a2f3a; }
  .at-item .at-title { font-size: 15px; font-weight: 700; line-height: 1.35; margin-bottom: 8px; }
  .at-item .at-detail { font-size: 13px; line-height: 1.45; max-height: 18vh; margin-bottom: 10px; }
  .at-item .at-meta { font-size: 11px; color: #8b949e; margin-bottom: 10px; }
  .at-item .at-image { max-height: 180px; margin-bottom: 10px; }
  .at-item .at-acts button { min-height: 40px; font-size: 13px; }
  /* TIB-028: an escalation card's own options. Whitespace is preserved because the card is written
     as four short paragraphs server-side and reads as prose, not as a log line; the technical tail
     is folded into <details> so it is one tap away rather than the first thing on screen. */
  .at-item .at-detail { white-space: pre-wrap; }
  .at-tech { margin: 0 0 10px; font-size: 12px; color: #8b949e; }
  .at-tech summary { cursor: pointer; min-height: 32px; line-height: 32px; color: #a8b0b8; }
  .at-tech div { white-space: pre-wrap; word-break: break-word; margin-top: 4px; }
  .at-acts-esc { flex-wrap: wrap; }
  /* TIB-044: the change's NAME is the first thing on the card and the biggest — "TIB-041 ·
     dead-means-accounted-for", never "a change". Then the four facts as a label/value list, so
     "What the checks said" is on screen next to its answer instead of buried in a paragraph.
     The drawer gets more room than the summary above it: it holds the whole story on purpose. */
  .at-esc-name { font-size: 17px; font-weight: 700; line-height: 1.3; color: #e6edf3; margin-bottom: 10px;
                 overflow-wrap: anywhere; }
  .at-esc-facts { display: grid; grid-template-columns: minmax(0, 11em) 1fr; gap: 4px 12px;
                  margin: 0 0 12px; font-size: 13px; line-height: 1.5; }
  .at-esc-facts dt { color: #8b949e; font-weight: 600; }
  .at-esc-facts dd { margin: 0; color: #e6edf3; overflow-wrap: break-word; }
  @media (max-width: 560px) { .at-esc-facts { grid-template-columns: 1fr; gap: 2px; }
                              .at-esc-facts dd { margin-bottom: 8px; } }
  .at-item .at-tech div { max-height: 46vh; overflow-y: auto; }
  /* A refused button, in a sentence, on the card. It sits directly above the buttons because that
     is where he is looking the instant one of them does not work. */
  .at-esc-note { font-size: 13px; line-height: 1.5; color: #f0d8a8; background: #2b2113;
                 border-left: 3px solid #e2c08d; border-radius: 4px;
                 padding: 8px 10px; margin-bottom: 10px; }
  /* TIB-078 §4 · the way to the notifications page, drawn as a LINK and never as a button. It is on
     the card because that is the only moment anybody changes a notification setting, and it is not a
     button because muting a whole class of card is not an answer to this one. Full-width on its own
     row under the buttons, quiet, and still a 40px tap target. */
  .at-esc-settings { flex-basis: 100%; display: block; margin-top: 2px; padding: 10px 2px;
                     font-size: 12.5px; line-height: 20px; color: #8b949e; text-decoration: underline;
                     text-underline-offset: 3px; cursor: pointer; }
  .at-esc-settings:active { color: #c9d1d9; }
  /* TIB-078 §4 · the receipt for a button that WORKED. The card it was written on is gone by the
     time this shows — that is what a resolving button does — so what actually happened (the desk
     that took the job, where a held job went) has to outlive it. Bottom of the screen, twelve
     seconds, nothing to answer. */
  .card-outcome-toast { position: fixed; left: 12px; right: 12px; z-index: var(--z-toast);
                        bottom: calc(12px + env(safe-area-inset-bottom));
                        display: none; padding: 12px 14px; border-radius: 10px;
                        background: #10261a; border: 1px solid #2ea043; color: #d6f5df;
                        font-size: 13px; line-height: 1.45; box-shadow: 0 6px 24px rgba(0,0,0,.45); }
  .card-outcome-toast.on { display: block; }

  /* WO-PRINTER-ALERT-REDESIGN (task T16): the grouped printer card. One card per MACHINE, a row
     per distinct fault code (with how often it hit), one plain sentence, one explicit instruction.
     Deliberately the calmest card in the panel — a printer fault is real but it is not a decision
     waiting on Brandon, so it must never out-shout a blocked task above it. */
  .at-codes { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
  .at-code-row { display: flex; align-items: baseline; gap: 8px; font-size: 12.5px; line-height: 1.4; }
  .at-code-label { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: #e6edf3; font-weight: 600; }
  .at-code-count { background: #30363d; color: #e6edf3; border-radius: 9px; padding: 0 7px; font-size: 11px; font-weight: 700; }
  .at-code-last { color: #8b949e; font-size: 11.5px; margin-left: auto; white-space: nowrap; }
  /* The one instruction. Given its own block + a left rule so the eye lands on it, not on the hex. */
  .at-do { font-size: 13px; line-height: 1.5; color: #e6edf3; border-left: 3px solid #e2c08d;
           padding: 6px 0 6px 10px; margin-bottom: 12px; }
  .at-do-k { font-weight: 700; color: #e2c08d; }
  /* Everything an engineer would want and a human would not: raw code, hex, HMS halves, timestamps,
     the stored detail line. Hidden until Details is tapped. */
  .at-details { background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 8px 10px;
                margin-bottom: 10px; font-size: 11px; line-height: 1.55; color: #8b949e;
                max-height: 22vh; overflow-y: auto; }
  .at-det-line { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: break-word; }
  .at-det-raw { color: #6e7681; margin: 2px 0 6px; overflow-wrap: break-word; }
  .at-note { margin-top: 10px; font-size: 12px; line-height: 1.45; color: #7ee787; }

  /* ── 3-column resizable desktop shell (>=1000px) ───────────────────────────────
     Left agents rail, center chat, right requests board. Each column scrolls
     independently; the page itself never scrolls. Widths are drag-resizable and
     persisted in localStorage. */
  @media (min-width: 1000px) {
    html, body { height: 100%; overflow: hidden; }
    /* ── TIB-075 §3 · THE SHELL ENDS WHERE THE WINDOW ENDS ────────────────────────────────────
       This used to read `height: calc(100vh - var(--header-h))` — the window, minus a number JS
       measured off the header. That arithmetic is only right while the header is the ONLY thing
       above the shell, and it stopped being right the moment a second row moved in above it (the
       not-working bar). Everything below then hung off the bottom of the window by exactly that
       row's height: the tab dock half-cut at the bottom edge, and the Manager's message box under
       the fold. No drag was needed to reach that state and no drag could leave it.
       So the page stops doing arithmetic about its own height. body is the column, the shell is
       the item that takes what is left, and whatever sits above it — one bar, three, or none —
       is subtracted by the layout itself rather than by a number that has to be kept in step.
       The shell keeps a floor (a chat is a header, a line of conversation and a composer); when
       the window is shorter than the floor plus the bars, the HEADER is what gives and scrolls
       inside itself. The one control that never gives is the one he types into. */
    body { display: flex; flex-direction: column; }
    body > * { flex: none; }
    /* IB-header-never-shrinks-below-content: same fix TIB-075's phone-landscape band already
       proved out (see body > header a few hundred lines down, capped 45vh) — the header used to
       be the thing that GIVES when the window is short (flex: 0 1 auto; min-height: 0), which
       means flexbox is free to shrink it below its own content and overflow-y:auto silently
       clips the bottom of it instead of showing it. Capped instead of shrinkable: it always
       renders at its real height (or scrolls internally past the cap), it just never disappears. */
    body > header { flex: 0 0 auto; max-height: 60vh; overflow-y: auto; }
    .shell { display: flex; flex-direction: row; flex: 1 1 auto; height: auto;
             min-height: min(220px, 100vh); overflow: hidden; }
    .rail { position: relative; top: auto; left: auto; bottom: auto; transform: none; box-shadow: none; flex: 0 0 auto; height: 100%; padding-bottom: 0; overflow-y: auto; }
    .rail-resize { position: relative; top: auto; left: auto; width: 4px; margin-left: 0; flex: 0 0 auto; height: 100%; }
    .main { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; height: 100%; padding-left: 0; overflow: hidden; }
    .main > .filterbar, .main > .pausebanner, .main > .opsbanner { flex: none; }
    /* §2 · the chat column's own floor. A rail dragged wide can take the room the board had; it
       can never take the room the composer needs, because a flex item does not shrink past its
       min-width. The drag clamps stop first (see getRailMax/getToolRailMax) — this is what holds
       if a saved width, a restored session or a window resize ever gets past them. */
    .chatpanel-wrapper { display: flex; flex-direction: column; flex: 1 1 auto; min-width: min(360px, 100%); overflow: hidden; }
    /* TIB-075 §3 · the Reset button (and the sentence beside it) belong ABOVE the conversation, not
       under it. As the last row of the column it was the row that got squeezed to nothing against
       the bottom edge — the control for fixing a broken layout, broken by the layout. */
    .layout-toolbar { flex: none; order: -1; }
    .chatpanel { margin: 0; border-radius: 0; }
    .chatpanel.on { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; overflow: hidden; }
    .chatpanel > h2, .chatpanel > .coder-status-tabs { flex: none; }
    #chat-pane { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
    #chat-pane > .va-status-card, #chat-pane > .attachments-panel,
    #chat-pane > .sticky-bar, #chat-pane > .attach-row, #chat-pane > .chat-footer,
    #chat-pane > .folder-chips, #chat-pane > .turn-bar,
    #chat-pane > .chatform { flex: none; }
    #chat-pane > .chatlog-wrap { flex: 1 1 auto; min-height: 0; overflow: hidden; position: relative; }
    .chatlog { height: 100%; min-height: 0; max-height: none; overflow: auto; }
    .wrap { flex: 1 1 auto; min-width: 0; overflow-y: auto; }
    .tool-rail-body { flex: 1 1 auto; overflow: auto; }
    .tool-rail-resize { display: block; width: 4px; flex: 0 0 auto; height: 100%; cursor: col-resize; z-index: var(--z-chrome-top); background: transparent; }
    .tool-rail-resize:hover, body.tool-rail-resizing .tool-rail-resize { background: rgba(56,139,253,.35); }
    body.tool-rail-resizing { cursor: col-resize; user-select: none; }
    .tool-rail { position: relative; top: auto; right: auto; bottom: auto; margin: 0; border-radius: 0; flex: 0 0 auto; height: 100%; }
  }

  /* ── TIB-075 §2 · THE SAME PROMISE ON A HALF-WIDTH WINDOW (760–999px) ──────────────────────
     Between the phone (where the chat is already a full-screen sheet with a pinned composer) and
     the 3-column shell there was a band where the chat sat in ordinary page flow: drag the window
     narrow and short, and the message box went below the fold — present, but only findable by
     scrolling, which is the same as gone when you are standing at the bench. The columns here stay
     as they are (the sidebar is still an overlay, the right rail still a drawer); only the height
     behaviour is borrowed from the shell above — the window is the frame, the conversation is what
     scrolls inside it, and the composer is pinned to the bottom of the frame. */
  @media (min-width: 760px) and (max-width: 999px) {
    html, body { height: 100%; overflow: hidden; }
    body { display: flex; flex-direction: column; }
    body > * { flex: none; }
    /* IB-header-never-shrinks-below-content: same fix TIB-075's phone-landscape band already
       proved out (see body > header a few hundred lines down, capped 45vh) — the header used to
       be the thing that GIVES when the window is short (flex: 0 1 auto; min-height: 0), which
       means flexbox is free to shrink it below its own content and overflow-y:auto silently
       clips the bottom of it instead of showing it. Capped instead of shrinkable: it always
       renders at its real height (or scrolls internally past the cap), it just never disappears. */
    body > header { flex: 0 0 auto; max-height: 60vh; overflow-y: auto; }
    .shell { display: flex; flex-direction: row; flex: 1 1 auto; min-height: min(220px, 100vh); overflow: hidden; }
    /* TIB-075 §2 follow-up: .rail was still `position: fixed; top: var(--header-h)` (the base
       min-width:760px rule) in this band, so it painted over #not-working-bar/#run-main-bar/
       #autoland-pause-bar whenever one was showing — --header-h only ever measured <header> itself.
       Same fix as the >=1000px shell above: .rail becomes a normal flex child of the now-row .shell,
       so its position falls out of the layout instead of a stale number. */
    .rail { position: relative; top: auto; left: auto; bottom: auto; transform: none; box-shadow: none; flex: 0 0 auto; height: 100%; padding-bottom: 0; overflow-y: auto; }
    .rail-resize { position: relative; top: auto; left: auto; width: 4px; margin-left: 0; flex: 0 0 auto; height: 100%; }
    .main { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; height: 100%; padding-left: 0; overflow: hidden; }
    .main > .filterbar, .main > .pausebanner, .main > .opsbanner { flex: none; }
    .chatpanel-wrapper { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; overflow: hidden; }
    /* TIB-075 §3 · the Reset button (and the sentence beside it) belong ABOVE the conversation, not
       under it. As the last row of the column it was the row that got squeezed to nothing against
       the bottom edge — the control for fixing a broken layout, broken by the layout. */
    .layout-toolbar { flex: none; order: -1; }
    .chatpanel.on { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }
    .chatpanel > h2, .chatpanel > .coder-status-tabs { flex: none; }
    #chat-pane { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
    #chat-pane > .va-status-card, #chat-pane > .attachments-panel,
    #chat-pane > .sticky-bar, #chat-pane > .attach-row, #chat-pane > .chat-footer,
    #chat-pane > .folder-chips, #chat-pane > .turn-bar,
    #chat-pane > .chatform { flex: none; }
    #chat-pane > .chatlog-wrap { flex: 1 1 auto; min-height: 0; overflow: hidden; position: relative; }
    /* IB-124 · coder status pane must claim height and scroll its log on tablet widths too. */
    #coder-status-pane { display: none; flex-direction: column; flex: 1 1 auto; min-height: 0; overflow: hidden; }
    #coder-status-pane.on { display: flex; }
    #coder-status-pane > .csp-log { flex: 1 1 auto; min-height: 0; overflow: auto; }
    .chatlog { height: 100%; min-height: 0; max-height: none; overflow: auto; }
    .wrap { flex: 1 1 auto; min-width: 0; overflow-y: auto; }
  }

  /* ── IB-119 · DESKTOP CHAT AS A SIDE DRAWER ─────────────────────────────────────────────
     On desktop/tablet the desk chat opens as a slim drawer beside the workbench instead of
     hiding the board. .chatpanel-wrapper becomes the positioning context; the panel is pinned to
     the right edge of the center column, and the board gains a matching margin so nothing sits
     underneath it. The composer + header stay pinned inside the drawer the same way they did in
     the full-column layout. Phones keep the existing full-screen fixed overlay. */
  @media (min-width: 760px) {
    .chatpanel-wrapper { position: relative; }
    body.desk-chat-open .chatpanel.on {
      position: absolute; top: 0; right: 0; bottom: 0;
      width: var(--chat-drawer-w); max-width: 90vw;
      z-index: var(--z-raised); display: flex; flex-direction: column;
      margin: 0; border-radius: 0; border-left: 1px solid #21262d;
    }
    body.desk-chat-open .wrap { margin-right: var(--chat-drawer-w); }
    /* WO-CHAT-DRAWER-WIDTH: Brandon — "get the manager to pick up the whole window that's
       available" — the drawer used to be hard-locked at 420px no matter how big the screen was.
       initChatDrawerResize() now sets --chat-drawer-w to a generous, viewport-proportional
       default on load (see chatDrawerDefaultW() in dashboard.html) and this handle lets it be
       dragged wider/narrower from there, same as the sidebar and tool-rail — and it's remembered
       the same way. */
    .chat-drawer-resize { display: block; position: absolute; top: 0; bottom: 0;
      right: var(--chat-drawer-w); width: 6px; margin-right: -3px; cursor: col-resize;
      z-index: var(--z-raised); background: transparent; }
    .chat-drawer-resize:hover, body.chat-drawer-resizing .chat-drawer-resize { background: rgba(56,139,253,.35); }
  }

    /* ── PHOTO-TO-PART · SEE stage upload panel (feat/see-photo-ui) ────────────────────────
         Snap/upload a part photo + a one-line description → 2.5D part brief. Reuses the shared
         .modal/.sheet chrome and the page's dark tokens (#161b22 sheet, #0d1117 wells, #58a6ff
         accent glow, #3fb950 ok, #d29922 warn, #f85149 err). Mobile-first: the camera picker
         (accept="image/*" capture="environment") opens the rear camera on a phone. */
    .photo-sheet { max-width: 640px; }
    .photo-intro { color: #8b949e; font-size: 12px; line-height: 1.5; margin: 4px 0 14px; }
    .photo-drop { border: 1.5px dashed #30363d; border-radius: 10px; background: #0d1117; padding: 14px;
      display: flex; align-items: center; gap: 14px; min-height: 96px; }
    .photo-drop.has-photo { border-style: solid; border-color: #21262d; padding: 10px; }
    .photo-thumb { width: 88px; height: 88px; object-fit: cover; border-radius: 8px; flex: 0 0 auto;
      background: #161b22; border: 1px solid #21262d; }
    .photo-pick { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 8px;
      padding: 10px 14px; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; }
    .photo-pick:hover { background: #262c36; }
    .photo-picknone { color: #6e7681; font-size: 12px; }
    .photo-desc { width: 100%; box-sizing: border-box; margin-top: 12px; background: #0d1117;
      border: 1px solid #30363d; border-radius: 8px; color: #e6edf3; font: inherit; font-size: 13px;
      padding: 10px 12px; min-height: 64px; resize: vertical; }
    .photo-desc:focus { outline: none; border-color: #58a6ff; box-shadow: 0 0 0 3px rgba(88,166,255,.18); }
    .photo-row { display: flex; gap: 10px; margin-top: 12px; align-items: center; }
    .photo-analyze { background: #1f6feb; color: #fff; border: 1px solid #1f6feb; border-radius: 8px;
      padding: 10px 18px; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
      box-shadow: 0 0 12px rgba(31,111,235,.35); }
    .photo-analyze:hover { background: #388bfd; }
    .photo-analyze:disabled { opacity: .5; cursor: default; box-shadow: none; }
    .photo-clear { background: none; border: none; color: #8b949e; font-size: 12px; cursor: pointer; }
    .photo-clear:hover { color: #e6edf3; }
    .photo-loading { margin-top: 16px; padding: 14px; background: #0d1117; border: 1px solid #21262d;
      border-radius: 8px; color: #8b949e; font-size: 13px; }
    .photo-loading .spin { display: inline-block; animation: photo-spin 1s linear infinite; }
    @keyframes photo-spin { to { transform: rotate(360deg); } }
    .photo-result { margin-top: 16px; background: #0d1117; border: 1px solid #21262d; border-radius: 10px; padding: 14px; }
    .photo-result-head { display: flex; gap: 12px; align-items: flex-start; }
    .photo-result-thumb { width: 96px; height: 96px; object-fit: cover; border-radius: 8px; flex: 0 0 auto;
      background: #161b22; border: 1px solid #21262d; }
    .photo-result-body { flex: 1 1 auto; min-width: 0; }
    .photo-summary { font-size: 14px; color: #e6edf3; font-weight: 600; margin: 0 0 6px; }
    .photo-badge { display: inline-block; padding: 3px 9px; border-radius: 20px; font-size: 11px;
      font-weight: 700; letter-spacing: .02em; margin-bottom: 8px; }
    .photo-badge-2d { background: rgba(63,185,80,.16); color: #3fb950; border: 1px solid rgba(63,185,80,.4);
      box-shadow: 0 0 10px rgba(63,185,80,.25); }
    .photo-badge-not2d { background: rgba(210,153,34,.14); color: #d29922; border: 1px solid rgba(210,153,34,.4);
      box-shadow: 0 0 10px rgba(210,153,34,.2); }
    .photo-badge-reason { color: #8b949e; font-size: 12px; line-height: 1.5; margin: 4px 0 10px; }
    .photo-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 12px; margin-top: 8px; }
    .photo-grid dt { color: #6e7681; }
    .photo-grid dd { margin: 0; color: #c9d1d9; }
    .photo-features-label { color: #6e7681; font-size: 12px; margin-top: 8px; }
    .photo-features { margin: 4px 0 0; padding-left: 18px; color: #c9d1d9; font-size: 12px; line-height: 1.6; }
    .photo-features.none { list-style: none; padding-left: 0; color: #6e7681; }
    .photo-ref { color: #6e7681; font-size: 11px; margin-top: 10px; font-family: ui-monospace, monospace; }
    .photo-visiondown { margin-top: 16px; padding: 16px; background: rgba(210,153,34,.08);
      border: 1px solid rgba(210,153,34,.4); border-radius: 10px; text-align: center; }
    .photo-visiondown h3 { margin: 0 0 6px; font-size: 13px; color: #d29922; text-transform: none; letter-spacing: 0; }
    .photo-visiondown p { margin: 0 0 12px; color: #8b949e; font-size: 12px; line-height: 1.5; }
    .photo-recent { margin-top: 22px; border-top: 1px solid #21262d; padding-top: 14px; }
    .photo-recent h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: #8b949e; margin: 0 0 8px; }
    .photo-recent-empty { color: #6e7681; font-size: 12px; }
    .photo-recent-item { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 8px;
      cursor: pointer; border: 1px solid transparent; }
    .photo-recent-item:hover { background: #1c2129; border-color: #21262d; }
    .photo-recent-badge { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; }
    .photo-recent-badge.yes { background: #3fb950; box-shadow: 0 0 6px #3fb950; }
    .photo-recent-badge.no { background: #d29922; box-shadow: 0 0 6px #d29922; }
    .photo-recent-summary { flex: 1 1 auto; min-width: 0; font-size: 13px; color: #e6edf3; overflow: hidden;
      text-overflow: ellipsis; white-space: nowrap; }
    .photo-recent-meta { flex: 0 0 auto; color: #6e7681; font-size: 11px; font-family: ui-monospace, monospace; }
    /* WO-SEE-DRAW-APPROVE: the human-in-the-loop step. The measurement row is the ONLY place a real
       millimetre enters the pipeline, so it reads as a form the operator must fill in, not a detail. */
    .photo-approve { margin-top: 14px; padding-top: 12px; border-top: 1px solid #21262d; }
    .photo-approve-label { font-size: 12px; color: #8b949e; margin-bottom: 8px; line-height: 1.5; }
    .photo-approve-label b { color: #e6edf3; }
    .photo-measure { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
    .photo-measure input[type=number] { width: 96px; background: #010409; border: 1px solid #30363d;
      color: #e6edf3; border-radius: 8px; padding: 8px 10px; font-size: 13px; }
    .photo-measure select { background: #010409; border: 1px solid #30363d; color: #e6edf3;
      border-radius: 8px; padding: 8px 10px; font-size: 13px; }
    .photo-measure-unit { color: #8b949e; font-size: 13px; font-family: ui-monospace, monospace; }
    .photo-measure-save { background: #21262d; color: #e6edf3; border: 1px solid #30363d; border-radius: 8px;
      padding: 8px 14px; font-size: 13px; cursor: pointer; }
    .photo-measure-save:hover { background: #30363d; }
    .photo-scale-set { color: #3fb950; font-size: 12px; margin: 8px 0 0; font-family: ui-monospace, monospace; }
    .photo-approve-btn { margin-top: 12px; width: 100%; background: #238636; color: #fff; border: 1px solid #2ea043;
      border-radius: 8px; padding: 11px 16px; font-size: 14px; font-weight: 700; cursor: pointer;
      box-shadow: 0 0 14px rgba(35,134,54,.3); }
    .photo-approve-btn:hover { background: #2ea043; }
    .photo-approve-btn:disabled { background: #21262d; color: #6e7681; border-color: #30363d;
      box-shadow: none; cursor: not-allowed; }
    .photo-approve-blocked { color: #d29922; font-size: 12px; margin: 10px 0 0; line-height: 1.5; }
    .photo-approved { margin-top: 14px; padding: 12px; border-radius: 8px; background: rgba(63,185,80,.1);
      border: 1px solid rgba(63,185,80,.35); }
    .photo-approved h4 { margin: 0 0 6px; font-size: 12px; color: #3fb950; }
    .photo-approved dl { margin: 0; font-size: 11px; color: #8b949e; font-family: ui-monospace, monospace;
      line-height: 1.7; word-break: break-all; }
    /* IB-130: the approval landed but DRAW never got it — amber, not the clean green tick above. */
    .photo-approved.photo-approved-warn { background: rgba(227,179,65,.1); border-color: rgba(227,179,65,.4); }
    .photo-approved.photo-approved-warn h4 { color: #e3b341; }
    .photo-approve-err { color: #f85149; font-size: 12px; margin-top: 8px; }
    @media (max-width: 600px) {
      .photo-result-head { flex-direction: column; }
      .photo-result-thumb { width: 100%; height: 160px; }
      .photo-drop { flex-direction: column; align-items: stretch; }
      .photo-thumb { width: 100%; height: 120px; }
      .photo-measure { flex-direction: column; align-items: stretch; }
      .photo-measure input[type=number] { width: auto; }
    }

  /* ══ TIB-081 · THE PHONE IS THE MAIN SCREEN (pass one) ══════════════════════════════════════
     Everything below was measured in real Chrome at 390×844 and 320×568 BEFORE a line changed,
     and re-measured after. Nothing here changes what a screen shows, what it fetches, or what a
     control does — it is width and wrapping only.

     This block sits at the END of the stylesheet on purpose. Several of the rules it corrects
     (the alert card's nested scroller, the badge column, the composer's icon widths) live in
     later, equal-specificity rules than the main ≤430px block, so a fix written up there would
     have lost the cascade silently. One block, at the end, in source order, is also the whole
     change in one place for whoever reads it next.

     The three known ones this pass had to answer, from the spec:
       · the coder cards — the header tiles stay hidden at phone width (that decision is older than
         this pass and still right: they overlapped into garbled text). What changes is that the
         compact status line that replaces them — the phone's ONLY coder readout, and the thing
         TIB-051 made the tap into a live build — was measured at 87×30 starting at x=427 on a
         390px screen: 37 pixels past the right edge, so tapping it was not possible at all. It
         now rides in the wrapped tab row, is a 44px target, and its own centre point belongs to
         it. Sideways it gets the same treatment for the same reason (see the rotation block).
       · the map rail — it is on screen (the ≤900px block already stacks it under the canvas); what
         it was not is usable: a 38px input, a 38px Send, 30px zoom buttons, and at 320px a close ✕
         sitting past the right edge, so the map could not be left.
       · the Manager's message box — 149px wide out of 390 (38% of the screen), with its own
         placeholder clipped mid-word, because two 28px icon buttons were being stretched to 64px
         each by the shared `.chatform button { min-width: 64px }`.  */

  @media (max-width: 430px) {
    /* ── 1 · The tab row: every tab on screen, every tab named, every tab thumb-sized ──────────
       Measured before: ten 37×34 buttons with every label display:none, in ONE nowrap row that
       scrolled — 504px of content in a 370px box, overflowing by 134px at 390px and 204px at
       320px. At 390px My Day ran from x=376 to x=413, so 14 pixels of a 37-pixel button were on
       a 390-pixel screen; at 320px both Calendar and My Day started past the right edge and were
       not on screen at all. Neither has a twin in the ⋯ menu, so the button he presses every
       morning existed only if he thought to flick a row with no visible end.
       Wrapping is the whole fix: same tabs, same order, nothing renamed, nothing moved — they
       just take a second line instead of hanging off the edge. The label comes back under the
       icon because eleven unlabelled emoji is not a menu you can read with sawdust on your hands. */
    .action-strip { flex-wrap: wrap; overflow-x: visible; row-gap: 6px; gap: 6px; }
    .astrip-btn { flex-direction: column; justify-content: center; gap: 3px;
                  min-height: 46px; min-width: 46px; padding: 5px 7px; }
    .astrip-ic { font-size: 17px; }
    .astrip-lb { display: block; font-size: 9px; font-weight: 600; line-height: 1;
                 max-width: 58px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    /* The spacers exist to push the coder cluster to the middle of a wide row. In a wrapping row
       they are flex:1 1 auto items that eat a whole line and shove the cluster onto its own. */
    .astrip-spacer { display: none; }

    /* ── 2 · The coder status line is on screen and is a real target ───────────────────────────
       It is the ONLY coder readout a phone gets (the tiles are display:none at this width), and it
       was last in the scrolling row, 24px tall, starting 37px beyond the right edge. With the row
       wrapped it is simply visible; this gives it the thumb height. `.hud-watchable` (TIB-051's
       tappable variant) sets its own padding at higher specificity, so the height is asked for as
       a min-height, which holds either way. */
    .hud-mobile-status { min-height: 44px; box-sizing: border-box;
                         display: inline-flex; align-items: center; }

    /* ── 3 · The task list says what the task is, ten rows at a time ──────────────────────────
       IB-138 supersedes TIB-081's two-line wrap here. That wrap (id+chevron line, a two-line
       title, the badges on their own row) read well one row at a time but was 3-4 lines tall —
       it only ever fit five tasks on a 390px screen, and Brandon runs a fleet of six. The id
       badge now carries ONLY the allocated spec code (specCode(), see .id above) and the title
       never repeats it (cardTitle() strips a leading "CODE · " prefix), so one line has room for
       both: id, the title (ellipsized — the full sentence is one tap away in .tdetail), one
       status pill, the chevron. Measured: ten rows now fit a 390×844 screen with the header and
       filter chips still on it; the old wrap fit five. */
    .trow { flex-wrap: nowrap; align-items: center; padding: 10px 14px; gap: 8px; }
    .trow .id { flex: 0 0 auto; }
    .trow .chev { flex: 0 0 auto; }
    .trow .tbody { flex: 1 1 auto; min-width: 0; }
    .trow .tbadges { flex: 0 0 auto; flex-direction: row; }
    .trow .tclamp { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .trow .title { flex: 1 1 auto; min-width: 0; }

    /* ── 4 · The Manager's message box gets the room ───────────────────────────────────────────
       Measured: 📎 and 🎤 are 28px square controls, but `.chatform button { min-width: 64px }`
       (written for Send, which needs its word) stretched each to 64px, leaving the textarea 149px
       of a 390px screen — and its own "Message this desk…" placeholder needed 65px of height in a
       42px box, so it was clipped mid-word. Square icons get the square 44px thumb target they
       should always have had, and the ~40px that frees goes to the box he types into. */
    .chatform .chat-attach,
    .chatform .chat-mic { width: 44px; min-width: 44px; height: 44px; min-height: 44px;
                          font-size: 17px; flex: 0 0 auto; }
    /* At 390px the freed room is enough and the row stays one line (measured: 189px box, the
       placeholder fits). At 320px it is NOT — the same row leaves 119px and the placeholder is
       clipped mid-word again. Rather than pick a second magic width, the row is allowed to wrap
       and the box is given a floor it must be granted: below that floor the row breaks and the
       box takes the width of its own line. 150px + the two icons + Send + the gaps still fits a
       390px screen, so nothing moves there; at 320px the box breaks onto its own line and gets
       192px instead of 119px. Width decides, not a device.
       IB-02 (item 13) keeps that floor and stops the row's height from being decided by it: the
       48px min-height TIB-081 set here was a second, larger floor than the one-line rule in the
       base block, so an EMPTY box on a phone was taller than the line it holds. The wrap floor is
       about WIDTH; the height is the auto-grow's to decide, one line to five. */
    .chatform { flex-wrap: wrap; }
    .chatform textarea { flex: 1 1 150px; }
    /* The per-message actions under every reply (copy / print / read aloud / up / down) were
       32×32 mouse targets sitting five-in-a-row. */
    .bact { min-width: 44px; min-height: 44px; font-size: 15px; }
    /* IB-139 · five unlabelled emoji on every reply, one of them a PRINTER, on a phone. Copy is the
       only default action now; Play/⬆/⬇ move behind one "⋯" control (Print has no real use on a
       phone at all — a network printer picker is not a thing he reaches for mid-conversation — so
       it is gone here, not just hidden). .bactions.expanded (the moreBtn onclick in bubbleActions)
       reveals the rest. */
    .bact-print { display: none; }
    .bact-play, .bact-rerun { display: none; }
    .bactions.expanded .bact-play, .bactions.expanded .bact-rerun { display: inline-flex; }
    .bact-more { display: inline-flex; }
    /* IB-139 · the per-message receipt (lane/tokens/cost/brief/remembered) collapses to the model
       pill + one tap, instead of five chips wrapping under every reply. */
    .bmeta-rest { display: none; }
    .bmeta.open .bmeta-rest { display: flex; flex-wrap: wrap; gap: 5px; width: 100%; margin-top: 3px; }
    .bmeta-toggle { display: inline-flex; align-items: center; justify-content: center;
                    min-width: 44px; min-height: 44px; padding: 2px 8px; background: none;
                    border: 1px solid #30363d; border-radius: 20px; color: #8b949e; font-size: 10px;
                    cursor: pointer; }
    .bmeta-toggle:hover { color: #e6edf3; border-color: #484f58; }
    /* IB-139 · the session cost total is chat furniture, not a fixed row between the transcript and
       the composer — the one place a phone has least room. It is still computed (renderChatUsage
       still writes #chat-usage) — buildChatOverflow surfaces the same line one tap away in the
       existing ⋯ menu instead (see #chat-ov-usage). */
    .chatpanel.on .chat-footer { display: none !important; }

    /* ── 5 · A decision card's options are not behind a second scrollbar ───────────────────────
       The card's four parts end with "his options" and "what each option does" — and the detail
       box was capped at 18vh with its own scroller inside a card that is itself scrolling. On a
       phone that reads as a card that stops mid-sentence: the measured card showed "Your options:
       Rebase and land it · Leave it for" and then nothing. The card list already scrolls; the
       detail inside it does not need to. */
    .at-item .at-detail { max-height: none; overflow-y: visible; }

    /* ── 6 · The map ──────────────────────────────────────────────────────────────────────────
       IB-1437 · the head still wraps rather than squeezing the close button off the screen
       (measured at 320px: the ✕ sat at x=317–361, past the right edge, so the map could not be
       closed) — that part of the ≤900px-and-down arrangement is unchanged. What's gone is the
       THREE STACKED FULL-WIDTH ROWS zoom/altitude/trace used to take below the title: on a 390px
       screen that stack alone measured ~150-200px, more than the canvas itself got in the reported
       screenshot, for five controls Brandon touches occasionally. They now share ONE row —
       .map-controls, order 3, flex-basis 100% — that scrolls sideways instead of wrapping (the
       same technique .map-routes already uses below), so it can never grow past one line no
       matter how many controls it holds. The three altitudes keep their labels AND their counts
       (mapAltitudeChoices still renders both) — only the row height shrinks. */
    .map-head { flex-wrap: wrap; row-gap: 8px; }
    .map-head h2 { flex: 1 1 auto; }
    .map-head .x { flex: 0 0 auto; order: 2; }
    .map-controls { order: 3; flex: 1 1 100%; display: flex; align-items: center; gap: 6px;
                     overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 2px; }
    .map-zoom { flex: 0 0 auto; margin-left: 0; }
    .map-zoom button { flex: 0 0 auto; min-width: 36px; height: 36px; font-size: 14px; }
    /* IB-111 · the altitude control used to take its own full row for the same reason the zoom
       did — three labelled buttons next to the title is what pushed the ✕ off a 320px screen. It
       still gets thumb-reachable buttons; it just no longer costs a whole line to get them. */
    .map-alt { flex: 0 0 auto; margin-left: 0; }
    .map-alt button { flex: 0 0 auto; min-height: 36px; padding: 0 8px; font-size: 12px; white-space: nowrap; }
    /* IB-114 · and the trace control used to take the row below that, for exactly the same
       reason — a fifth control beside the title is how the ✕ went off a 320px screen again. */
    .map-trace-btn { flex: 0 0 auto; margin-left: 0; min-height: 36px; white-space: nowrap; }
    /* IB-125 · Complete used to sit on its own row right after Trace, for the same 320px reason. */
    .map-trace-complete-btn { flex: 0 0 auto; margin-left: 0; min-height: 36px; white-space: nowrap; }
    /* IB-1437 · the ≤900px block gives the rail (the conversation he talks to the map through) a
       fixed 210px, sized for a tablet-width phone. At ≤430px that is trimmed to 170px — the log
       still scrolls, the input row still gets its full 44px — so the canvas keeps more of a
       390px screen without the rail losing its own scroll. */
    .map-body { grid-template-rows: minmax(0, 1fr) 170px; }
    /* Same corner as the laptop — one place a route pill ever lives — but a single scrolling row
       rather than a wrapping block, so four routes cannot eat half the canvas. */
    .map-routes { left: 8px; right: 8px; max-width: none; flex-wrap: nowrap;
                  overflow-x: auto; padding-bottom: 2px; }
    .map-route-pill { flex: 0 0 auto; }
    .map-route-pill button { min-height: 40px; }
    .map-ask { gap: 8px; }
    .map-ask input,
    .map-ask button { min-height: 44px; }
    .map-ask .map-mic { min-width: 44px; }

    /* ── 7 · The chrome that is on every screen ────────────────────────────────────────────────
       Search 32px tall, the 🏠 and 💬 buttons 30px WIDE, the repo filter chips 28px tall. Each is
       hit dozens of times a day with one thumb. */
    .cmdbox { min-height: 44px; }
    .hdr-search { min-height: 44px; }
    #allbtn, #managerbtn { width: 44px; height: 44px; }
    .repo-tabs { gap: 8px; }
    .repo-tab { min-height: 44px; padding: 0 14px; display: inline-flex; align-items: center; }
    .brand { min-height: 44px; }
    /* IB-138 · the blocked pill no longer grows on a phone — see the base .badge.b-blocked.tap
       rule; the row it sits in is the 44px target (below). */

    /* ── 8 · My Day — the morning screen, checked off with one thumb ───────────────────────────
       Measured: the Top 3 tick boxes were 16×16 — the smallest targets in the app, on the screen
       he opens first every day — the goal fields 32px, the notes carets 22×26 and "+ Add block"
       31px. Same three goals, same notes, same button; they are just hittable now. */
    .pd-goal-row { gap: 10px; }
    .pd-goal-row input[type=checkbox] { width: 26px; height: 26px; margin: 9px 0; }
    .pd-goal-input { min-height: 44px; }
    .pd-goal-caret { width: 44px; height: 44px; font-size: 14px; }
    .pd-all-goals-link { display: inline-flex; align-items: center; min-height: 44px; }
    .cal-add-btn { min-height: 44px; }
    .pd-lock-btn { min-height: 44px; }

    /* ── 9 · The merge tray reads its own rows ─────────────────────────────────────────────────
       Same failure as the task list, on the screen where he decides what ships: the branch title
       was clipped to a third of itself (33%, 236px of 713px) and the branch line under it to 32%,
       so several rows read as the same half-sentence. Both wrap to two lines instead. */
    .mt-cname { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2;
                -webkit-box-orient: vertical; line-clamp: 2; overflow: hidden; }
    .mt-csub { white-space: normal; overflow-wrap: anywhere; display: -webkit-box;
               -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-clamp: 2; overflow: hidden; }
    .mt-x { width: 44px; height: 44px; }
  }

  /* ══ TIB-081 · ROTATION ═════════════════════════════════════════════════════════════════════
     Rotate the phone and it is 844px wide — wider than every phone breakpoint in this file — so
     every rule above switches off and the page takes the tablet treatment on a screen 390px tall.
     Measured at 844×390: the rail's "+ new thread" was 9×16, the pin buttons 20×20, the collapse
     control 22×22, the rail search 27px tall, the tab row 28px. Mouse metrics, on a phone, in the
     hand that is not holding the board.
     Keyed on a SHORT LANDSCAPE VIEWPORT WITH A COARSE POINTER, so a laptop window dragged short
     keeps its mouse-sized controls and only a rotated touch device gets these. */
  @media (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
    .astrip-btn { min-height: 44px; }
    .repo-tab { min-height: 44px; padding: 0 14px; display: inline-flex; align-items: center; }
    .cmdbox, .hdr-search, .rail-search { min-height: 44px; }
    .hdr-ico, #allbtn, #managerbtn { width: 44px; height: 44px; }
    .overflow-btn { min-width: 44px; min-height: 44px; }
    .pinbtn, .rbtn, .collapse, .addthreadbtn,
    .rail-close, .trg-btn { min-width: 44px; min-height: 44px; }
    .bact { min-width: 44px; min-height: 44px; }
    .taction { min-height: 44px; }
    .mt-x, .pd-goal-caret { width: 44px; height: 44px; }
    /* Rotating must not put a fixed screen back the way it was. Sideways the merge tray still
       halved its branch names (measured 52% and 46% of the sentence, so two different rows read
       the same), which is the portrait failure with the phone turned over. Same answer: two
       lines instead of one third of one. */
    .mt-cname { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2;
                -webkit-box-orient: vertical; line-clamp: 2; overflow: hidden; }
    .mt-csub { white-space: normal; overflow-wrap: anywhere; display: -webkit-box;
               -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-clamp: 2; overflow: hidden; }
    .pd-goal-row input[type=checkbox] { width: 26px; height: 26px; margin: 9px 0; }
    .pd-goal-input, .cal-add-btn, .pd-lock-btn { min-height: 44px; }
    .pd-all-goals-link { display: inline-flex; align-items: center; min-height: 44px; }
    .lp-add-btn, .lp-more, .card-widetoggle, #reset-layout-btn { min-width: 44px; min-height: 44px; }
    .map-zoom button { min-width: 44px; height: 44px; }
    .map-ask input, .map-ask button { min-height: 44px; }
    .chatform .chat-attach, .chatform .chat-mic { width: 44px; min-width: 44px; height: 44px; }
    /* Sideways, the phone-width block is off and the composer takes its desktop metrics: a 38px
       box and a 29px Send, measured. The box has the room here (485px of 844) — it is the height
       that is a mouse's. */
    .chatform textarea { min-height: 44px; }
    .chatform button { min-height: 44px; }
    /* 390px of height is the whole frame here, and TIB-075 decided that when the frame is too
       short the HEADER is what gives and scrolls inside itself. That is right for a laptop window
       dragged short. On a phone turned sideways it means the tab row and the coder readout get
       clipped away silently: measured, the header wanted 167px, was handed 90px, and a thumb
       aimed at the coder line hit the board underneath it.
       So here the header does not give. It is capped (45vh) and it holds that size rather than
       shrinking below it — the shell still clears its own floor with room to spare, and with the
       coder tiles off (below) the whole header fits inside the cap with nothing scrolled away. */
    body > header { flex: 0 0 auto; max-height: 45vh; overflow-y: auto; }

    /* A rotated phone is 844px wide, so it takes the DESKTOP coder readout: the full tiles, in a
       header that is now capped. Measured — the tile's own centre point returned the task panel
       underneath it, i.e. the tile had been scrolled out of the capped header and a thumb aimed
       at it would hit the board behind. It was reachable only by scrolling a container with no
       visible end, which is the exact failure this spec was written about.
       It is still a phone. It gets the phone's coder readout in both orientations — the same
       compact line, the same tap into the live build — so the route does not change when he turns
       the device over, and the header gets short enough that the cap stops mattering. */
    .coder-cluster-head, .coder-cluster-cards, .printer-cluster { display: none; }
    .hud-mobile-status { display: inline-flex; align-items: center; min-height: 44px;
                         box-sizing: border-box; flex: 0 1 auto; min-width: 0; max-width: 100%;
                         padding: 4px 8px; font-size: 11px; font-weight: 600; color: #c9d1d9;
                         white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  }

  /* ══ IB-12 · THE PHONE GETS ITS SCREEN BACK (pass two) ═════════════════════════════════════════
     Pass one made every control reachable one-handed. This pass makes the header stop eating the
     board. Measured before: the shared <header> on the home board was 368 px tall — 41 % of the
     viewport. Everything here is presentation-only: no routing, no dispatch, no store, no fence
     file, no tool permission changes. */

  @media (max-width: 430px) {
    /* ── 1 · Tighter header band: trim the vertical air and the gaps between rows ──────────────── */
    header { padding-top: calc(6px + env(safe-area-inset-top)); padding-bottom: 6px; row-gap: 4px; }

    /* ── 2 · The identity row: hamburger, brand and overflow menu only ───────────────────────────
       The brand does not stretch to claim a whole row; it shrinks to its content so the counters
       can ride on the next line. */
    .brand { flex: 0 1 auto; min-width: 0; }
    .wordmark { font-size: 11px; }

    /* ── 3 · One status row: vitals only, no duplicate nav buttons ────────────────────────────────
       #allbtn and #managerbtn duplicate the Right Now / New tabs in the action strip; removing them
       frees a whole row without hiding any route. Fleet health lives in the action strip. */
    .vitals { order: 10; flex: 0 1 auto; flex-basis: auto; min-width: 0; max-width: 42%;
              flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
    .vitals::-webkit-scrollbar { display: none; }
    .vpill { padding: 2px 5px; font-size: 11px; }
    .hdr-search { display: none; }
    #allbtn { display: none; }
    /* IB-123: the Manager chat entry point must stay reachable from the mobile header. The
       Dashboard button stays hidden (the brand tap + the action-strip "Right Now" tab cover it);
       the Manager button is restored as a 44px icon square. */
    #managerbtn { display: inline-flex; order: 6; }

    /* ── 4 · A shorter wrapped action strip: still labeled, still thumb-sized ─────────────────────
       Pass one chose wrapping over hidden scrolling, and that stays. The tab box model is brought
       down to a 44 px total and the row gap tightens, so two wrapped rows take less vertical space. */
    .action-strip { order: 40; flex-wrap: wrap; row-gap: 4px; gap: 4px; padding: 0; }
    .astrip-btn { min-height: 44px; min-width: 44px; padding: 2px 5px; }
    .astrip-ic { font-size: 16px; }
    .astrip-lb { font-size: 9px; }

    /* ── 5 · The coder/waiting status line under the tabs: keep the tap target, trim the padding ── */
    .hud-mobile-status { min-height: 44px; padding: 2px 6px; font-size: 11px; }
    .waiting-chip { min-height: 44px; padding: 2px 6px; }
    .waiting-line { max-width: 100%; font-size: 11px; }

    /* ── 6 · Deferred pass-one cosmetics: every small target becomes thumb-sized ──────────────────
       The probe flagged these as under 44 px at 390 px. Each keeps its wording and action; it is
       just given a comfortable box. */
    #clearfilter { min-width: 44px; min-height: 44px; }
    .rn-group-clear, .rn-item-dismiss { min-width: 44px; min-height: 44px; }
    .at-dismiss-all, .at-group-dismiss, .at-approve, .at-dismiss { min-height: 44px; }
    .at-item .at-acts button, .at-clear-flood { min-height: 44px; }
    .cal-tab { min-height: 44px; }
    .photo-pick, .photo-analyze, .photo-clear { min-height: 44px; }
    .pjob-act { min-height: 44px; }
    .pq-sel, .pq-act { min-height: 44px; }
    .coding-head .cb-retire { min-height: 44px; }
    #usage-refresh-btn, #burn-cap-pct, #burn-cap-pct-out + button { min-height: 44px; }
  }
  .ap-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(160px,1fr)); gap:10px; margin-bottom:12px; }
  .ap-card { position:relative; background:#161b22; border:1px solid #30363d; border-radius:10px; padding:10px; }
  .ap-chead { display:flex; align-items:center; gap:6px; font-size:12px; color:#8b949e; font-weight:600; }
  .ap-dot { width:8px; height:8px; border-radius:50%; background:#3fb950; flex:0 0 auto; }
  .ap-model { margin-left:auto; font-size:10px; background:#21262d; border:1px solid #30363d; border-radius:20px; padding:1px 7px; color:#c9d1d9; }
  .ap-thumb { margin:8px 0; height:54px; border-radius:8px; background:linear-gradient(135deg,#1c232d,#0d1117); display:flex; align-items:center; justify-content:center; font-size:24px; overflow:hidden; }
  /* WO-PRINTER-CARD-DEDUPE req 3: this strip is wide and short, so the product thumbnail is
     letterboxed rather than cropped (the square card thumbs use cover). */
  .ap-thumb .pt-img { object-fit:contain; background:transparent; }
  .ap-meta { font-size:11px; color:#6e7681; }
  .ap-card .ap-rm { position:absolute; top:6px; right:6px; background:#0d1117; border:1px solid #30363d; color:#8b949e; border-radius:6px; width:20px; height:20px; line-height:18px; text-align:center; padding:0; cursor:pointer; font-size:12px; }
  .ap-card .ap-rm:hover { color:#f85149; border-color:#f85149; }
  .ap-card.ap-add { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px; min-height:120px; border-style:dashed; background:transparent; color:#6e7681; cursor:pointer; }
  .ap-card.ap-add:hover { color:#58a6ff; border-color:#58a6ff; }
  .ap-card.ap-add .ap-plus { font-size:26px; line-height:1; }
  .ap-empty { font-size:12px; color:#6e7681; padding:6px 2px; }
  .ap-sheet { max-width:440px; }
  #add-printer-modal { z-index: var(--z-modal); }
  .ap-sub { font-size:12px; color:#8b949e; margin:0 0 14px; line-height:1.5; }
  .ap-step { display:none; } .ap-step.on { display:block; }
  .ap-scan { display:flex; align-items:center; gap:10px; color:#8b949e; font-size:13px; padding:8px 0; }
  .ap-spin { width:14px; height:14px; border:2px solid #30363d; border-top-color:#58a6ff; border-radius:50%; animation:ap-spin 1s linear infinite; }
  @keyframes ap-spin { to { transform:rotate(360deg); } }
  .ap-found { display:flex; align-items:center; gap:10px; background:#161b22; border:1px solid #30363d; border-radius:9px; padding:10px; margin-bottom:8px; cursor:pointer; }
  .ap-found:hover { border-color:#58a6ff; }
  .ap-found.added { cursor:default; opacity:.82; }
  .ap-found.added:hover { border-color:#30363d; }
  .ap-found.sel { cursor:default; border-color:#58a6ff; }
  .ap-radio { width:15px; height:15px; border-radius:50%; border:2px solid #30363d; flex:0 0 auto; }
  .ap-found.sel .ap-radio { border-color:#58a6ff; background:#58a6ff; box-shadow:inset 0 0 0 3px #161b22; }
  .ap-found .ap-name { font-size:13px; font-weight:600; color:#e6edf3; }
  .ap-found .ap-ip { font-size:11px; color:#6e7681; }
  .ap-tag { margin-left:auto; font-size:10px; padding:2px 8px; border-radius:20px; flex:0 0 auto; }
  .ap-tag-new { background:#0d2b1f; color:#3fb950; border:1px solid #1e5233; }
  .ap-tag-added { background:#21262d; color:#8b949e; border:1px solid #30363d; }
  .ap-link { background:none; border:none; color:#58a6ff; font-size:12px; cursor:pointer; padding:8px 0; margin-top:4px; }
  .ap-link:hover { text-decoration:underline; }
  .ap-label { font-size:12px; color:#8b949e; display:block; margin:12px 0 6px; }
  .ap-input { width:100%; background:#0d1117; border:1px solid #30363d; border-radius:8px; color:#e6edf3; padding:10px; font-size:15px; box-sizing:border-box; }
  .ap-input:focus { outline:none; border-color:#58a6ff; }
  #ap-code { letter-spacing:2px; text-transform:uppercase; }
  .ap-code-row { display:flex; gap:8px; }
  .ap-code-row .ap-input { flex:1; }
  .ap-photo { white-space:nowrap; background:#0d2b1f; border:1px solid #1e5233; color:#3fb950; border-radius:8px; padding:0 12px; font-size:12px; font-weight:600; cursor:pointer; }
  .ap-photo:hover { border-color:#3fb950; }
  .ap-hint { font-size:11px; color:#6e7681; margin-top:8px; line-height:1.5; }
  .ap-btn { width:100%; margin-top:16px; background:#58a6ff; color:#04140a; font-weight:700; border:none; border-radius:9px; padding:11px; font-size:14px; cursor:pointer; }
  .ap-btn:hover { filter:brightness(1.08); }
  .ap-btn:disabled { opacity:.6; cursor:default; }
  .ap-btn.ghost { background:transparent; border:1px solid #30363d; color:#8b949e; margin-top:8px; }
  .ap-result { text-align:center; padding:14px 0; }
  .ap-result .ap-big { font-size:38px; line-height:1; }
  .ap-result.ok .ap-big { color:#3fb950; }
  .ap-result.bad .ap-big { color:#f85149; }
  .ap-result .ap-title { font-weight:700; margin-top:8px; color:#e6edf3; }
  .ap-result .ap-msg { font-size:12px; color:#8b949e; margin-top:6px; }
  .ap-err { color:#f85149; font-size:12px; margin-top:8px; }
