/* =====================================================================
 * MERIDIAN DESIGN TOKENS — the single source of truth.
 *
 * Every colour, size, space and radius in the product is defined here and
 * nowhere else. Change a value in this file and it changes everywhere: the
 * app, the style guide, and any artefact that links it. `harness/stylecheck.js`
 * fails the build if a raw colour appears outside this file, which is what
 * keeps that promise true rather than aspirational.
 *
 * Four blocks, in order:
 *   1. :root                    the light theme, and everything theme-agnostic
 *   2. prefers-color-scheme     dark for people who have not chosen
 *   3. [data-theme="dark"]      dark for people who have
 *   4. .theme-light/.theme-dark forced, for showing both at once
 * Blocks 2–4 redefine ONLY the values that differ. Nothing else belongs here.
 * Blocks 2 and 3 are byte-identical by contract; block 4 restores block 1.
 *
 * ---------------------------------------------------------------------
 * THE COLOUR SYSTEM (adopted 7 Sep — Meridian brand, draft 1b)
 *
 * The palette is no longer designed here. It comes from the brand package in
 * `meridian-brand-draft1b/`, generated from `palette.json` and audited there
 * with 0 WCAG failures across every declared pairing. This file is where those
 * values land and where the app's own structural tokens live beside them. Two
 * consequences worth stating plainly:
 *
 *   * Do not hand-tune a colour here. Change `palette.json`, re-audit, copy in.
 *     A value edited only here is a value the audit no longer covers.
 *   * The oklch() beside each hex is the editable form and the brand's source
 *     of truth. Hex ships because `tokenHex()` in the panels parses it.
 *
 * What changed from the palette this replaces, and why it matters:
 *
 * 1. THE ACCENT IS TEAL, NOT AMBER. `--accent` is Meridian Teal #00736E,
 *    oklch(0.500 0.086 190) — the brand's one chromatic colour and the answer
 *    to "if you can only pick one". The old amber accent and the separate
 *    `--teal` it sat beside have collapsed into it, so `--teal`/`--teal-soft`
 *    are now aliases. That is the brand file's own instruction, not a
 *    shortcut: the AI's voice and the primary action are the same colour
 *    because they are the same idea.
 *
 * 2. ONE NEUTRAL FAMILY, NOT TWO. Both modes now sit on hue ~272, a navy
 *    family, at chroma 0.004–0.035. The palette this replaces ran a warm
 *    axis in light (hue 48–64) and a cool one in dark, on the argument that a
 *    warm grey reads as mud when dark. The brand chose family consistency
 *    instead, and it holds because the chroma is low enough that the "warmth"
 *    was never carrying meaning — only a cast.
 *
 * 3. `--on-warn` IS DARK NOW. It was white. The brand's amber button
 *    `--warn-fill` #BC8700 takes `--ink` at 5.2:1; white on it fails. Any
 *    label on that fill must stay >= 14px semibold — thin text at 5.2:1 is
 *    within the letter of SC 1.4.3 and outside its spirit.
 *
 * 4. THREE FACES, THREE JOBS. Manrope carries the wordmark and headings
 *    (`--display`). Public Sans does body and UI (`--sans`) — it has real
 *    tabular numerals, which is why queue times stop jittering. IBM Plex Mono
 *    keeps call IDs and timestamps (`--mono`).
 *
 *    `--cond` no longer names a condensed face, because the brand has none. It
 *    resolves to `--sans`, which is *correct* rather than a fallback: all but
 *    three of its uses are 9–11px uppercase micro-labels, and the brand
 *    assigns those to Public Sans 600. The three that were really headings now
 *    take `--display`. The token name stays so the ~24 label rules below did
 *    not all have to change to say the same thing.
 *
 * 5. `--ink-2` IS DERIVED, NOT GIVEN. The brand has no such token; this file
 *    has ~20 uses of it. It is computed on the brand's own neutral hue at the
 *    lightness the old ramp used — oklch(0.340 0.030 272) light,
 *    oklch(0.862 0.013 269) dark — and measured: 11.3:1 and 11.8:1 on their
 *    respective grounds. If the brand ever names one, take theirs.
 *
 * 6. THE MARK IS THREE SHADES, AND THEY SWAP. `--mark-ring` is always the
 *    darkest and always drawn on top; `--mark-prime` is the meridian and
 *    equator; `--mark-grid` is the graticule behind them. Light mode runs
 *    #005F5D / #00736E / #24B5AC; dark mode lifts all three rather than
 *    inverting the order, so the ring stays the anchor. `--accent-bright` is
 *    the graticule's colour and is MARK ONLY — never a fill under text.
 *
 * 7. STATUS STILL SEPARATES BY LIGHTNESS. Hue is the axis colour-vision
 *    deficiency destroys, so the ladder matters more than the hues:
 *        light:  good .450 < warn .500 = accent .500 < info .520 < crit .550
 *        dark:   accent .720 < info .739 < crit .680 < warn .800 < good .800
 *    `warn`/`accent` in light and `warn`/`good` in dark are level, so those
 *    pairs are handed to iconography — every status pill needs its glyph, not
 *    just its colour. That was true of the old palette too, for different
 *    pairs, and it is why the pills have never been colour alone.
 *
 * 8. `--coral` IS DECORATIVE, NEVER SEMANTIC. It appears in zero product
 *    surfaces. It is here so that a deliberate use has somewhere to come
 *    from, not as a sixth status colour.
 * ------------------------------------------------------------------- */

:root {
  /* Tell the browser which way round we are. Without this the *native* parts
     of the page — <select> menus, scrollbars, form control chrome, the canvas
     behind an overscroll — stay in light mode no matter what the tokens say,
     which is why the language and status dropdowns used to render as pale
     rectangles in a dark console. The palette this replaces never declared it;
     the brand's own tokens.css does, and adopting it is the moment to fix it. */
  color-scheme: light;

  /* -- neutrals: one family, hue ~272, chroma 0.004 -> 0.035 */
  --ground:#F9FAFD;     /* oklch(0.985 0.004 272) app background            */
  --surface:#FFFFFF;    /* oklch(1.000 0.000 --) raised card, the C=0 anchor */
  --surface-2:#F4F5F9;  /* oklch(0.970 0.006 272) subtle raised            */
  --sunk:#E9EBF1;       /* oklch(0.940 0.008 272) inset well               */
  /* The bar either side of a video whose aspect does not fill its frame. Not a
     surface and not themed: it is the absence of picture, and a light-grey
     letterbox reads as a bug. Defined once, on bare :root, deliberately. */
  --letterbox:#000000;  /* oklch(0 0 0) — true black, in every theme          */
  --ink:#191E30;        /* oklch(0.240 0.035 272) primary text, 15.9:1     */
  --ink-2:#323748;      /* oklch(0.340 0.030 272) strong secondary, derived */
  --muted:#5F636F;      /* oklch(0.500 0.020 272) secondary text, 6.0:1    */
  --faint:#6D717E;      /* oklch(0.550 0.020 272) tertiary — passes 4.5:1  */
  --rule:#DBDEE5;       /* oklch(0.900 0.010 272) DECORATIVE dividers only  */
  --rule-strong:#B4B7C1;/* oklch(0.780 0.014 272) */
  --rule-control:#7D808A;/* oklch(0.600 0.016 272) load-bearing edge, 3.9:1 */
  --border-control:#7D808A;/* the brand's name for the same token          */
  /* -- Meridian Teal, hue 190: the one chromatic colour */
  --accent:#00736E;     /* oklch(0.500 0.086 190) white on it 5.71:1       */
  --accent-hover:#005F5D;/* oklch(0.440 0.085 192) */
  --accent-ink:#005F5D; /* oklch(0.440 0.085 192) text on accent-soft, 6.5:1 */
  --accent-soft:#E0F3F3;/* oklch(0.950 0.020 195) */
  --accent-bright:#24B5AC;/* oklch(0.700 0.115 188) MARK ONLY             */
  --teal:var(--accent);      /* alias: .msg.ai / .badge.ai — see note 1    */
  --teal-soft:var(--accent-soft);
  /* -- the mark's three shades, darkest ring on top */
  --mark-ring:#005F5D; --mark-prime:#00736E; --mark-grid:#24B5AC;
  /* -- status: separated by lightness, backed by iconography */
  --good:#006732;       /* oklch(0.450 0.120 152) */
  --good-dim:#3E9B68;
  --good-soft:#E4F5EA;  /* oklch(0.955 0.023 158) */
  --warn:#825B00;       /* oklch(0.500 0.130 80)  text use                 */
  --warn-fill:#BC8700;  /* oklch(0.658 0.136 81)  amber fill; label = ink  */
  --warn-soft:#FBF0D9;  /* oklch(0.958 0.032 86)  */
  --crit:#C9302D;       /* oklch(0.550 0.190 27)  */
  --crit-soft:#FDEEEC;  /* oklch(0.960 0.017 27)  */
  --info:#1A67C2;       /* oklch(0.520 0.160 256) */
  --info-soft:#E6EEFB;  /* oklch(0.947 0.020 260) */
  /* -- the accent thread: decorative only, never semantic */
  --coral:#F75040;      /* oklch(0.661 0.206 29)  */
  --coral-ink:#C9302D;  /* oklch(0.551 0.190 27)  */
  --coral-soft:#FDEEEC;
  --on-accent:#FFFFFF; --on-good:#FFFFFF; --on-warn:var(--ink); --on-crit:#FFFFFF;
  --shadow:0 1px 2px rgba(25,30,48,.06), 0 8px 24px -12px rgba(25,30,48,.16);
  /* The bar's height, as a token because two things need to agree on it: the
     bar itself, and anything pinned below it. The incoming-call alert is
     positioned from this, and used to be pinned to the bottom of the window
     where it sat on top of the message box a colleague types into. */
  --nav-h:52px;
  --mark-stroke:8; --mark-mid:6.5; --mark-hair:5;   /* ring / prime / graticule, 100-unit grid */
  --scrim:rgba(25,30,48,.58);
  --sans:"Public Sans",ui-sans-serif,system-ui,"Segoe UI",Helvetica,Arial,sans-serif;
  --display:"Manrope",var(--sans);
  --cond:var(--sans);   /* no condensed face in the brand — see note 4     */
  --mono:"IBM Plex Mono","SF Mono",ui-monospace,Menlo,monospace;
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px;
  --r1:4px; --r2:7px; --r3:11px;
}

/* Dark is the working theme — a call room at night, a phone in a car, a
   console open for hours. It is designed, not inverted: shadows off (they are
   invisible), elevation carried by stepping surfaces lighter, the accent
   lightened and its chroma cut so it does not vibrate on black, and the mark's
   strokes thickened so hairlines survive. Unlike the palette this replaces,
   the neutral hue does NOT change between modes — the brand keeps one family
   in both, and the chroma is low enough that nothing reads as a cast. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --ground:#13161F;     /* oklch(0.201 0.019 270) */
    --surface:#1B1E28;    /* oklch(0.237 0.020 272) +0.04 L is the elevation */
    --surface-2:#232630;  /* oklch(0.270 0.019 272) */
    --sunk:#0F1117;       /* oklch(0.178 0.013 271) */
    --ink:#E5E8EF;        /* oklch(0.931 0.010 267) not white: white blooms  */
    --ink-2:#CED1DA;      /* oklch(0.862 0.013 269) derived — see note 5     */
    --muted:#9498A5;      /* oklch(0.681 0.020 272) */
    --faint:#828693;      /* oklch(0.621 0.020 272) */
    --rule:#2D303B;       /* oklch(0.311 0.020 273) DECORATIVE dividers only */
    --rule-strong:#444753;/* oklch(0.400 0.021 274) */
    --rule-control:#7D808A;/* oklch(0.600 0.016 272) unchanged across modes  */
    --border-control:#7D808A;
    --accent:#4CB9AF;     /* oklch(0.720 0.100 187) */
    --accent-hover:#6ECAC1;/* oklch(0.780 0.089 187) */
    --accent-ink:#6ECAC1; /* oklch(0.780 0.089 187) */
    --accent-soft:#1A3A38;/* oklch(0.323 0.038 190) */
    --accent-bright:#6ECAC1;
    --mark-ring:#2A8F87; --mark-prime:#4CB9AF; --mark-grid:#6ECAC1;
    --good:#7FD497;       /* oklch(0.800 0.120 152) */
    --good-dim:#46856A;
    --good-soft:#1D3326;  /* oklch(0.298 0.037 157) */
    --warn:#E6B55D;       /* oklch(0.800 0.120 80)  */
    --warn-fill:#E6B55D;
    --warn-soft:#3A2F18;  /* oklch(0.311 0.040 85)  */
    --crit:#E66F64;       /* oklch(0.680 0.150 27)  */
    --crit-soft:#3C2624;  /* oklch(0.295 0.034 25)  */
    --info:#7CADEF;       /* oklch(0.739 0.110 256) */
    --info-soft:#1E2A3F;  /* oklch(0.284 0.042 261) */
    --coral:#F47C70;      /* oklch(0.721 0.149 27)  */
    --coral-ink:#E66F64;
    --coral-soft:#3C2624;
    --on-accent:#13161F; --on-good:#13161F; --on-warn:#13161F; --on-crit:#13161F;
    --shadow:none;
    --scrim:rgba(9,10,15,.74);
    --mark-stroke:8.8; --mark-mid:7.2; --mark-hair:5.6; /* thickened: hairlines vanish on dark */
  }
}

/* Byte-identical to the block above. Two blocks, not one selector list,
   because `:root:not([data-theme="light"])` must stay inside the media query
   for an explicit light choice to beat a dark OS preference. */
:root[data-theme="dark"], .theme-dark {
  color-scheme: dark;
  --ground:#13161F;     /* oklch(0.201 0.019 270) */
  --surface:#1B1E28;    /* oklch(0.237 0.020 272) */
  --surface-2:#232630;  /* oklch(0.270 0.019 272) */
  --sunk:#0F1117;       /* oklch(0.178 0.013 271) */
  --ink:#E5E8EF;        /* oklch(0.931 0.010 267) */
  --ink-2:#CED1DA;      /* oklch(0.862 0.013 269) */
  --muted:#9498A5;      /* oklch(0.681 0.020 272) */
  --faint:#828693;      /* oklch(0.621 0.020 272) */
  --rule:#2D303B;       /* oklch(0.311 0.020 273) DECORATIVE dividers only */
  --rule-strong:#444753;/* oklch(0.400 0.021 274) */
  --rule-control:#7D808A;/* oklch(0.600 0.016 272) */
  --border-control:#7D808A;
  --accent:#4CB9AF;     /* oklch(0.720 0.100 187) */
  --accent-hover:#6ECAC1;/* oklch(0.780 0.089 187) */
  --accent-ink:#6ECAC1; /* oklch(0.780 0.089 187) */
  --accent-soft:#1A3A38;/* oklch(0.323 0.038 190) */
  --accent-bright:#6ECAC1;
  --mark-ring:#2A8F87; --mark-prime:#4CB9AF; --mark-grid:#6ECAC1;
  --good:#7FD497;       /* oklch(0.800 0.120 152) */
  --good-dim:#46856A;
  --good-soft:#1D3326;  /* oklch(0.298 0.037 157) */
  --warn:#E6B55D;       /* oklch(0.800 0.120 80)  */
  --warn-fill:#E6B55D;
  --warn-soft:#3A2F18;  /* oklch(0.311 0.040 85)  */
  --crit:#E66F64;       /* oklch(0.680 0.150 27)  */
  --crit-soft:#3C2624;  /* oklch(0.295 0.034 25)  */
  --info:#7CADEF;       /* oklch(0.739 0.110 256) */
  --info-soft:#1E2A3F;  /* oklch(0.284 0.042 261) */
  --coral:#F47C70;      /* oklch(0.721 0.149 27)  */
  --coral-ink:#E66F64;
  --coral-soft:#3C2624;
  --on-accent:#13161F; --on-good:#13161F; --on-warn:#13161F; --on-crit:#13161F;
  --shadow:none;
  --scrim:rgba(9,10,15,.74);
  --mark-stroke:8.8; --mark-mid:7.2; --mark-hair:5.6;
}

/* Forced light, for side-by-side demonstration and for anyone who chooses it.
   Restores block 1 exactly — every value it overrides is a value block 2 or 3
   may have set. */
:root[data-theme="light"], .theme-light {
  color-scheme: light;
  --ground:#F9FAFD;     /* oklch(0.985 0.004 272) */
  --surface:#FFFFFF;    /* oklch(1.000 0.000 --)  */
  --surface-2:#F4F5F9;  /* oklch(0.970 0.006 272) */
  --sunk:#E9EBF1;       /* oklch(0.940 0.008 272) */
  --ink:#191E30;        /* oklch(0.240 0.035 272) */
  --ink-2:#323748;      /* oklch(0.340 0.030 272) */
  --muted:#5F636F;      /* oklch(0.500 0.020 272) */
  --faint:#6D717E;      /* oklch(0.550 0.020 272) */
  --rule:#DBDEE5;       /* oklch(0.900 0.010 272) DECORATIVE dividers only */
  --rule-strong:#B4B7C1;/* oklch(0.780 0.014 272) */
  --rule-control:#7D808A;/* oklch(0.600 0.016 272) */
  --border-control:#7D808A;
  --accent:#00736E;     /* oklch(0.500 0.086 190) */
  --accent-hover:#005F5D;/* oklch(0.440 0.085 192) */
  --accent-ink:#005F5D; /* oklch(0.440 0.085 192) */
  --accent-soft:#E0F3F3;/* oklch(0.950 0.020 195) */
  --accent-bright:#24B5AC;
  --mark-ring:#005F5D; --mark-prime:#00736E; --mark-grid:#24B5AC;
  --good:#006732;       /* oklch(0.450 0.120 152) */
  --good-dim:#3E9B68;
  --good-soft:#E4F5EA;  /* oklch(0.955 0.023 158) */
  --warn:#825B00;       /* oklch(0.500 0.130 80)  */
  --warn-fill:#BC8700;  /* oklch(0.658 0.136 81)  */
  --warn-soft:#FBF0D9;  /* oklch(0.958 0.032 86)  */
  --crit:#C9302D;       /* oklch(0.550 0.190 27)  */
  --crit-soft:#FDEEEC;  /* oklch(0.960 0.017 27)  */
  --info:#1A67C2;       /* oklch(0.520 0.160 256) */
  --info-soft:#E6EEFB;  /* oklch(0.947 0.020 260) */
  --coral:#F75040;      /* oklch(0.661 0.206 29)  */
  --coral-ink:#C9302D;
  --coral-soft:#FDEEEC;
  --on-accent:#FFFFFF; --on-good:#FFFFFF; --on-warn:var(--ink); --on-crit:#FFFFFF;
  --shadow:0 1px 2px rgba(25,30,48,.06), 0 8px 24px -12px rgba(25,30,48,.16);
  --scrim:rgba(25,30,48,.58);
  --mark-stroke:8; --mark-mid:6.5; --mark-hair:5;   /* ring / prime / graticule, 100-unit grid */
}

/* ---------------------------------------------------------------------
 * The top navigation. Lives here rather than in a page so every view gets
 * the same bar, and so it is styled only in tokens.
 * ------------------------------------------------------------------- */
.topnav{display:flex;align-items:center;gap:var(--s2);height:var(--nav-h);padding:0 var(--s4);
        background:var(--surface);border-bottom:1px solid var(--rule);flex:0 0 auto;position:relative;z-index:20}
.topnav .brand{display:flex;align-items:center;gap:var(--s2);font:600 16px/1 var(--display);
               color:var(--ink);text-decoration:none;letter-spacing:-.01em;margin-right:var(--s3)}
.topnav .mark{width:20px;height:20px;flex:none}

/* THE LOCKUP. Two files, not one tokenised drawing.
 *
 * The lockup is fixed artwork rather than a tokenised drawing: unlike the
 * inline mark above, it cannot be one drawing reading custom properties. It
 * is the full-colour artwork on light and a reversed-to-white one on dark,
 * and the browser is told which by CSS.
 *
 * The swap mirrors the token contract exactly, and for the same reason: a
 * media query for people who have not chosen, [data-theme] for people who
 * have, and an explicit light choice must beat a dark OS preference. Both
 * files load either way — 31KB each, once, cached — which buys a theme change
 * with no flicker and no JavaScript in the path.
 *
 * Narrow viewports hide the whole `.lockups` wrapper rather than the images
 * inside it, so the four theme rules below never need overriding. */
.topnav .lockups{display:flex;align-items:center;flex:none}
.topnav .compact{display:none}
/* 32px, against a 24px floor for this lockup in the guide. The height is what
   the rest of the bar is proportioned against. */
.topnav .lockup{height:32px;width:auto;display:block}
.topnav .lockup.on-dark{display:none}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .topnav .lockup.on-light{display:none}
  :root:not([data-theme="light"]) .topnav .lockup.on-dark{display:block}
}
:root[data-theme="dark"] .topnav .lockup.on-light, .theme-dark .topnav .lockup.on-light{display:none}
:root[data-theme="dark"] .topnav .lockup.on-dark,  .theme-dark .topnav .lockup.on-dark{display:block}
:root[data-theme="light"] .topnav .lockup.on-light, .theme-light .topnav .lockup.on-light{display:block}
:root[data-theme="light"] .topnav .lockup.on-dark,  .theme-light .topnav .lockup.on-dark{display:none}
.topnav .navlink{font:500 14px/1 var(--sans);color:var(--muted);text-decoration:none;
                 padding:8px 11px;border-radius:var(--r2)}
.topnav .navlink:hover{color:var(--ink);background:var(--sunk)}
.topnav .navlink.on{color:var(--ink);background:var(--sunk)}
.topnav .grow{flex:1}
.topnav .standby{display:inline-flex;align-items:center;gap:6px;font:600 11px/1 var(--cond);
                 letter-spacing:.07em;text-transform:uppercase;padding:6px 10px;border-radius:999px;
                 border:1px solid var(--rule-strong);background:var(--surface);color:var(--muted);cursor:pointer}
.topnav .standby i{width:7px;height:7px;border-radius:50%;background:var(--faint);display:block}
.topnav .standby.on{background:var(--good-soft);border-color:transparent;color:var(--good)}
.topnav .standby.on i{background:var(--good)}
.topnav .acct{position:relative}
.topnav .caret{color:var(--faint);font-size:11px}
.topnav .menu{position:absolute;right:0;top:calc(100% + 6px);min-width:250px;background:var(--surface);
              border:1px solid var(--rule);border-radius:var(--r3);box-shadow:var(--shadow);padding:var(--s1);z-index:30}
.topnav .menu a,.topnav .menu > button{display:block;width:100%;text-align:left;font:400 14px/1.2 var(--sans);
              color:var(--ink);background:none;border:0;padding:9px 11px;border-radius:var(--r2);
              cursor:pointer;text-decoration:none}
.topnav .menu a:hover,.topnav .menu > button:hover{background:var(--sunk)}
.topnav .menu .sep{height:1px;background:var(--rule);margin:var(--s1) 0}
.topnav .menu .themerow{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);
                        padding:7px 11px;font:400 13px/1 var(--sans);color:var(--muted)}
.topnav .seg{display:inline-flex;border:1px solid var(--rule-strong);border-radius:var(--r2);overflow:hidden}
.topnav .seg button{font:500 11.5px/1 var(--sans);padding:5px 8px;border:0;background:var(--surface);
                    color:var(--muted);cursor:pointer}
.topnav .seg button.on{background:var(--accent-soft);color:var(--accent-ink)}
.topnav button:focus-visible,.topnav a:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ---------------------------------------------------------------------
 * The five-panel page. The shell only: panels bring their own contents.
 * ------------------------------------------------------------------- */
/* `overflow-x:hidden` is belt and braces, not the fix: the columns are allowed
   to shrink now (`flex:0 1` in panels.js) and cannot exceed the row. If some
   future panel does force a wider box, the shell clips it rather than growing
   a document-wide horizontal scrollbar that carries Panel 5 off the screen. */
.panels{flex:1;display:flex;min-height:0;overflow-x:hidden;background:var(--ground)}
/* The floor a column may be squeezed to. Five of them come to 740px, under the
   760px breakpoint where the one-at-a-time phone layout takes over — so no
   viewport width leaves the columns wider than the window. Kept in step with
   MINW in panels.js. */
/* ---- one panel is the primary one, and it should look like it ----
 *
 * All five columns were the same surface, so the conversation — the thing you
 * are actually here to read — had no more visual weight than the file browser
 * beside it. The four supporting columns now sit back on `--ground` and the
 * conversation stays on `--surface`, which is the elevation ladder this
 * palette already defines: ground → surface is a step *up* in both themes
 * (L 0.960 → 1.000 light, 0.185 → 0.225 dark), so the same rule reads
 * correctly either way rather than inverting in one of them.
 *
 * Elevation, not colour. Tinting one column would have meant a sixth surface
 * token and a decision about hue that the palette deliberately does not make. */
.panel-col{display:flex;flex-direction:column;min-width:148px;min-height:0;position:relative;
           background:var(--ground);border-right:1px solid var(--rule)}
.panel-col:last-child{border-right:0}

/* It no longer needs a width floor of its own. It used to: `grow:true` gave it
   the *remaining* space after four fixed columns totalling 1290px, so on a
   1500px window the primary panel was the narrowest on the board at 210px.
   Every column now takes an equal share of the row, so this one is 20% like
   the rest and the floor that compensated for the old model is gone. */
.panel-col[data-panel="talk"]{background:var(--surface);box-shadow:var(--shadow);z-index:1}
/* The rules either side of the raised column are load-bearing here rather than
   decorative — they are the edge of the thing being raised. */
.panel-col[data-panel="talk"]{border-left:1px solid var(--rule-strong);border-right:1px solid var(--rule-strong)}
.panel-col[data-panel="talk"] .panel-h{background:var(--surface);border-bottom-color:var(--rule-strong)}
/* The header label carries the same signal for anyone who cannot see the
   elevation — a step up in weight and ink, not a colour on its own. */
.panel-col[data-panel="talk"] .panel-h .t{color:var(--ink);font-weight:700}
.panel-col[data-panel="talk"] .panel-h .collapse{color:var(--muted)}
/* Collapsed, it is no longer the primary anything: drop the lift so a folded
   column does not sit proud of the board. */
.panel-col[data-panel="talk"].collapsed{box-shadow:none;background:var(--ground)}
.panel-col[data-panel="talk"].collapsed .panel-h{background:var(--surface-2)}
/* min-width beats flex-basis, so a collapsed column has to lower its own floor
   or it would refuse to collapse at all. */
.panel-col.collapsed{flex:0 0 42px !important;min-width:42px}
.panel-col.collapsed .panel-b,.panel-col.collapsed .t,.panel-col.collapsed .tools{display:none}
.panel-col.collapsed .panel-h{justify-content:center;padding:var(--s3) 0}
.panel-h{display:flex;align-items:center;gap:var(--s2);padding:0 var(--s3);height:38px;
         border-bottom:1px solid var(--rule);background:var(--surface-2);flex:0 0 auto}
.panel-h .t{font:600 11px/1 var(--cond);letter-spacing:.13em;text-transform:uppercase;color:var(--muted)}
.panel-h .tools{margin-left:auto;display:flex;align-items:center;gap:var(--s2)}
/* The collapse control carries the panel's icon, and when the panel is folded
   it *is* the panel — 42px of strip with nothing else on it. So it is a proper
   target rather than a 12px glyph, and it sits at --muted rather than --faint:
   the one thing telling you a column exists should not be the dimmest ink on
   the screen. */
.panel-h .collapse{border:0;background:none;color:var(--muted);cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;
  width:24px;height:24px;padding:0;border-radius:var(--r1);flex:0 0 auto}
.panel-h .collapse:hover{background:var(--sunk);color:var(--ink)}
.panel-h .collapse:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.panel-col[data-panel="talk"] .panel-h .collapse{color:var(--ink-2)}
/* Folded, the icon is the whole affordance: give it the width of the strip so
   the hover target — and the tooltip that names the panel — is the strip. */
.panel-col.collapsed .panel-h .collapse{width:100%;height:30px}
.panel-b{flex:1;overflow:auto;min-height:0}

/* ---- a scrollable panel has to look scrollable ----
 *
 * A column holding thirty-one calls in nine rows' worth of height looked like
 * a column holding nine calls. Nothing was missing and nothing said so.
 *
 * A styled scrollbar was the obvious answer and it does not work: macOS uses
 * overlay scrollbars, so the track occupies no layout width and stays
 * invisible until something is already scrolling — measured at
 * `offsetWidth - clientWidth === 0` with the rules applied. An affordance you
 * can only see *after* you have discovered the thing it points at is not one.
 *
 * So: scroll shadows. Four backgrounds — two "covers" pinned to the content
 * (`local`) and two shadows pinned to the box (`scroll`). At the top of the
 * list the cover sits over the shadow and hides it; scroll down and the cover
 * moves away with the content, revealing a soft edge that says there is more.
 * It is correct at both ends by construction rather than by a scroll handler,
 * and it needs no JavaScript.
 *
 * The cover has to be the panel's own background, so there are two variants —
 * the supporting columns sit on --ground, the conversation on --surface. */
.panel-b{scrollbar-width:thin;scrollbar-color:var(--rule-strong) transparent}
.panel-b::-webkit-scrollbar{width:10px;height:10px}
.panel-b::-webkit-scrollbar-track{background:transparent}
.panel-b::-webkit-scrollbar-thumb{background:var(--rule-strong);border-radius:999px;
  border:3px solid transparent;background-clip:content-box}
.panel-b::-webkit-scrollbar-thumb:hover{background:var(--muted);background-clip:content-box}

.panel-col > .panel-b{
  background:
    linear-gradient(var(--ground) 40%, transparent) top    / 100% 26px no-repeat local,
    linear-gradient(transparent, var(--ground) 60%) bottom / 100% 26px no-repeat local,
    linear-gradient(to bottom, var(--rule-strong), transparent) top    / 100% 9px no-repeat scroll,
    linear-gradient(to top,    var(--rule-strong), transparent) bottom / 100% 9px no-repeat scroll,
    var(--ground);
}
.panel-col[data-panel="talk"] > .panel-b{
  background:
    linear-gradient(var(--surface) 40%, transparent) top    / 100% 26px no-repeat local,
    linear-gradient(transparent, var(--surface) 60%) bottom / 100% 26px no-repeat local,
    linear-gradient(to bottom, var(--rule-strong), transparent) top    / 100% 9px no-repeat scroll,
    linear-gradient(to top,    var(--rule-strong), transparent) bottom / 100% 9px no-repeat scroll,
    var(--surface);
}
.grip{position:absolute;top:0;right:-3px;width:6px;height:100%;cursor:col-resize;z-index:5}
/* The strip is only for the narrow layout; above the breakpoint it is not
   there at all rather than hidden, so it cannot take a tab stop. */
.panelbar{display:none}

/* The nav on a phone. Brand, two links, the standby switch and an account
   button came to 485px on a 390px screen, so the page scrolled sideways even
   once the panels behaved. Nothing is dropped: the standby switch keeps its
   dot and its colour and loses only the word, and the account button truncates
   rather than pushing the row wider. */
@media (max-width: 760px) {
  :root{--nav-h:48px}
  .topnav{height:var(--nav-h);padding:0 var(--s2);gap:var(--s1)}
  .topnav .brand{margin-right:var(--s1);font-size:15px}
  /* No room for the endorsed lockup at its 32px floor — the mark and the name. */
  .topnav .lockups{display:none}
  .topnav .compact{display:flex;align-items:center;gap:var(--s2)}
  .topnav .navlink{padding:8px 8px}
  .topnav .standby{font-size:0;gap:0;padding:7px 9px}
  .topnav .standby i{width:8px;height:8px}
  /* Everything in the row is nowrap, so without something allowed to give the
     row simply exceeds the screen. The account button is the thing that can
     lose characters without losing meaning. */
  .topnav .acct{min-width:0;flex:0 1 auto}
  .topnav #nav-acct{max-width:100%;min-width:0;display:flex;align-items:center;gap:5px;
                    padding:7px 9px;font-size:13px}
  /* Truncate the name, not the caret — the caret is what says this opens. */
  .topnav #nav-acct .who{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
  .topnav #nav-acct .caret{flex:none}
  .topnav .menu{min-width:min(280px,92vw)}
}

/* ---------------------------------------------------------------------
 * On a phone. Five columns came to 1294px on a 390px screen, so the page
 * scrolled sideways and the conversation — the thing a person who tapped the
 * link in a text came to read — sat off the right edge, collapsed to 1px.
 * Same panels, same code, one at a time.
 * ------------------------------------------------------------------- */
@media (max-width: 760px) {
  .panels{flex-direction:column}
  .panelbar{display:flex;gap:2px;padding:var(--s2) var(--s2) 0;background:var(--surface);
            border-bottom:1px solid var(--rule);overflow-x:auto;flex:0 0 auto;
            -webkit-overflow-scrolling:touch;scrollbar-width:none}
  .panelbar::-webkit-scrollbar{display:none}
  .panelbar button{flex:0 0 auto;border:0;background:none;cursor:pointer;white-space:nowrap;
                   font:600 11px/1 var(--cond);letter-spacing:.11em;text-transform:uppercase;
                   color:var(--muted);padding:10px 12px;border-bottom:2px solid transparent}
  .panelbar button.on{color:var(--ink);border-bottom-color:var(--accent)}
  /* The desktop layout writes flex widths inline and remembers a collapsed
     state; neither means anything here, so both are overridden outright. */
  .panel-col{display:none !important;border-right:0;border-bottom:0}
  /* One column at a time down here, so there is nothing for the conversation
     to be raised *against*; it takes the plain surface like every other. */
  .panel-col[data-panel="talk"]{box-shadow:none;border-left:0;border-right:0;background:var(--surface)}
  .panels[data-only] .panel-col{flex:1 1 auto !important}
  .panels[data-only="calls"] .panel-col[data-panel="calls"],
  .panels[data-only="room"]  .panel-col[data-panel="room"],
  .panels[data-only="talk"]  .panel-col[data-panel="talk"],
  .panels[data-only="now"]   .panel-col[data-panel="now"],
  .panels[data-only="who"]   .panel-col[data-panel="who"]{display:flex !important}
  /* The panel's own header repeats what the strip already says, and the grip
     drags a width the layout no longer has. */
  .panel-h{display:none}
  .grip{display:none}
}
.grip:hover{background:var(--accent-soft)}

/* --- panel: lines --- */
/* Three controls now — search, which calls, which language. They wrap rather
   than squeeze: the search box takes the first line and the two dropdowns
   share the second, so none of them is unreadable in a narrow column. */
.panels .filters{display:flex;flex-wrap:wrap;gap:var(--s2);padding:var(--s2) var(--s3);
                 border-bottom:1px solid var(--rule)}
.panels .filters input,.panels .filters select{flex:1;min-width:0;font:13px var(--sans);color:var(--ink);
  background:var(--surface-2);border:1px solid var(--rule-control);border-radius:var(--r2);padding:6px 8px}
.panels .filters .cl-q{flex:1 1 100%}
.panels .filters select{flex:1 1 40%}
.panels .lines{display:flex;flex-direction:column}
/* One height for every row, whatever is in it.
   A board is read by running an eye down it, and rows that grow a pixel when a
   badge wraps make that a scan of ragged blocks instead of a column. So the
   height is fixed and the second line clips rather than wrapping — a badge
   that will not fit is worth less than the shape of the list. */
.panels .line{height:56px;padding:0 var(--s3);border-bottom:1px solid var(--rule);cursor:pointer;
              display:flex;flex-direction:row;align-items:center;gap:var(--s3)}
.panels .line .cl-body{flex:1;min-width:0;display:flex;flex-direction:column;
                       justify-content:center;gap:5px;overflow:hidden}
.panels .line:hover{background:var(--surface-2)}
.panels .line.on{background:var(--accent-soft);box-shadow:inset 3px 0 0 var(--accent)}
.panels .line.alone{box-shadow:inset 3px 0 0 var(--crit)}
.panels .line.alone.on{box-shadow:inset 3px 0 0 var(--crit),inset 0 0 0 99px var(--accent-soft)}
.panels .line .l1{display:flex;align-items:center;gap:7px;min-width:0}
.panels .line .l2{font:13px/1.3 var(--sans);color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* One line, clipped. Wrapping is what made the rows different heights. */
.panels .line .l3{display:flex;align-items:center;gap:5px;flex-wrap:nowrap;
                  overflow:hidden;min-width:0}
.panels .line .l3 > *{flex:none}
.panels .empty{padding:var(--s5) var(--s4);color:var(--muted);font-size:13.5px;text-align:center}
.panels .count{font:12px var(--mono);color:var(--muted)}

/* --- panel: conversation --- */
/* Density. A bilingual turn cost ~90px, which put 7.8 turns in a 700px thread;
   the savings here come entirely from padding, gaps and leading and take it to
   ~69px. The body stays at 14.5px on purpose — this is an intelligence
   transcript, and the 13px gloss under it is already near the floor. */
.panels .thread{padding:var(--s3) var(--s4);display:flex;flex-direction:column;gap:var(--s2);
                overflow:auto;flex:1;min-height:0}
.panels .aipaused{background:var(--warn-soft);color:var(--warn);padding:var(--s3) var(--s4);
  border-bottom:1px solid var(--rule);font-size:13.5px}
.panels .aipaused .said{color:var(--ink-2);font-style:italic;margin-top:4px}
.panels .composer{border-top:1px solid var(--rule);padding:var(--s3);background:var(--surface-2);flex:0 0 auto}
.panels .composer .ctl{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s2);flex-wrap:wrap}
.panels .composer .grow{flex:1}
.panels .composer .say{display:flex;gap:var(--s2)}
.panels .composer input{flex:1;min-width:0;font:14px var(--sans);color:var(--ink);background:var(--surface);
  border:1px solid var(--rule-control);border-radius:var(--r2);padding:9px 11px}
.panels .composer select{font:13px var(--sans);color:var(--ink-2);background:var(--surface);
  border:1px solid var(--rule-control);border-radius:var(--r2);padding:0 8px}
.panels .msg{max-width:86%} .panels .msg.source{margin-left:auto}
.panels .msg .who{font:600 10.5px/1 var(--cond);letter-spacing:.09em;text-transform:uppercase;color:var(--muted);margin-bottom:2px}
.panels .msg .bubble{padding:5px 10px;border-radius:var(--r3);background:var(--sunk);font-size:14.5px;line-height:1.4}
.panels .msg.source .bubble{background:var(--info-soft)}
.panels .msg.ai .bubble{background:var(--teal-soft)}
.panels .msg.human .bubble{background:var(--accent-soft)}
.panels .msg.system{max-width:none;margin:0 auto}
/* The one dashed box in the thread, and the only thing that ever made a system
   line — "Alice joined the room." — look like a quoted turn was the box itself.
   Centred italic muted at 12.5px is already unmistakably not dialogue.
   NOTE: app.css:35 carries a competing rule on this exact selector that has
   been losing on specificity; it still declares the dashed border. */
.panels .msg.system .bubble{background:none;border:0;color:var(--muted);
  padding:1px var(--s2);font-size:12.5px;font-style:italic;text-align:center}
.panels .msg .gloss{font-size:13px;line-height:1.4;color:var(--muted);padding:2px 10px 0}
.panels .msg .rtl{direction:rtl;text-align:right}
/* Arabic and Persian ascenders clip before Latin does, so the right-to-left
   gloss keeps the leading the rest of the thread just gave up. */
.panels .msg .gloss.rtl,.panels .msg .bubble.rtl{line-height:1.65}

/* --- panel: room --------------------------------------------------------
 * Who is in the call, who is coming, who we could ask, and the back channel.
 * Sections are separated by rules rather than boxed, so four of them stack in
 * one narrow column without reading as clutter.
 * ---------------------------------------------------------------------- */
/* `min-height:100%` let the column grow past the panel and hand the scrolling
   to `.panel-b`, which carried the message box off the bottom of the screen.
   `height:100%;min-height:0` is Panel 3's `.tk` pattern, and it is why Panel
   3's composer has always genuinely pinned. */
.rm{display:flex;flex-direction:column;height:100%;min-height:0}
/* the three rosters scroll together; the back channel below them does not */
.rm-scroll{flex:1;min-height:0;overflow:auto}
.rm-sec{padding:var(--s3) var(--s3) var(--s4);border-bottom:1px solid var(--rule)}
.rm-sec:last-child{border-bottom:0}
.rm-h{font:600 10.5px/1 var(--cond);letter-spacing:.13em;text-transform:uppercase;
      color:var(--muted);margin:0 0 var(--s2)}
.rm-hint{font:400 11.5px/1.4 var(--sans);color:var(--faint);margin:0 0 var(--s2)}
.rm-empty{font:400 13px/1.5 var(--sans);color:var(--faint);margin:var(--s1) 0}
.rm-empty.pad{padding:var(--s4)}
.rm-empty code{font:11.5px var(--mono);background:var(--sunk);padding:1px 4px;border-radius:var(--r1)}
.rm-count{font:500 10.5px/1 var(--cond);letter-spacing:.06em;text-transform:uppercase;color:var(--faint)}

/* a person, a request, a candidate — one row shape, three uses */
.rm-p,.rm-req,.rm-cand{display:flex;align-items:flex-start;gap:var(--s2);padding:var(--s2) 0;
                       border-top:1px solid var(--rule)}
.rm-p:first-child,.rm-req:first-child,.rm-cand:first-child{border-top:0}
.rm-who{flex:1;min-width:0}
.rm-who b{font:500 13.5px/1.35 var(--sans);color:var(--ink);display:block}
.rm-sub{display:flex;flex-wrap:wrap;align-items:center;gap:5px;margin-top:2px;
        font:400 11.5px/1.5 var(--sans);color:var(--muted)}
/* The address under a name in the Room panel. Quieter than the name, wider
   than the chips, and truncated rather than wrapped — an address is scanned or
   copied, and a two-line one pushes every row out of step. */
.rm-mail{font:400 11.5px/1.35 var(--mono);color:var(--muted);margin:2px 0 1px;
         overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}
.rm-sub .ok{color:var(--good)} .rm-sub .muted{color:var(--faint)}
/* Each row carries one meta run, joined in JS with a separator bound to the
   word after it, so a wrapped line never ends on a stranded dot. */
.rm-sub > span:not(.rm-lang){min-width:0}
.rm-note{font:400 11.5px/1.45 var(--sans);color:var(--warn);margin-top:3px}

/* present, but how present: someone who just spoke reads stronger than
   someone who joined and has said nothing */
.rm-p.quiet .rm-who b{color:var(--ink-2)}
.rm-p.fresh .rm-who b{color:var(--ink)}
.rm-p.fresh{background:var(--good-soft);margin:0 calc(var(--s2) * -1);padding-left:var(--s2);
            padding-right:var(--s2);border-radius:var(--r2);border-top-color:transparent}
.rm-p.ai .rm-who b{color:var(--teal)}
.rm-req.stale .rm-who b{color:var(--warn)}

.rm-dot{width:7px;height:7px;border-radius:50%;background:var(--faint);flex:none;margin-top:5px}
.rm-dot.on{background:var(--good)} .rm-dot.live{background:var(--good);box-shadow:0 0 0 3px var(--good-soft)}
.rm-dot.ai{background:var(--teal);box-shadow:0 0 0 3px var(--teal-soft)}
.rm-dot.off{background:var(--faint)}

.rm-lang{display:inline-block;font:600 9.5px/1 var(--mono);letter-spacing:.04em;padding:3px 5px;
         border-radius:var(--r1);background:var(--sunk);color:var(--muted)}
.rm-lang.match{background:var(--accent-soft);color:var(--accent-ink)}

.rm-tag{font:600 9.5px/1 var(--cond);letter-spacing:.07em;text-transform:uppercase;
        padding:4px 7px;border-radius:999px;background:var(--sunk);color:var(--muted);flex:none;white-space:nowrap}
.rm-tag.ok{background:var(--good-soft);color:var(--good)}
.rm-tag.ai{background:var(--teal-soft);color:var(--teal)}
.rm-tag.warn{background:var(--warn-soft);color:var(--warn)}

.rm-btn{font:500 12px/1 var(--sans);padding:6px 10px;border-radius:var(--r2);flex:none;
        border:1px solid var(--rule-strong);background:var(--surface);color:var(--ink);cursor:pointer}
.rm-btn:hover{background:var(--surface-2)}
.rm-btn.quiet{border-color:transparent;background:transparent;color:var(--muted)}
.rm-btn.quiet:hover{background:var(--sunk);color:var(--ink)}
.rm-btn:disabled{opacity:.45;cursor:not-allowed}
.rm-btn:focus-visible,.rm-in:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/* The margin belongs to the control, not the wrapper: with the "why" field
   gone the search box is often hidden, and a wrapper with its own margin left
   an 8px hole above an empty list. */
.rm-ctl{display:flex;flex-direction:column;gap:var(--s1)}
.rm-ctl .rm-in{margin-bottom:var(--s2)}
.rm-in{width:100%;font:400 12.5px/1.3 var(--sans);color:var(--ink);background:var(--surface-2);
       border:1px solid var(--rule-control);border-radius:var(--r2);padding:6px 9px}
.rm-in::placeholder{color:var(--faint)}

/* the back channel keeps its own scroll so a long conversation cannot push
   the roster off the panel */
/* A footer, not a fifth thing in the stack: it takes what it needs up to
   two-fifths of the panel and then scrolls inside itself. The 320px cap on the
   log is gone — the section's own ceiling does that job now, and at any panel
   height rather than one. */
.rm-bcsec{display:flex;flex-direction:column;flex:0 0 auto;min-height:0;max-height:40%;
          border-top:1px solid var(--rule);border-bottom:0}
.rm-log{flex:1;overflow:auto;min-height:60px;padding-right:2px}
.rm-msg{padding:var(--s2) 0;border-top:1px solid var(--rule)}
.rm-msg:first-child{border-top:0}
.rm-msg-h{display:flex;align-items:center;gap:6px;font:400 11px/1.4 var(--sans);color:var(--faint)}
.rm-msg-h b{font:600 11.5px/1.4 var(--sans);color:var(--ink-2)}
.rm-msg.from-ai .rm-msg-h b{color:var(--teal)}
.rm-msg-b{font:400 13px/1.5 var(--sans);color:var(--ink);margin-top:2px;white-space:pre-wrap;word-break:break-word}
.rm-msg.to-ai .rm-msg-b{border-left:2px solid var(--teal);padding-left:var(--s2)}
.rm-form{display:flex;gap:var(--s2);margin-top:var(--s2)}

/* --- panel: plan ---------------------------------------------------------
 * The plan the line is following, and nothing else: the whole tree, drawn
 * once per revision, with the step it is on lit, the steps passed ticked in
 * green, the arms not taken folded to one grey line, and under each question
 * the caller has answered, what they said. The rail takes the column's whole
 * height and scrolls on its own.
 *
 * The founder's ruling on the rail, applied in colour: completed steps are
 * clearly green — text and a tick — and skipped parts of the tree are grey;
 * the answers are filled in under the questions. Hue alone is not relied on:
 * the tick, the fold and the italic answer carry the same state.
 *
 * Everything that moves here is a class toggled on an element that stays put
 * (panel-now.js only re-classes after the first build), which is what makes
 * these transitions real: a step turning green fades in, the arm not taken
 * folds shut. `max-height` transitions from a large value rather than `none`,
 * because `none` has no number to animate from. Reduced motion turns all of
 * it off — the state is still legible from colour and the tick.
 *
 * The `.nw-` vocabulary is shared with the caller column beside it: section,
 * heading, note, mono, chip. Both columns are read the same way.
 * ---------------------------------------------------------------------- */
.nw{display:flex;flex-direction:column;height:100%;min-height:0}
.nw-sec{padding:var(--s3) var(--s3) var(--s4);border-bottom:1px solid var(--rule)}
.nw-sec:last-child{border-bottom:0}
.nw-h{display:flex;align-items:baseline;gap:var(--s2);flex-wrap:wrap;
      font:600 10.5px/1 var(--cond);letter-spacing:.13em;text-transform:uppercase;
      color:var(--muted);margin:0 0 var(--s3)}
.nw-asof{margin-left:auto;font:400 10.5px/1 var(--mono);letter-spacing:0;
         text-transform:none;color:var(--faint);white-space:nowrap}
.nw-note{font:400 12.5px/1.5 var(--sans);color:var(--muted);margin:0}
.nw-mono{font-family:var(--mono)}
.nw-chip{display:inline-block;font:600 10px/1 var(--cond);letter-spacing:.06em;
         padding:4px 7px;border-radius:999px;background:var(--sunk);color:var(--muted)}
.nw-chip.ok{background:var(--good-soft);color:var(--good)}
.nw-chip.warn{background:var(--warn-soft);color:var(--warn)}
.nw-chip.crit{background:var(--crit-soft);color:var(--crit)}
.nw-chip.info{background:var(--info-soft);color:var(--info)}
/* "+3 more" — the affordance that lets a capped list stay capped honestly. It
   is a button, not a link: it changes this page and goes nowhere. */
.nw-more{margin-top:var(--s2);border:0;background:none;padding:0;cursor:pointer;
         font:600 10px/1.4 var(--cond);letter-spacing:.09em;text-transform:uppercase;
         color:var(--accent-ink);text-align:left}
.nw-more:hover{text-decoration:underline}
.nw-more:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r1)}
/* the words shown while there is no rail to show */
.nw-wait{margin:0;padding:var(--s3);font:400 12.5px/1.5 var(--sans);color:var(--muted)}
.nw-wait[hidden]{display:none}

/* the rail: the whole column, scrolling on its own; nothing when there is no
   plan, so a snapshot without one renders no rail at all */
.nw-plan{flex:1 1 auto;max-height:none;min-height:0;overflow:auto}
.nw-plan:empty{display:none}
.nw-plansec{padding-bottom:var(--s2)}
.nw-pn-name{color:var(--ink-2);text-transform:none;letter-spacing:0;font:600 11px/1 var(--sans)}
.nw-pn-why{font:400 11.5px/1.45 var(--sans);color:var(--muted);margin:0 0 var(--s2)}
.nw-pn-why:empty{display:none}
.nw-pn-tree,.nw-pn-arms,.nw-pn-then{list-style:none;margin:0;padding:0}
.nw-pn-tree{display:flex;flex-direction:column;gap:2px}
.nw-pn-arms{margin-left:var(--s3);padding-left:var(--s2);border-left:1px solid var(--rule)}
.nw-pn-then{margin-left:var(--s3);padding-left:var(--s2);border-left:1px dashed var(--rule);
            display:flex;flex-direction:column;gap:2px;overflow:hidden;max-height:1200px;opacity:1;
            transition:max-height .45s ease,opacity .3s ease}
.nw-pn-row{display:flex;align-items:baseline;gap:var(--s2);padding:3px var(--s2);
           border-left:2px solid var(--rule);border-radius:0 var(--r1) var(--r1) 0;
           background:transparent;opacity:.62;
           transition:border-color .35s ease,background-color .35s ease,opacity .35s ease}
.nw-pn-k{flex:0 0 auto;font:600 9px/1.5 var(--cond);letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.nw-pn-t{flex:1 1 auto;min-width:0;font:400 12px/1.4 var(--sans);color:var(--ink-2);overflow-wrap:anywhere;
         transition:color .35s ease}
.nw-pn-t.exact::before,.nw-pn-t.exact::after{content:"\201C";color:var(--faint)}
.nw-pn-t.exact::after{content:"\201D"}
.nw-pn.kind-prompt .nw-pn-t{white-space:pre-wrap}
.nw-pn-cond{flex:1 1 auto;min-width:0;font:italic 400 11.5px/1.4 var(--sans);color:var(--ink-2);overflow-wrap:anywhere;
            transition:color .35s ease}
.nw-pn-cond .op{font:600 8.5px/1 var(--cond);font-style:normal;letter-spacing:.1em;text-transform:uppercase;
                color:var(--faint);margin:0 4px}
.nw-pn-lang{flex:0 0 auto;font-size:9.5px;color:var(--faint);background:var(--sunk);padding:1px 4px;border-radius:var(--r1)}
.nw-pn-tick{flex:0 0 auto;width:10px;font:600 10px/1.4 var(--mono);color:var(--good);opacity:0;transition:opacity .35s ease}
.nw-pn-tick::before{content:"\2713"}
.nw-pn-n{display:none;margin-left:auto;font:400 9.5px/1.5 var(--mono);color:var(--faint);white-space:nowrap}

/* the caller's answer, under the question it answered: quieter than the
   question, set apart as their words, and green-edged because the step is done */
.nw-pn-ans{margin:1px 0 3px var(--s3);padding:1px var(--s2);display:flex;align-items:baseline;gap:var(--s2);
           border-left:2px solid var(--good-soft);font:400 11.5px/1.45 var(--sans);color:var(--muted)}
.nw-pn-ans[hidden]{display:none}
.nw-pn-ansk{flex:0 0 auto;font:600 8.5px/1.6 var(--cond);letter-spacing:.1em;text-transform:uppercase;color:var(--good)}
.nw-pn-anst{flex:1 1 auto;min-width:0;font-style:italic;overflow-wrap:anywhere}

/* the states, one class each (panel-now.js STATUSES) */
.nw-pn.is-current > .nw-pn-row{opacity:1;border-left-color:var(--accent);background:var(--accent-soft)}
.nw-pn.is-current > .nw-pn-row .nw-pn-t,.nw-pn.is-current > .nw-pn-row .nw-pn-cond{color:var(--ink);font-weight:500}
.nw-pn.is-current > .nw-pn-row .nw-pn-k{color:var(--accent)}
.nw-pn.is-waiting > .nw-pn-row{opacity:1;border-left-color:var(--warn);background:var(--warn-soft)}
.nw-pn.is-waiting > .nw-pn-row .nw-pn-k{color:var(--warn)}
/* done, entered, taken: green — the edge, the label, the words and the tick */
.nw-pn.is-entered > .nw-pn-row,.nw-pn.is-taken > .nw-pn-row,
.nw-pn.is-done > .nw-pn-row{opacity:1;border-left-color:var(--good)}
.nw-pn.is-done > .nw-pn-row .nw-pn-k,.nw-pn.is-entered > .nw-pn-row .nw-pn-k,
.nw-pn.is-taken > .nw-pn-row .nw-pn-k{color:var(--good)}
.nw-pn.is-done > .nw-pn-row .nw-pn-t,.nw-pn.is-done > .nw-pn-row .nw-pn-cond,
.nw-pn.is-entered > .nw-pn-row .nw-pn-t,.nw-pn.is-entered > .nw-pn-row .nw-pn-cond,
.nw-pn.is-taken > .nw-pn-row .nw-pn-t,.nw-pn.is-taken > .nw-pn-row .nw-pn-cond{color:var(--good)}
.nw-pn.is-done > .nw-pn-row .nw-pn-tick,.nw-pn.is-entered > .nw-pn-row .nw-pn-tick,
.nw-pn.is-taken > .nw-pn-row .nw-pn-tick{opacity:1}
/* skipped: grey, all of it */
.nw-pn.is-skipped > .nw-pn-row{opacity:.55;border-left-color:var(--rule)}
.nw-pn.is-skipped > .nw-pn-row .nw-pn-t,.nw-pn.is-skipped > .nw-pn-row .nw-pn-cond,
.nw-pn.is-skipped > .nw-pn-row .nw-pn-k{color:var(--faint)}
/* the arms: which way the plan went, and the ways it did not */
.nw-pn-arm > .nw-pn-row{border-left-style:dashed;padding-top:2px;padding-bottom:2px}
.nw-pn-arm.is-taken > .nw-pn-row{opacity:1;border-left-style:solid;border-left-color:var(--good)}
.nw-pn-arm.is-taken > .nw-pn-row .nw-pn-k,.nw-pn-arm.is-taken > .nw-pn-row .nw-pn-cond{color:var(--good)}
.nw-pn-arm.is-skipped > .nw-pn-row{opacity:.55;border-left-color:var(--rule)}
.nw-pn-arm.is-skipped > .nw-pn-row .nw-pn-k,.nw-pn-arm.is-skipped > .nw-pn-row .nw-pn-cond,
.nw-pn-arm.is-skipped > .nw-pn-row .nw-pn-n{color:var(--faint)}
.nw-pn-arm.is-collapsed > .nw-pn-then{max-height:0;opacity:0;border-color:transparent}
.nw-pn-arm.is-collapsed > .nw-pn-row > .nw-pn-n{display:inline}
/* the step being worked breathes, once a second, so the eye finds it */
@keyframes nw-pn-breathe{0%,100%{box-shadow:0 0 0 0 var(--accent-soft)}50%{box-shadow:0 0 0 3px var(--accent-soft)}}
.nw-pn.is-current > .nw-pn-row{animation:nw-pn-breathe 2s ease-in-out infinite}
@media (prefers-reduced-motion: reduce){
  .nw-pn-row,.nw-pn-tick,.nw-pn-then,.nw-pn-t,.nw-pn-cond{transition:none}
  .nw-pn.is-current > .nw-pn-row{animation:none;box-shadow:0 0 0 2px var(--accent-soft)}
}

/* --- the density class ---------------------------------------------------
 * One class on one element. The day someone decides 11.5px is too small, it
 * is one deletion rather than a dozen edits scattered through the panel.
 * ---------------------------------------------------------------------- */
.nw--tight .nw-sec{padding:var(--s2) var(--s3)}
/* A section heading that wraps to two lines spends 13px saying nothing. Held
   on one line; the chip gives ground first. */
.nw--tight .nw-h{margin-bottom:var(--s2);flex-wrap:nowrap;white-space:nowrap;overflow:hidden}
.nw--tight .nw-note{font-size:11.5px}
.nw--tight .nw-pn-t{font-size:11.5px}
.nw--tight .nw-pn-cond{font-size:11px}

/* --- panel: who is calling ----------------------------------------------
 * The line the call came in on, and what earlier calls on it produced. The
 * number, in the weight of the fact it is; where the network traced it to,
 * with the precision named; a map of that trace the full width of the column,
 * with a small margin; the warnings that still apply; and the extension's
 * earlier calls — date, duration, who spoke, one line of summary.
 *
 * Nothing about a person: there is no file for a caller. The `.wh-` classes
 * are this panel's; `.nw-sec/.nw-h/.nw-note/.nw-mono/.nw-chip` are shared with
 * the plan column so the two read alike.
 * ---------------------------------------------------------------------- */
.wh{display:flex;flex-direction:column;min-height:100%}
.wh-line{flex:0 0 auto}
.wh-earlier{flex:1 1 auto;min-height:0}
.wh-top{border-bottom:0;padding-bottom:var(--s3)}
/* the number, given the weight of the fact it is */
.wh-num{font:500 19px/1.2 var(--mono);color:var(--ink);letter-spacing:-.01em;margin-bottom:var(--s2)}
/* Label beside value while there is room for both, label above it when there
   is not. A real floor plus wrapping means the row reflows instead of the
   word: a narrow column once rendered "Guadalajar / a, MX". */
.wh-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2)}
.wh-lbl{flex:0 0 60px;font:600 9.5px/1.5 var(--cond);letter-spacing:.11em;
        text-transform:uppercase;color:var(--faint)}
.wh-val{flex:1 1 10ch;min-width:10ch;font:400 13px/1.45 var(--sans);color:var(--ink-2);
        overflow-wrap:break-word;word-break:normal}
.wh-cap{margin:6px 0 0;font:400 11px/1.4 var(--sans);color:var(--faint)}
.wh-cap .nw-mono{font-size:10.5px}

/* The map: the whole width of the column at 8:5, inside a small margin. The
   still is requested at 640x400@2x, which is the box at retina on a column
   up to 640px wide; wider than that it is upscaled rather than re-requested.
   `box-sizing:border-box` so the padding is inside the 100%, not added to it. */
.wh-map{width:100%;box-sizing:border-box;aspect-ratio:8/5;padding:0 var(--s3) var(--s3);
        border-bottom:1px solid var(--rule)}
.wh-mapbox{position:relative;width:100%;height:100%;box-sizing:border-box;overflow:hidden;
           border:1px solid var(--rule);border-radius:var(--r2);background:var(--sunk);line-height:0}
.wh-mapbox.none{display:flex;align-items:center;justify-content:center;
                font:400 11.5px/1.4 var(--sans);color:var(--faint);line-height:1.4;text-align:center}
.wh-mapimg{display:block;width:100%;height:100%;object-fit:cover}
.wh-mapbox .wmap{display:block;width:100%;height:100%}
/* the way into a map you can actually move. Only drawn when there are tiles to
   move — an Expand that opens an empty overlay is worse than no Expand. */
.wh-expand{position:absolute;right:6px;bottom:6px;border:1px solid var(--rule);
           background:var(--surface);color:var(--ink-2);cursor:pointer;
           font:600 9.5px/1 var(--cond);letter-spacing:.08em;text-transform:uppercase;
           padding:5px 7px;border-radius:var(--r1)}
.wh-expand:hover{background:var(--sunk);color:var(--ink)}
.wh-expand:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/* a warning that still applies: the language on a fallback, a recording that
   did not upload */
.wh-flag{margin:var(--s2) var(--s3) 0;padding:var(--s2) var(--s3);border-radius:var(--r2);
         background:var(--warn-soft);color:var(--warn);font:500 12px/1.45 var(--sans)}
.wh-flag.info{background:var(--info-soft);color:var(--info)}

/* earlier calls from this extension: a list of records, each one line of when
   and who and one line of what */
.wh-hist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--s2)}
.wh-call{padding:var(--s1) 0 var(--s1) var(--s2);border-left:2px solid var(--rule-strong)}
.wh-call-h{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2);
           font:400 11px/1.4 var(--sans);color:var(--faint)}
.wh-call-when{color:var(--ink-2);font-size:11px;white-space:nowrap}
.wh-call-dur{font-size:10.5px;white-space:nowrap}
.wh-call-who{margin-left:auto;font:500 11px/1.4 var(--sans);color:var(--muted);
             white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:55%}
.wh-call-sum{margin:2px 0 0;font:400 12.5px/1.45 var(--sans);color:var(--ink-2);
             overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wh-call-sum.none{color:var(--faint);font-style:italic}

/* --- the expanded map ----------------------------------------------------
 * Above the topnav (20) and the account menu (30), because a modal that a menu
 * can sit on top of is not a modal. Leaflet and its stylesheet are fetched
 * from a CDN on the first Expand click and never for anyone who does not
 * click, so nothing here costs a page load anything.
 * ---------------------------------------------------------------------- */
.wh-ov{position:fixed;inset:0;z-index:40;background:var(--scrim);
       display:flex;align-items:center;justify-content:center;padding:var(--s5)}
.wh-ovbox{display:flex;flex-direction:column;width:min(1100px,100%);height:min(760px,100%);
          background:var(--surface);border:1px solid var(--rule);border-radius:var(--r3);
          overflow:hidden;box-shadow:var(--shadow)}
.wh-ovbar{display:flex;align-items:baseline;gap:var(--s3);flex-wrap:wrap;
          padding:var(--s3) var(--s4);border-bottom:1px solid var(--rule);background:var(--surface-2)}
.wh-ovt{font:600 14px/1.2 var(--display);letter-spacing:-.01em;color:var(--ink)}
.wh-ovsub{font:400 11.5px/1.4 var(--sans);color:var(--faint)}
.wh-ovx{margin-left:auto;border:1px solid var(--rule);background:var(--surface);
        color:var(--ink-2);cursor:pointer;font:600 10.5px/1 var(--cond);letter-spacing:.09em;
        text-transform:uppercase;padding:7px 11px;border-radius:var(--r2)}
.wh-ovx:hover{background:var(--sunk);color:var(--ink)}
.wh-ovx:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Leaflet takes this element over and stamps its own classes on it, so it must
   be a plain positioned box — the loading line is placed over it rather than
   laid out inside it, or the map panes inherit a flex context they never
   asked for. */
.wh-ovmap{flex:1;min-height:0;position:relative;background:var(--sunk)}
.wh-ovmap > .nw-note{position:absolute;inset:0;display:flex;
                     align-items:center;justify-content:center;text-align:center}
/* Leaflet ships a light-mode-only chrome: white zoom buttons and a white
   attribution strip, which in the dark theme are two bright rectangles on a
   dark map. Repainted from tokens rather than left as the one part of the
   product that does not follow the theme. */
.wh-ovmap .leaflet-bar{border:1px solid var(--rule);box-shadow:none}
.wh-ovmap .leaflet-bar a{background:var(--surface);color:var(--ink-2);border-bottom-color:var(--rule)}
.wh-ovmap .leaflet-bar a:hover{background:var(--sunk);color:var(--ink)}
.wh-ovmap .leaflet-control-attribution{background:var(--surface);color:var(--faint)}
.wh-ovmap .leaflet-control-attribution a{color:var(--muted)}

/* --- panel: talk --------------------------------------------------------
 * Additions to the conversation block above, which the transcript panel
 * still uses for the thread and the bubbles. What is added here is everything
 * the panel needs beyond a bubble: the quality tags a reader judges a
 * translation by, the phase pill, and the controls that speak. There is no
 * identity exchange to mark — the extension is the verification.
 * ---------------------------------------------------------------------- */
.tk{display:flex;flex-direction:column;height:100%;min-height:0}
.tk-empty{font:400 13px/1.5 var(--sans);color:var(--faint);margin:var(--s1) 0;text-align:center}
.tk-empty.pad{padding:var(--s5) var(--s4)}
/* the older console sheet still gives every .msg a bottom margin; the thread
   spaces its turns with flex gap and wants none */
.panels .thread .msg{margin-bottom:0}

/* a claim about quality, never decoration */
.tk-tag{display:inline-block;font:500 9px/1.4 var(--cond);letter-spacing:.06em;text-transform:uppercase;
        padding:1px 6px;border-radius:var(--r1);border:1px solid var(--rule);color:var(--muted);white-space:nowrap}
.tk-tag.high{color:var(--good);border-color:var(--good-soft)}
.tk-tag.partial{color:var(--warn);border-color:var(--warn-soft)}
.tk-tag.low{color:var(--crit);border-color:var(--crit-soft)}

.tk-phase{font:600 9.5px/1 var(--cond);letter-spacing:.09em;text-transform:uppercase;
          padding:4px 7px;border-radius:999px;background:var(--sunk);color:var(--muted)}
.tk-phase.open{background:var(--info-soft);color:var(--info)}
.tk-phase.push{background:var(--warn-soft);color:var(--warn)}
/* while a plan governs, the pill says so rather than naming a phase the plan has replaced */
.tk-phase.plan{background:var(--accent-soft);color:var(--accent-ink)}
.tk-count{font:500 10.5px/1 var(--cond);letter-spacing:.06em;text-transform:uppercase;color:var(--faint)}

/* the call is over: say so where the typing happens, not in a corner */
.tk-dead{padding:var(--s3) var(--s4);border-bottom:1px solid var(--rule);background:var(--surface-2);
         color:var(--muted);font:400 12.5px/1.5 var(--sans)}
/* The recording of an ended call sits where the typing used to happen. The
   player itself is `.recplayer` from app.css; only its placement is ours. */
.tk-dead .recplayer{margin:var(--s2) 0 0}
.tk-norec{margin-top:var(--s2);color:var(--faint);font-size:11.5px}
/* the upload failed, and until now that was an event with no surface at all */
.tk-recbad{margin-top:var(--s2);padding:var(--s2) var(--s3);border-radius:var(--r2);
           background:var(--warn-soft);color:var(--warn);font:500 12px/1.45 var(--sans)}

.tk-btn{font:500 12px/1 var(--sans);padding:7px 11px;border-radius:var(--r2);flex:none;
        border:1px solid var(--rule-strong);background:var(--surface);color:var(--ink);cursor:pointer}
.tk-btn:hover{background:var(--surface-2)}
.tk-btn.quiet{border-color:transparent;background:transparent;color:var(--muted)}
.tk-btn.quiet:hover{background:var(--sunk);color:var(--ink)}
.tk-btn.primary{background:var(--accent);border-color:var(--accent);color:var(--surface)}
.tk-btn.primary:hover{background:var(--accent-ink);border-color:var(--accent-ink)}
/* The browser refused to play the call's audio. Amber — and the label says so
   as well, because meaning is never carried by colour alone. */
.tk-btn.warn{background:var(--warn-soft);border-color:var(--warn);color:var(--ink)}
.tk-btn.danger{color:var(--crit)}
.tk-btn.danger:hover{background:var(--crit-soft)}
/* listening and speaking are states of the room, so they are lit, not labelled */
.tk-btn.on{background:var(--good-soft);border-color:transparent;color:var(--good)}
.tk-btn.hot{background:var(--crit-soft);border-color:transparent;color:var(--crit)}
.tk-btn:disabled{opacity:.45;cursor:not-allowed}
.tk-btn:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.panels .composer input:focus-visible,.panels .composer select:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

/* --- panel: calls -------------------------------------------------------
 * Panel 1 extends the shared line vocabulary above with the three things a
 * glance has to answer: which line this is, what is being spoken on it, and
 * whether anyone is in the room with them.
 *
 * Staffing is deliberately the loudest thing in a row. A call no human has
 * joined gets the crit edge from `.line.alone` *and* says "AI only" in words,
 * because a colour alone is a thing you learn and then stop seeing, and this
 * is the one state the panel exists to make impossible to miss.
 * ---------------------------------------------------------------------- */
.cl{display:flex;flex-direction:column;min-height:100%}
/* the filters follow the reader down a long board */
.cl .filters{position:sticky;top:0;z-index:1;background:var(--surface)}
.cl-hint{display:block;font:400 11.5px/1.5 var(--sans);color:var(--faint);margin-top:4px}

/* the line: the extension keyed, in the mono face every identifier in the
   console wears, taking the width the name used to. A row names a line, never
   a person. */
.cl-ext{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
        font:500 14px/1.2 var(--mono);letter-spacing:.02em;color:var(--ink);
        font-variant-numeric:tabular-nums}
.cl-ext.none{font:400 12.5px/1.2 var(--sans);color:var(--faint);letter-spacing:0}

.cl-time{flex:none;font:500 11.5px/1 var(--mono);color:var(--faint);font-variant-numeric:tabular-nums}

/* what the machine is doing, and where the conversation has got to */
.cl-b{font:600 9.5px/1 var(--cond);letter-spacing:.07em;text-transform:uppercase;padding:4px 7px;
      border-radius:999px;background:var(--sunk);color:var(--muted);white-space:nowrap}
.cl-b.ai{background:var(--teal-soft);color:var(--teal)}
.cl-b.warn{background:var(--warn-soft);color:var(--warn)}
.cl-b.crit{background:var(--crit-soft);color:var(--crit)}
.cl-b.phase{background:transparent;border:1px solid var(--rule-strong);color:var(--muted)}
.cl-b.plan{background:var(--accent-soft);color:var(--accent-ink)}

/* who is in the room */
.cl-staff{margin-left:auto;display:inline-flex;align-items:center;gap:3px;min-width:0}
.cl-face{width:18px;height:18px;border-radius:50%;flex:none;display:inline-flex;align-items:center;
         justify-content:center;font:600 8.5px/1 var(--cond);letter-spacing:.03em;
         background:var(--accent-soft);color:var(--accent-ink)}
.cl-face.more{background:var(--sunk);color:var(--muted)}
.cl-n{font:500 11px/1 var(--sans);color:var(--muted);white-space:nowrap;margin-left:2px}
.cl-staff.none{padding:4px 7px;border-radius:999px;background:var(--crit-soft);color:var(--crit);
               font:600 9.5px/1 var(--cond);letter-spacing:.07em;text-transform:uppercase}
.cl-staff.quiet{font:400 11px/1 var(--sans);color:var(--faint)}

/* Ended calls are a filter, not a basement — the fold and its two lists are
   gone. A finished call reads as past, and a selected one still reads as
   selected wherever the filter puts it. */
.panels .line.done:not(.on){background:var(--surface-2)}
.panels .line.done .cl-ext{color:var(--ink-2)}
/* the recording mark: a fact about the row, not a control — the player lives
   in the conversation panel */
.cl-b.rec{background:var(--accent-soft);color:var(--accent-ink);letter-spacing:0;padding:4px 6px}
/* the language being spoken, not the caller's home country */
.panels .line .lchip{flex:none}
/* the board is reachable from the keyboard: one row in the tab order, arrows
   for the rest */
.panels .line:focus-visible,.cl .filters input:focus-visible,.cl .filters select:focus-visible{
  outline:2px solid var(--accent);outline-offset:-2px}

/* --- manager: demo scaffolding ------------------------------------------
   Deliberately quiet and deliberately labelled: these controls publish a
   source's briefing, which no real deployment would do. */
.demoswitch { display: flex; align-items: center; gap: 6px; font-size: 12px;
              color: var(--muted); white-space: nowrap; cursor: pointer; }
.demoswitch input { accent-color: var(--warn); margin: 0; }
.demolink { display: inline-block; margin-top: 2px; font-size: 11px;
            color: var(--warn); text-decoration: none; }
.demolink:hover { text-decoration: underline; }
