/* brand-tokens:start — GENERATED from packages/core/src/brand/tokens.ts by `npm run brand:generate` (#1412). Do not edit by hand. */
:root {
  --ground: #111417;
  --surface: #191D22;
  --raised: #22272E;
  --border: #30363F;
  --border-strong: #6B7481;
  --text: #EDF0F3;
  --text-2: #B3BAC5;
  --muted: #8A93A0;
  --primary: #3FC3B4;
  --on-primary: #0B1A18;
  --primary-tint: #12302D;
  --brass: #E2B24A;
  --success: #4BD07A;
  --success-tint: #16301F;
  --on-success: #0C1D12;
  --warning: #F59E3B;
  --warning-tint: #33230F;
  --on-warning: #1A1200;
  --danger: #FF6B6B;
  --danger-tint: #3A1C1F;
  --on-danger: #1A0B0C;
  --info: #7AAEFF;
  --practice: #C9B3F5;
  --practice-tint: #241B3A;
  --on-practice: #0B1A18;
  --seal-ground: #FFFFFF;
  --paper: #FFFFFF;
  --on-paper: #222222;
  /* today's names, aliased onto roles (#1412 D2) — removed by #1444 */
  --bg: var(--ground);
  --surface-2: var(--raised);
  --accent: var(--primary);
  --good: var(--success);
  --bad: var(--danger);
  --warn: var(--warning);
  --radius: 12px;
  --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px;
  --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 24px; --space-6: 32px; --space-7: 48px; --space-8: 64px;
  --space-1-size: 4; --space-2-size: 8; --space-3-size: 12; --space-4-size: 16; --space-5-size: 24; --space-6-size: 32; --space-7-size: 48; --space-8-size: 64;
  --font-sans: "IBM Plex Sans", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, Menlo, Consolas, monospace;
  --t1: 500 0.75rem/1rem var(--font-sans); --t1-tracking: 0.06em; /* caps */
  --t2: 400 0.875rem/1.25rem var(--font-sans); --t2-tracking: 0em;
  --t3: 400 1rem/1.5rem var(--font-sans); --t3-tracking: 0em;
  --t4: 600 1.125rem/1.625rem var(--font-sans); --t4-tracking: 0em;
  --t5: 600 1.375rem/1.75rem var(--font-sans); --t5-tracking: 0em;
  --t6: 600 1.75rem/2.125rem var(--font-sans); --t6-tracking: 0em;
  --t7: 600 2.5rem/2.75rem var(--font-sans); --t7-tracking: 0em;
  --t8: 700 5.5rem/5.5rem var(--font-sans); --t8-tracking: 0em;
  --t1-size: 12; --t1-line: 16;
  --t2-size: 14; --t2-line: 20;
  --t3-size: 16; --t3-line: 24;
  --t4-size: 18; --t4-line: 26;
  --t5-size: 22; --t5-line: 28;
  --t6-size: 28; --t6-line: 34;
  --t7-size: 40; --t7-line: 44;
  --t8-size: 88; --t8-line: 88;
  --desk-body: 1.125rem; --desk-target: 3.5rem; --desk-input: 3.25rem;
  --compact-table: 0.875rem; --compact-row: 2.5rem;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --ground: #F5F6F7;
    --surface: #FFFFFF;
    --raised: #ECEFF2;
    --border: #D3D8DE;
    --border-strong: #7E8792;
    --text: #15191E;
    --text-2: #495260;
    --muted: #626B78;
    --primary: #0B6E66;
    --on-primary: #FFFFFF;
    --primary-tint: #E3F1EF;
    --brass: #8A5A0B;
    --success: #1A7B39;
    --success-tint: #E6F3EA;
    --on-success: #FFFFFF;
    --warning: #AF520A;
    --warning-tint: #FBEEE2;
    --on-warning: #FFFFFF;
    --danger: #B42323;
    --danger-tint: #FBE9E9;
    --on-danger: #FFFFFF;
    --info: #2A5FA8;
    --practice: #6D3FC4;
    --practice-tint: #EFE8FA;
    --on-practice: #FFFFFF;
    --seal-ground: #FFFFFF;
    --paper: #FFFFFF;
    --on-paper: #222222;
  }
}
:root[data-theme="light"] {
  --ground: #F5F6F7;
  --surface: #FFFFFF;
  --raised: #ECEFF2;
  --border: #D3D8DE;
  --border-strong: #7E8792;
  --text: #15191E;
  --text-2: #495260;
  --muted: #626B78;
  --primary: #0B6E66;
  --on-primary: #FFFFFF;
  --primary-tint: #E3F1EF;
  --brass: #8A5A0B;
  --success: #1A7B39;
  --success-tint: #E6F3EA;
  --on-success: #FFFFFF;
  --warning: #AF520A;
  --warning-tint: #FBEEE2;
  --on-warning: #FFFFFF;
  --danger: #B42323;
  --danger-tint: #FBE9E9;
  --on-danger: #FFFFFF;
  --info: #2A5FA8;
  --practice: #6D3FC4;
  --practice-tint: #EFE8FA;
  --on-practice: #FFFFFF;
  --seal-ground: #FFFFFF;
  --paper: #FFFFFF;
  --on-paper: #222222;
}
/* brand-tokens:end */
/* brand-fonts:start — GENERATED by `npm run gen-web-fonts` (#1414). Do not edit by hand. */
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(/fonts/ibm-plex-sans-latin-400-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(/fonts/ibm-plex-sans-latin-ext-400-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(/fonts/ibm-plex-sans-latin-500-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(/fonts/ibm-plex-sans-latin-ext-500-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(/fonts/ibm-plex-sans-latin-600-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 600;
  src: url(/fonts/ibm-plex-sans-latin-ext-600-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url(/fonts/ibm-plex-sans-latin-700-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 700;
  src: url(/fonts/ibm-plex-sans-latin-ext-700-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: italic;
  font-display: swap;
  font-weight: 400;
  src: url(/fonts/ibm-plex-sans-latin-400-italic.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: italic;
  font-display: swap;
  font-weight: 400;
  src: url(/fonts/ibm-plex-sans-latin-ext-400-italic.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(/fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url(/fonts/ibm-plex-mono-latin-ext-400-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(/fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url(/fonts/ibm-plex-mono-latin-ext-500-normal.woff2) format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
/* brand-fonts:end */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; height: 100%; }
/* The `hidden` attribute must always win — a class like `.pill`/`.chair-ctrl`
   that sets `display` otherwise overrides the UA `[hidden]` rule and leaks the
   element (e.g. a role badge or a control shown when it should be hidden). */
[hidden] { display: none !important; }
body { font-family: var(--font-sans); font-variant-numeric: tabular-nums;   /* #1414 D6 — global, no per-site sweep */
  background: var(--bg); color: var(--text); font-size: 1rem; line-height: 1.45;
  padding-top: var(--rv-banner-h, 0); }   /* reserve space for the fixed support banner (0 when none) */
h1 { font-size: 1.375rem; margin: 4px 0 12px; } h2 { font-size: 1rem; margin: 22px 0 8px; color: var(--muted); }
a { color: var(--accent); }
.hint { color: var(--muted); font: var(--t2); }   /* #1426 F2 — 13px snapped to the type scale (s2, 14/20) */
/* #2119 review (B1) — `font` is a SHORTHAND: `.hint`'s own `font: var(--t2)` above resets
   font-variant-numeric to its initial value (`normal`), silently undoing the global tabular-nums
   `body` sets (#1414 D6). Every other numeric line on this repo that carries a `font: var(--tN)`
   shorthand re-declares the property right after it for exactly this reason (.integrity-root-missing/
   .integrity-coverage, .obs-stats, .obs-card-count, .obs-ledger-table, .obs-shown, .meet-table) — this
   is that same fix for the delegation Inbox's allotment reconciliation line (ReconLine,
   web/src/surfaces/delegations/App.tsx), which used to be `.pill` elements (also a shorthand-free
   reset) and is now plain text riding `.hint`. Rule of thumb worth keeping: a `font:` shorthand
   resets every longhand it does not name — treat it as resetting font-variant-numeric on sight. */
.recon-line { font-variant-numeric: tabular-nums; }
.muted { color: var(--muted); }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 8px 0; }
/* #784 — the clerk meeting header must wrap gracefully. #d-chain lays its buttons out as a wrapping
   flex row (not inline squeezed into a narrow column). On a phone the header stacks: the meeting name
   on top, then the actions as a full-width wrapping row — instead of one button per line down the side. */
.d-head { flex-wrap: wrap; }
.d-head #d-chain { display: flex; flex-wrap: wrap; align-items: center; min-width: 0; }
@media (max-width: 680px) {
  .d-head { flex-direction: column; align-items: stretch; }
  .d-head #d-chain { width: 100%; }
}
.mono { font-family: var(--font-mono); font-size: 0.75rem; }
.big-code { font-size: 1.375rem; font-weight: 800; letter-spacing: 1px; }
label { display: block; font: var(--t2); color: var(--muted); margin: 14px 0 6px; }   /* #1426 F2 */
input, select, textarea { width: 100%; padding: 12px 14px; font-size: 1rem; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; font-family: inherit; font-variant-numeric: inherit; }
/* Checkboxes & radios are not full-width fields — keep them intrinsic size so the
   label text sits beside them instead of overlapping. */
input[type="checkbox"], input[type="radio"] { width: auto; padding: 0; vertical-align: middle; accent-color: var(--accent, #6b8afd); }
/* WYSIWYG message editor (Communications composer) — looks like a field, edits like a doc. */
.rt-editor { min-height: 150px; padding: 12px 14px; font-size: 1rem; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; line-height: 1.5; overflow-wrap: anywhere; }
.rt-editor:focus { outline: none; border-color: var(--accent); }
.rt-editor:empty::before { content: attr(data-ph); color: var(--muted); }
.rt-editor h2 { font-size: 1.0625rem; color: var(--text); margin: 8px 0 6px; }
.rt-editor h3 { font-size: 0.9375rem; color: var(--text); margin: 8px 0 6px; }
.rt-editor p { margin: 0 0 8px; } .rt-editor ul, .rt-editor ol { margin: 0 0 8px; padding-left: 22px; }
.rt-editor a { color: var(--accent); }
button { font: var(--t3); font-weight: 600; border: none; border-radius: 12px; padding: 12px 16px;
  cursor: pointer; margin-top: 12px; background: var(--surface-2); color: var(--text);
  font-family: inherit; font-variant-numeric: inherit; }   /* #1414 — the UA `font` shorthand resets both on buttons; #1426 F2 — 15px snapped to s3 (16px) */
/* #1412: the primary label is the --on-primary token, which the generated brand-tokens region
   sets per theme (dark ink on the dark theme's light teal, white on the light theme's deep teal);
   test/contrast.test.ts asserts it clears AA in both. Replaces #1428's per-theme #fff overrides.
   a.primary (#1455) shares the same fill and label, so the two can't drift apart. */
button.primary, a.primary { background: var(--accent); color: var(--on-primary); }
/* #1455 — Gate's CTA (and a couple of others, e.g. Settings' data export) is correctly an
   <a> — it navigates, it doesn't submit — but only button.primary carried the primary-button
   look, so it rendered as a plain underlined link. Give a.primary the same box model as the
   bare `button` rule above (it can't inherit that selector: `button` and `a` share no rule),
   keeping the focus ring on default (no `outline` here) so keyboard focus stays visible. */
/* `min-height: 44px` is an explicit WCAG touch-target floor, not a copy of something `button`
   already guarantees: `<button>` gets a UA-stylesheet `line-height: normal` reset (measured
   ~41px tall with this padding — under 44px on its own), while a plain `<a>` has no such UA
   reset and instead inherits `body`'s `line-height: 1.45` (measured ~46px — already over 44px
   here, but that's an inherited value, not a guarantee — a narrower ancestor line-height would
   silently shrink it). Pin it explicitly so the target size doesn't ride on inheritance. */
a.primary { display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font: var(--t3); font-weight: 600; border-radius: 12px; padding: 12px 16px; margin-top: 12px;   /* #1426 F2 */
  min-height: 44px; text-decoration: none; cursor: pointer; }
button.ghost { background: transparent; border: 1px solid var(--border); color: var(--muted); padding: 8px 12px; font: var(--t2); font-weight: 600; font-family: inherit; font-variant-numeric: inherit; margin: 0; }   /* #1426 F2 — same UA-reset defense as the base button rule (#1414) */
button.danger { background: var(--danger-tint); color: var(--bad); }
button.sm { padding: 7px 12px; font: var(--t2); font-weight: 600; font-family: inherit; font-variant-numeric: inherit; margin: 0; }   /* #1426 F2 */
button:disabled { opacity: .45; cursor: not-allowed; }
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; margin-top: 14px; }
.panel:empty { display: none; }
/* Help link — present on every surface except the big screen */
.help-link { display: inline-flex; align-items: center; gap: 5px; font: var(--t2); font-weight: 600; text-decoration: none;   /* #1426 F2 */
  color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px; white-space: nowrap; }
.help-link:hover { color: var(--text); border-color: var(--accent); }
.topbar .brand-row { display: flex; align-items: center; gap: 12px; }
/* RosterVote brand. Single-colour per #1413/#1505: weight, not colour, splits "Roster"/"Vote"
   (matching the brand.html lockup rule: 500 / 700, one colour). .rv-brand (the pre-#1413 header/
   footer treatment) has no consumer left — src/marketing/chrome.ts uses the shared SVG sprite now. */
.rv-hero { text-align: center; margin-bottom: 14px; }
.rv-hero img { width: 54px; height: 54px; }
.rv-hero .wm { font-size: 1.625rem; font-weight: 500; margin-top: 6px; letter-spacing: -.01em; }
.rv-hero .wm b { font-weight: 700; }
/* Collapsible setup / add-vote / ledger drawers, and (#1737/#1774) the admin console's own
   collapsible ticket rows — same disclosure-triangle treatment on the .admin-row row style. */
details.panel > summary, details.admin-row > summary { cursor: pointer; list-style: none; user-select: none; }
details.panel > summary::-webkit-details-marker, details.admin-row > summary::-webkit-details-marker { display: none; }
details.panel > summary::before, details.admin-row > summary::before { content: '▸ '; color: var(--muted); }
details.panel[open] > summary::before, details.admin-row[open] > summary::before { content: '▾ '; }
/* M6 (#1873 review): at 390px the ▸/▾ marker could wrap onto its own line, ahead of the ticket row's
   flex-wrap content span — flex on the summary keeps the marker and the row's first line together;
   the span still wraps its own pills/text internally. Scoped to .admin-row (only Tickets.tsx uses a
   <details> here); .panel summaries are plain inline text and don't hit this. */
details.admin-row > summary { display: flex; flex-wrap: nowrap; align-items: flex-start; }
/* NOW — the live control center */
.now-panel { border-color: var(--accent); }
.now-nav { display: flex; gap: 6px; align-items: center; }
.now-cur { margin-top: 8px; }
.now-open { margin-top: 6px; }
.addvote-panel { border-style: dashed; }
.pill { display: inline-block; font: var(--t2); padding: 3px 8px; border-radius: 999px; background: var(--surface-2); color: var(--muted); white-space: nowrap; }   /* #1426 F2 — not a caps label (no text-transform), so 11px -> s2 (14), not s1 */
/* The identity pills (surface name + org) never shrink or wrap their text; the meeting picker shrinks
   (capped) instead — without a width cap a bare <select> in the brand-row ate space and squished the
   pills onto extra lines. Everything stays on one row. */
.topbar .brand-row > .brand, .topbar .brand-row > #org, .topbar .brand-row > .rv-home { flex-shrink: 0; }
.topbar .brand-row select { max-width: 240px; min-width: 120px; }
.pill.public { background: var(--primary-tint); color: var(--accent); }
.pill.secret { background: var(--warning-tint); color: var(--warn); }
.pill.election { background: var(--success-tint); color: var(--good); }
.pill.open { background: var(--success-tint); color: var(--good); }
.pill.archived, .pill.closed { color: var(--muted); }
/* #1416 D6 — the medal badge pairs the icon with the word; .pill.alternate keeps the base .pill look
   (surface-2/muted), so only .pill.elected gets its own tint. Tokens only (#1412 raw-colour ratchet). */
.pill.elected { display: inline-flex; align-items: center; gap: 4px; background: var(--primary-tint); color: var(--primary); }
.pill.alternate { display: inline-flex; align-items: center; gap: 4px; }
/* #1416 — any pill that holds an icon centres it the way .pill.elected does (a 16px icon in an 11px pill). */
.pill:has(> .rv-i) { display: inline-flex; align-items: center; gap: 4px; }
/* #1586 — baseline anchor for icon pills and chips. With align-items: center no flex item is baseline-aligned, so an
   inline-flex box takes its baseline from its FIRST item; when that is the svg (baseline = its bottom edge) the pill
   rides ~5px above the text beside it. This zero-width-space ::before becomes the first item instead: same font and
   line-height as the label and centred the same way, so the box's baseline is the label's text baseline at any line
   height or icon size. `/ ''` (alt text) keeps it out of the accessible name; the plain declaration before it is the
   fallback for engines without alt text. -4px cancels the 4px gap it would add (width unchanged). */
.pill.elected::before, .pill.alternate::before, .pill:has(> .rv-i)::before,
.chip.practice:has(> .rv-i)::before, .chip.support:has(> .rv-i)::before { content: '\200B'; content: '\200B' / ''; margin-inline-end: -4px; }
/* #1386 B1 — in-meeting surface switcher: group spacing + emphasize the current surface */
/* #1426 W1 — at narrow widths (or large text, once F1 makes text respond to it) the surface-switch
   pills (Check-in/Clerk/Chair/…) plus the meeting select no longer fit on one line; wrap instead of
   pushing a pill off-screen sideways (WCAG 1.4.10 reflow). min-width: 0 is required alongside
   flex-wrap here: as a flex ITEM of .topbar's last-child row, this element's default min-width:auto
   pins it to its unwrapped content width, so flex-wrap alone never gets a chance to engage. */
.rv-surface-switch { display: inline-flex; flex-wrap: wrap; gap: 6px; align-items: center; min-width: 0; max-width: 100%; }
.rv-surface-switch .pill.active { background: var(--accent); color: var(--on-primary); font-weight: 600; }
.result-good { color: var(--good); font-weight: 600; } .result-bad { color: var(--bad); font-weight: 600; }
.tally-row { display: flex; align-items: center; gap: 10px; margin: 8px 0; }
.tally-row .name { width: 120px; font-size: 0.875rem; }
.bar { flex: 1; height: 10px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.bar > span { display: block; height: 100%; background: var(--accent); }
.tally-row .val { width: 40px; text-align: right; font-variant-numeric: tabular-nums; }
.badge { font-size: 0.75rem; padding: 4px 10px; border-radius: 999px; background: var(--surface-2); color: var(--muted); }
.badge.good { background: var(--success-tint); color: var(--good); }
.badge.bad { background: var(--danger-tint); color: var(--bad); }

/* ---------- MOBILE voter shell ---------- */
#app { display: flex; flex-direction: column; min-height: 100vh; max-width: 520px; margin: 0 auto; }
.topbar { display: flex; align-items: center; justify-content: space-between; padding: 8px 14px;
  background: var(--surface); position: sticky; top: var(--rv-banner-h, 0); z-index: 10; border-bottom: 1px solid var(--border);
  padding-top: max(8px, env(safe-area-inset-top)); }
.brand { font-weight: 700; white-space: nowrap; }
/* Tenant branding logo (white-labeling, 4.5) — sits in the top bar, on the
   projector header, the ballot sticker, and the voter/login surfaces. */
.rv-brand-logo { height: 24px; width: auto; max-width: 130px; object-fit: contain; vertical-align: middle; margin-right: 8px; border-radius: 4px; }
.scr-brand-logo { height: clamp(28px, 5vh, 56px); width: auto; max-width: 28vw; object-fit: contain; margin-right: 16px; vertical-align: middle; }
.sticker .s-logo { max-height: 40px; max-width: 70%; width: auto; object-fit: contain; margin: 0 auto 8px; display: block; }
.brand-hero-logo { max-height: 64px; max-width: 70%; width: auto; object-fit: contain; margin: 0 auto 12px; display: block; }
.brand-hero-logo.primary { max-height: 96px; margin: 8px auto 16px; }   /* the tenant's own logo, when it's the hero */
.powered-by { text-align: center; margin-top: 18px; font-size: 0.75rem; color: var(--muted); }
.powered-by a { color: var(--muted); font-weight: 600; text-decoration: none; }
.powered-by a:hover { color: var(--text); text-decoration: underline; }
/* Injected "Powered by RosterVote" footer (ui.js) — every tenant-facing surface. */
.rv-powered { text-align: center; padding: 20px 12px 24px; font-size: 0.75rem; color: var(--muted); }
.rv-powered a { color: var(--muted); font-weight: 600; text-decoration: none; }
.rv-powered a:hover { color: var(--text); text-decoration: underline; }
#screens { flex: 1; padding: 16px 16px 90px; }
.screen[hidden] { display: none; }
.choice { display: block; width: 100%; text-align: left; background: var(--surface-2); border: 1px solid var(--border); }
.choice.sel { border-color: var(--accent); background: var(--primary-tint); }

/* ---------- DESKTOP (check-in, clerk) ---------- */
.desktop { max-width: 1120px; margin: 0 auto; padding: 24px; }
.desktop .topbar { border-radius: var(--radius); border: 1px solid var(--border); flex-wrap: wrap; gap: 8px 16px; }
/* #784 — on a phone the crowded clerk/meetings topbar must WRAP the brand row instead of overflowing
   the separate org pill off the right edge. (The org home fits already: its brand IS the org name, so
   there's no separate pill, no back button, no workspace label.) */
@media (max-width: 680px) {
  .topbar .brand-row { flex-wrap: wrap; row-gap: 6px; }
}
/* Right-hand action group (meeting picker, Home, help, mode pills): a single
   centered row that wraps as a unit — never a tall stack that inflates the header. */
.topbar > :last-child:not(.brand-row) { display: flex; align-items: center; gap: 10px; }
/* A meeting picker in the topbar must size to its content — not the global
   input width:100%, which would hog the line and shove Home onto a 2nd row. */
.topbar > :last-child:not(.brand-row) select { width: auto; max-width: 220px; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 760px) { .grid2 { grid-template-columns: 1fr; } }
/* #531 — the check-in label editor's live preview pins beside the (often long) element list
   while it scrolls. Lives here rather than as an inline style so the narrow-viewport override
   below can win without `!important` (an inline style always beats a stylesheet rule otherwise).
   Below ~760px the two-column rowflex (see CredentialLabel.tsx) wraps to one column, so the
   preview has no tall sibling left to pin against — sticky there just leaves it half-stuck
   partway down the page, so it drops back to static and flows like a normal panel instead. */
.cl-preview { position: sticky; top: calc(var(--rv-banner-h, 0px) + var(--rv-topbar-h, 0px) + 12px); }
@media (max-width: 760px) { .cl-preview { position: static; top: auto; } }
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px,1fr)); gap: 12px; }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; text-align: center; }
.card .num { font-size: 1.625rem; font-weight: 800; } .card .lbl { font-size: 0.75rem; color: var(--muted); margin-top: 4px; }
/* #1421/#1752 — the clerk console's one-line stat row (was a `.cards` tile grid). `body` already
   sets `font-variant-numeric: tabular-nums` globally (#1414 D6); this rule is explicit anyway so the
   sentence stays steady under scrutiny even if that global rule ever narrows its scope. */
.stat-line { font-variant-numeric: tabular-nums; margin-top: 12px; font-size: 0.9375rem; color: var(--muted); }
table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); }
th { color: var(--muted); font-weight: 600; font-size: 0.75rem; text-transform: uppercase; letter-spacing: .04em; }
.list-item { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; margin-top: 10px; cursor: pointer; }
.list-item:hover { border-color: var(--accent); }
.ledger-item { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 8px 12px; margin-top: 6px; font-size: 0.8125rem; }
.ledger-item .hash { color: var(--muted); word-break: break-all; }
.rowflex { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.rowflex > * { margin-top: 0; }
input.pf { padding: 4px 6px; font-size: 0.8125rem; display: inline-block; width: auto; }
/* county console: tabs, breadcrumb drill, slide-in detail */
.ctab-bar { display: flex; gap: 6px; margin: 16px 0 0; flex-wrap: wrap; }
.ctab-bar button { background: var(--surface-2); color: var(--muted); border: 1px solid var(--border); border-bottom: none; border-radius: 10px 10px 0 0; margin: 0; padding: 10px 18px; font-size: 0.875rem; }
.ctab-bar button.active { background: var(--surface); color: var(--text); }

/* #2120 (#1444 step 2) — underline tabs, replacing the .ctab-bar "folder tab" pattern (raised boxes
   with a corner cut into the panel below, styles.css ~449-451 above) on the surfaces this sweep
   converts. No box, no border, no panel-corner hack to cancel: a 2px bottom border is the only
   affordance, in `--primary` when active and transparent otherwise, so the strip never grows or
   shrinks between states. Brand tokens only (#1412) — no raw colour/radius. */
.tabs-underline { display: flex; gap: var(--space-4); margin: var(--space-4) 0 0; flex-wrap: wrap; border-bottom: 1px solid var(--border); }
.tabs-underline button { background: transparent; border: none; border-bottom: 2px solid transparent; border-radius: 0; margin: 0 0 -1px; padding: var(--space-2) 2px var(--space-3); font: var(--t3); color: var(--muted); }
.tabs-underline button:hover { color: var(--text); }
.tabs-underline button.active { color: var(--text); border-bottom-color: var(--primary); font-weight: 600; }
.tabs-underline button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.crumb { color: var(--muted); font-size: 0.875rem; margin-bottom: 14px; }
.crumb a { cursor: pointer; color: var(--accent); }
/* #1313 — the check-in desk's meeting line is the first thing under the sticky topbar; give it top
   breathing room so it doesn't butt against the header border. Styled here (not inline) so it's a
   single source of truth. */
#ci-meeting { font-size: 1.05em; margin: 10px 0 12px; }
.crumb b { color: var(--text); }

/* #1419 — the check-in desk redesign: one 52pt scan-or-type field, desk density (body 18, target 56,
   input 52 — density.desk in packages/core/src/brand/tokens.ts), a split view from 900 wide (the roll
   left, the confirm/issue pane right) and an in-flow sheet below that (no overlay, fork 1). Role
   tokens only — no raw colour (test:raw-colours). */
.ci-desk { font-size: var(--desk-body); }
.ci-split { display: block; }
@media (min-width: 900px) {
  .ci-split { display: grid; grid-template-columns: 420px minmax(0, 1fr); gap: var(--space-5); align-items: start; }
}
.ci-col { display: flex; flex-direction: column; gap: var(--space-3); }
.ci-desk-field { display: flex; flex-direction: column; gap: var(--space-2); }
.ci-field { display: flex; align-items: center; gap: var(--space-2); min-height: var(--desk-input);
  border: 1.5px solid var(--border-strong); border-radius: var(--radius-lg); background: var(--surface); padding: 0 var(--space-2); }
.ci-field:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-tint); }
.ci-field input#search { flex: 1; border: 0; background: transparent; font: inherit; min-height: var(--desk-input); outline: none; color: var(--text); }
.ci-field-find { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border: 0; background: transparent; color: var(--text-2); border-radius: var(--radius-md); cursor: pointer; }
.ci-field-find:hover { background: var(--raised); }
.ci-field-cam { display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 44px; border: 0; border-radius: var(--radius-md);
  background: var(--primary); color: var(--on-primary); cursor: pointer; flex: none; }
.ci-secondary { align-self: flex-start; min-height: 44px; padding: 0 var(--space-3); border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  background: transparent; color: var(--text); display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer; }

.desk-row { display: grid; grid-template-columns: 36px 1fr auto; align-items: center; gap: var(--space-3);
  width: 100%; min-height: var(--desk-target); padding: var(--space-2) var(--space-2); border: 0; border-bottom: 1px solid var(--border);
  background: transparent; text-align: left; cursor: pointer; font: inherit; color: var(--text); }
.desk-row[aria-current="true"] { background: var(--primary-tint); box-shadow: inset 3px 0 0 var(--primary); }
.desk-av { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; background: var(--raised); color: var(--text-2); }
.desk-av.guest { background: var(--warning-tint); color: var(--warning); }
.desk-row-name { font: var(--t4); grid-column: 2; }
.desk-row-class { color: var(--text-2); font: var(--t2); grid-column: 2; }
.desk-row-kind { grid-column: 3; }
.desk-row-status { grid-column: 3; color: var(--text-2); font: var(--t2); white-space: nowrap; }
.desk-row-status.in { color: var(--success); }

.ci-pane { background: var(--surface); border-left: 1px solid var(--border); padding: var(--space-5); border-radius: var(--radius-lg); }
.ci-pane #ci-pane-name { font: var(--t6); margin: var(--space-2) 0; }
.ci-pane .eyebrow { text-transform: uppercase; letter-spacing: .06em; font: var(--t1); color: var(--text-2); }
.ci-pane .ci-class { color: var(--text-2); margin: 0 0 var(--space-3); }
.ci-pane button.primary { min-height: var(--desk-target); font: var(--t4); width: 100%; margin-top: var(--space-3); }
.ci-pane .ci-ok { background: var(--success-tint); color: var(--success); border-radius: var(--radius-md); padding: var(--space-2) var(--space-3); display: inline-flex; align-items: center; gap: var(--space-2); }
.ci-issued-name { font: var(--t5); margin: var(--space-3) 0 var(--space-1); }
.ci-issued-actions { display: grid; gap: var(--space-2); margin-top: var(--space-4); }
.ci-practice { font-weight: 800; color: var(--practice); letter-spacing: .08em; margin-bottom: var(--space-2); }
.ci-prefs { margin: var(--space-3) 0; }
.ci-pref { display: inline-flex; gap: 4px; align-items: center; margin-right: var(--space-3); }
.ci-confirm-card { border-color: var(--primary); margin: var(--space-2) 0; }
.ci-back { margin-bottom: var(--space-2); }
.ci-rest { display: flex; flex-direction: column; gap: var(--space-3); }

.ci-stack .ci-sheet { background: var(--surface); border-top: 1px solid var(--border); border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: var(--space-4); margin-top: var(--space-3); }

.gitem { cursor: pointer; }
.gitem:hover { border-color: var(--accent); }
/* #1737/#1774 — the platform-admin console's one row style (was .list-item/.ledger-item/.gitem at three
   different radii). Scoped to admin/*.tsx only: .list-item/.ledger-item stay unchanged for every other
   surface that already renders through them (records, clerk, county, etc.) — see the plan's box-flattening
   task for why a new class, not a shared-radius edit, is the safe way to do this. */
.admin-row { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 8px 12px; margin-top: 8px; }
.admin-row:first-child { margin-top: 0; }
/* M3 (#1873 review): hover implies "does something on click" — scope it to rows that actually do (the
   .gitem org list, the user list, a ticket's own disclosure summary), not the static Support roster,
   pending invites, broadcast history or ticket thread messages that merely reuse .admin-row for the box. */
.admin-row.is-clickable:hover { border-color: var(--accent); }
/* A small icon+text secondary-state marker beside a row's one pill — never a second pill. */
.row-indicator { display: inline-flex; align-items: center; gap: 4px; font-size: 0.75rem; color: var(--text-2); white-space: nowrap; }
.row-indicator svg { width: 14px; height: 14px; flex: 0 0 auto; }
.side-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 49; }
.side-panel { position: fixed; top: 0; right: 0; height: 100vh; width: min(460px, 94vw); background: var(--surface);
  border-left: 1px solid var(--border); box-shadow: -10px 0 40px rgba(0,0,0,.45); padding: 22px; overflow-y: auto; z-index: 50; }
.side-panel .close { float: right; }
.kv { display: grid; grid-template-columns: 110px 1fr; gap: 6px 12px; font-size: 0.875rem; margin: 12px 0; }
.kv .k { color: var(--muted); }
.gate { max-width: 380px; margin: 12vh auto; }

/* ---------- Sticker print ----------
   The Phomemo 76mm ballot-label @media print rules USED to live here, but a global
   @page can't be scoped to one surface, so they resized every page's print output
   (the onboarding kit had to override all three). They now live in checkin-app.html
   — the only surface that prints. Don't re-add print @page rules to this shared
   sheet. (#44) */

/* ---------- Big screen / projector ---------- */
.screenbg { height: 100vh; overflow: hidden; display: flex; flex-direction: column; }
.screen-head { display: flex; justify-content: space-between; align-items: center; padding: 24px 40px; border-bottom: 1px solid var(--border); }
.screen-head .conv { font-size: 1.5rem; font-weight: 700; }
.screen-main { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 40px; }
.screen-main .kicker { font-size: 1.375rem; color: var(--muted); text-transform: uppercase; letter-spacing: .1em; }
.screen-main .title { font-size: 4rem; font-weight: 800; margin: 18px 0; line-height: 1.1; max-width: 20ch; }
.screen-main .status { font-size: 2.5rem; color: var(--accent); font-weight: 700; }
.screen-tally { width: min(1100px, 90vw); margin-top: 20px; }
.screen-tally .tally-row { margin: 16px 0; } .screen-tally .name { width: 320px; font-size: 1.875rem; }
.screen-tally .bar { height: 26px; } .screen-tally .val { width: 90px; font-size: 1.875rem; }
.winner-chip { display: inline-block; background: var(--success-tint); color: var(--good); border: 1px solid var(--good);
  border-radius: 12px; padding: 14px 22px; margin: 8px; font-size: 1.875rem; font-weight: 700; }
.screen-idle { font-size: 2.75rem; color: var(--muted); }
.screen-noms { margin-top: 26px; display: flex; flex-direction: column; gap: 14px; align-items: center; }
.screen-noms .nom { font-size: 2.5rem; font-weight: 700; display: flex; align-items: baseline; gap: 16px; }
.screen-noms .nom-sec { font-size: 1.375rem; color: var(--muted); font-weight: 400; }
/* Big-screen agenda / run-of-show */
.screen-agenda { width: min(1100px, 92vw); margin-top: 24px; display: flex; flex-direction: column; gap: 8px; text-align: left; }
.screen-agenda .ag-row { display: flex; align-items: center; gap: 18px; font-size: 2rem; padding: 10px 20px; border-radius: 12px; color: var(--muted); }
.screen-agenda .ag-row .ag-mark { width: 34px; text-align: center; opacity: .8; }
.screen-agenda .ag-row .ag-title { flex: 1; font-weight: 600; }
.screen-agenda .ag-passed { opacity: .45; }
.screen-agenda .ag-passed .ag-title { text-decoration: line-through; }
.screen-agenda .ag-cur { background: var(--success-tint); color: var(--text); border: 2px solid var(--good); font-size: 2.375rem; }
.screen-agenda .ag-tag { font-size: 1.25rem; font-weight: 700; letter-spacing: .06em; padding: 4px 12px; border-radius: 999px; text-transform: uppercase; }
.screen-agenda .ag-tag.live { color: var(--good); border: 1px solid var(--good); }
.screen-agenda .ag-tag.staged { color: var(--muted); border: 1px solid var(--muted); }
.screen-agenda .ag-tag.done { color: var(--muted); }
/* Live credentials board — always one page, no scroll */
.cred-cards { display: flex; gap: clamp(12px, 2vw, 28px); margin-top: clamp(12px, 3vh, 32px); flex-wrap: wrap; justify-content: center; }
.cred-card { background: rgba(255,255,255,.05); border-radius: 16px; padding: clamp(12px, 2.2vh, 24px) clamp(20px, 3vw, 40px); min-width: 190px; }
.cred-num { font-size: clamp(40px, 7vw, 84px); font-weight: 800; line-height: 1; }
.cred-lbl { font-size: clamp(15px, 1.5vw, 22px); color: var(--muted); margin-top: 6px; text-transform: uppercase; letter-spacing: .06em; }
.cred-summary { font-size: clamp(18px, 2vw, 30px); color: var(--warn); font-weight: 700; margin-top: clamp(14px, 3vh, 30px); }
.cred-muni { width: min(1200px, 94vw); margin-top: clamp(10px, 2vh, 20px); display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.cred-muni-item { font-size: clamp(13px, 1.3vw, 20px); font-weight: 600; background: rgba(255,255,255,.05); border: 1px solid var(--border); border-radius: 999px; padding: 5px 14px; }
.cred-muni-item b { color: var(--warn); }

/* ---- Shared UI layer (ui.js): toasts, confirm modal, spinner ---- */
#rv-toasts { position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 8px; z-index: 1000; width: min(440px, calc(100vw - 24px)); pointer-events: none; }
/* #1416 — Lucide icons (web <Icon> and, later, the vanilla sprite) sit on the text's optical centre, not
   its baseline, and never shrink inside flex rows. Size comes from each icon's width/height attributes. */
.rv-i { display: inline-block; vertical-align: -0.2em; flex: none; }
/* #1587 — the three sizes RV.icon() emits (public/ui.js). Tokens only: the stroke is currentColor from the sprite. */
.rv-i-16 { width: 16px; height: 16px; }
.rv-i-20 { width: 20px; height: 20px; }
.rv-i-24 { width: 24px; height: 24px; }
.rv-toast { pointer-events: auto; display: flex; align-items: center; gap: 10px;
  background: var(--surface-2); border: 1px solid var(--border); border-left-width: 4px;
  border-radius: 12px; padding: 11px 12px; box-shadow: 0 12px 30px rgba(0,0,0,.4);
  font-size: 0.875rem; animation: rv-toast-in .18s ease-out; }
.rv-toast.good { border-left-color: var(--good); }
.rv-toast.bad { border-left-color: var(--bad); }
.rv-toast.info { border-left-color: var(--accent); }
.rv-toast-msg { flex: 1; }
.rv-toast-action { margin: 0; padding: 5px 12px; font-size: 0.8125rem; font-weight: 700; background: var(--accent); color: var(--on-primary); border-radius: 8px; }
.rv-toast-x { margin: 0; padding: 2px 6px; font-size: 0.8125rem; background: transparent; color: var(--muted); border-radius: 8px; }
@keyframes rv-toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

.rv-modal-back { position: fixed; inset: 0; background: rgba(6,8,16,.62); backdrop-filter: blur(2px);
  display: flex; align-items: center; justify-content: center; z-index: 1001; padding: 20px; }
.rv-modal { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 20px; width: min(420px, 100%); box-shadow: 0 24px 60px rgba(0,0,0,.5); animation: rv-toast-in .16s ease-out; }
/* Radix (#399 W2) portals the Overlay + Content as SIBLINGS, so the box can't rely on the
   .rv-modal-back flex parent to centre it — this pins the React-kit dialogs to the viewport
   centre. Hand-rolled modals (still nested inside .rv-modal-back) are unaffected. */
.rv-modal.rv-modal-center { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 1002; max-width: calc(100vw - 40px); max-height: calc(100vh - 40px); overflow-y: auto; }
/* Wider variant for forms / detail (e.g. the org create + detail modals) — taller
   content scrolls inside the box so the page never scrolls behind the backdrop. */
.rv-modal.rv-modal-wide { width: min(560px, 100%); max-height: calc(100vh - 40px); overflow-y: auto; }
.rv-modal-msg { font-size: 1rem; font-weight: 600; }
.rv-modal-detail { color: var(--muted); font-size: 0.875rem; margin-top: 8px; }
.rv-modal-row { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
.rv-modal-row button { margin: 0; }
.rv-modal-input { width: 100%; margin-top: 12px; padding: 10px 12px; font: inherit; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; box-sizing: border-box; resize: vertical; }
.rv-modal-input:focus { outline: none; border-color: var(--accent); }

/* Launcher-style section cards (shared by the tenant launcher + admin console). */
.launch-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; margin-top: 14px; }
.launch-card { display: block; text-align: left; text-decoration: none; color: inherit; border: 1px solid var(--border); border-radius: 14px;
  padding: 16px; background: var(--surface); cursor: pointer; transition: border-color .12s, transform .12s; }
.launch-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.launch-card .ic { font-size: 1.5rem; }
.launch-card .t { font-weight: 700; margin-top: 6px; }
.launch-card .d { color: var(--muted); font: var(--t2); margin-top: 3px; line-height: 1.4; }   /* #1426 F2 — the trailing line-height keeps this card's own ratio, not s2's paired one */

/* First-run "Getting started" checklist on the launcher (shown to an admin while
   the org is still onboarding). */
.setup-card { margin: 14px 0 0; border-left: 3px solid var(--accent); }
.setup-h { margin-bottom: 6px; }
.setup-step { display: flex; gap: 12px; align-items: center; padding: 10px 8px; border-top: 1px solid var(--border);
  text-decoration: none; color: var(--text); border-radius: 8px; }
.setup-step:hover { background: var(--surface-2); }
.setup-n { flex: 0 0 26px; height: 26px; border-radius: 50%; background: var(--surface-2); color: var(--muted);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.8125rem; }
.setup-n.done { background: color-mix(in srgb, var(--good) 20%, var(--surface-2)); color: var(--good); }

/* Fixed at the very top so it never scrolls away or lets content slide under it;
   ui.js publishes its height as --rv-banner-h and `body` reserves that space (see
   the body rule), which clears the banner for EVERY page + sticky topbar at once.
   Opaque backgrounds (color-mix over --bg) so scrolling content can't show through. */
/* A fixed container that stacks 1–2 bars (platform announcement + support/onboarding);
   its total height flows into --rv-banner-h, which the whole app already clears. */
.rv-banner { position: fixed; top: 0; left: 0; right: 0; z-index: 1000; }
/* Grid row: [left gutter] [centered message] [dismiss ✕] — vertical centering
   comes from align-items, so it can't drift onto the divider. */
.rv-bar { display: grid; grid-template-columns: 36px 1fr 36px; align-items: center; padding: 9px 4px; font-size: 0.875rem; border-bottom: 1px solid var(--border); background: var(--surface); }
.rv-bar.bad, .rv-bar.critical { background: color-mix(in srgb, var(--bad) 16%, var(--bg)); color: var(--bad); border-bottom-color: var(--bad); }
.rv-bar.warn { background: color-mix(in srgb, var(--warn) 15%, var(--bg)); color: var(--warn); border-bottom-color: var(--warn); }
.rv-bar.info { background: var(--surface-2); color: var(--muted); }
.rv-bar.rehearsal { background: var(--practice); color: var(--on-practice); border-bottom-color: var(--practice); font-weight: 600; }
/* #1415 D3 — the practice bar is ONE line: both copies always render, CSS shows exactly one (the short
   one below 480px), and neither may wrap (min-width:0 lets the 1fr grid cell shrink so ellipsis works). */
.rv-bar.rehearsal .rv-bar-msg { min-width: 0; }
.rv-bar-full, .rv-bar-short { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rv-bar-short { display: none; }
@media (max-width: 479px) { .rv-bar-full { display: none; } .rv-bar-short { display: block; } }
.rv-bar-tap { cursor: pointer; }
.rv-bar-expanded { display: block; margin-top: 4px; font-weight: 400; font-size: 0.8125rem; white-space: normal; }
.rv-bar-msg { grid-column: 2; text-align: center; }
.rv-bar-x { grid-column: 3; justify-self: center; cursor: pointer; background: none; border: 0; color: inherit; font-size: 0.9375rem; line-height: 1; opacity: .6; padding: 4px 6px; border-radius: 6px; }
.rv-bar-x:hover { opacity: 1; background: rgba(127,127,127,.15); }
/* #1417 — the expandable (practice) bar's chevron: grid column 3, rotated via aria-expanded, no
   transition under reduced motion. Outside .rv-bar-msg so vote.spec.ts's exact .rv-bar-full /
   .rv-bar-short text assertions are untouched. */
.rv-bar-chev { grid-column: 3; justify-self: center; display: inline-flex; opacity: .8; }
@media (prefers-reduced-motion: no-preference) { .rv-bar-chev svg { transition: transform .15s ease; } }
.rv-bar[aria-expanded="true"] .rv-bar-chev svg { transform: rotate(180deg); }
/* #1415 decision 5 / #1418 D10 — the projector's practice indicator. The chip (`.rv-practice-corner`,
   class kept so #1415's e2e still finds it) now sits IN-FLOW inside `.scr-head` (#1418's room frame,
   below) instead of floating fixed bottom-right — always visible, never covering the stage or the
   clock. The change bar (`.rv-practice-changebar`) is also in-flow now: App.tsx renders it in place of
   `ScreenHead` for 10s after a change, so `position: static` (not `fixed`) lets it fill that grid row. */
.rv-practice-corner { display: inline-flex; align-items: center; gap: var(--scr-space-2, 8px); background: var(--practice-tint); color: var(--practice); font-weight: 700; font-size: calc(var(--t1-size, 12) * var(--scr-u, 1px)); text-transform: uppercase; letter-spacing: 0.06em; padding: 0.3em 0.8em; border-radius: 999px; white-space: nowrap; }
.rv-practice-changebar { background: var(--practice); color: var(--on-practice); font-weight: 600; text-align: center; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-3) var(--space-4); font-size: calc(var(--t3-size, 16) * var(--scr-u, 1px)); animation: rv-practice-in .25s ease; }
@keyframes rv-practice-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .rv-practice-changebar { animation: none; } }

/* #1455 — block callout ("Support" onboarding/warn notices, comment-signup closed, etc.):
   .note had no rule at all, so it rendered as plain text with a border-left-color and no
   border-left-width to show it (a de facto zero-width border). Base tone is --accent
   (informational); toned variants mirror the .rv-bar.* pattern above. `display: block` is
   required: .note also lands on an inline <span> (public/privacy.html), and the padding/
   border/radius here would otherwise split across line wraps on an inline box. */
.note { display: block; background: color-mix(in srgb, var(--accent) 10%, transparent); border-left: 3px solid var(--accent);
  border-radius: 8px; padding: 10px 12px; margin: 10px 0; }
.note.warn { background: color-mix(in srgb, var(--warn) 10%, transparent); border-left-color: var(--warn); }
.note.bad { background: color-mix(in srgb, var(--bad) 10%, transparent); border-left-color: var(--bad); }
.note.good { background: color-mix(in srgb, var(--good) 10%, transparent); border-left-color: var(--good); }

.rv-spin { display: inline-block; width: 13px; height: 13px; margin-right: 8px; vertical-align: -1px;
  border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: rv-spin .6s linear infinite; }
@keyframes rv-spin { to { transform: rotate(360deg); } }

/* Shared "reconnecting" chip (ui.js RV.conn) for the live-poll surfaces — the
   delegate ballot + the big screen. Stays out of the way (top-center) but is
   legible on both a phone and the dark projector; amber reads on either ground. */
.rv-reconnect { position: fixed; top: 12px; left: 50%; transform: translateX(-50%); z-index: 9999;
  background: #b26a00; color: #fff; font-size: 0.875rem; font-weight: 600; letter-spacing: .01em;
  padding: 7px 16px; border-radius: 999px; box-shadow: 0 6px 20px rgba(0,0,0,.35);
  max-width: 92vw; text-align: center; pointer-events: none; }
@media print { .rv-reconnect { display: none; } }
/* #1418 D16 — the projector's own reconnect chip: quiet (tokens, not the raw amber above), bottom-
   left (never over the stage or the header's clock), a display: inline-flex row for the Icon +
   text. Every other page's chip is unchanged (its raw hex amber and glyph are reported, not fixed —
   spec "Reported, not built"; out of scope for #1418). */
.rv-reconnect--projector { top: auto; bottom: 14px; left: 14px; transform: none; display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface); color: var(--warning); border: 1px solid var(--warning); box-shadow: none;
  font-size: 0.8125rem; font-weight: 500; }

/* Account menu (1.9) — ui.js-injected. #328: now docked in the standardized header's
   right slot (was a floating fixed widget), so it's position:relative for its dropdown. */
.rv-acct { position: relative; z-index: 901; }
.rv-acct-btn { display: inline-flex; align-items: center; gap: 7px; margin: 0; padding: 5px 12px 5px 6px; font-size: 0.8125rem; font-weight: 600;
  background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 999px; cursor: pointer; }
.rv-acct-btn:hover { border-color: var(--accent); }
.rv-acct-dot { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; background: var(--accent); color: var(--on-primary); font-size: 0.75rem; font-weight: 800; }
.rv-acct-nm { max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rv-acct-menu { position: absolute; top: 42px; right: 0; min-width: 190px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: 0 14px 36px rgba(0,0,0,.42); padding: 6px; z-index: 902; }
.rv-acct-menu .rv-acct-hd { font-size: 0.75rem; color: var(--muted); padding: 6px 10px; border-bottom: 1px solid var(--border); margin-bottom: 4px; overflow: hidden; text-overflow: ellipsis; }
.rv-acct-menu a, .rv-acct-menu .rv-acct-out { display: block; width: 100%; text-align: left; padding: 8px 10px; font-size: 0.875rem; color: var(--text);
  text-decoration: none; background: none; border: 0; border-radius: 8px; margin: 0; cursor: pointer; }
.rv-acct-menu a:hover, .rv-acct-menu .rv-acct-out:hover { background: var(--surface-2); }
.rv-acct-menu .rv-acct-out { color: var(--bad); border-top: 1px solid var(--border); margin-top: 4px; }
@media print { .rv-acct { display: none; } }

/* #1423 — Appearance (System / Light / Dark) in the account menu, React AppShell and vanilla ui.js
   alike. Same pairings as .rv-segmented (#1750): onPrimary on primary when chosen; text +
   borderStrong on surface otherwise, both already AA-checked by test:contrast in light and dark. */
.rv-acct-menu:has(.rv-acct-appearance) { min-width: 272px; }
.rv-acct-appearance { border-top: 1px solid var(--border); margin-top: 4px; padding: 6px 4px 4px; }
.rv-acct-sub { font-size: 0.75rem; color: var(--muted); padding: 0 6px 6px; }
.rv-acct-seg { display: flex; gap: 4px; }
.rv-acct-seg-opt { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px;
  padding: 0 var(--space-2); border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  background: var(--surface); color: var(--text); font: inherit; font-size: 0.875rem; font-weight: 600; cursor: pointer; }
.rv-acct-seg-opt:hover { border-color: var(--text-2); }
.rv-acct-seg-opt.is-selected { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.rv-acct-seg-opt:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* #328: standardized header bits — Home slot (left), right-side control group, and the
   pre-auth minimal header for /login. */
.rv-home { display: inline-flex; align-items: center; gap: 5px; text-decoration: none; color: var(--text); font-weight: 600; font-size: 0.875rem;
  padding: 5px 10px; border: 1px solid var(--border); border-radius: 999px; }
.rv-home:hover { border-color: var(--accent); }
.rv-home .rv-home-ic { font-size: 0.9375rem; line-height: 1; }
.topbar .rv-hd-right { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rv-header-min { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; }
.rv-header-min .rv-hm-brand { font-weight: 800; font-size: 1rem; color: var(--text); }
.rv-header-min .rv-hd-right { display: inline-flex; align-items: center; gap: 10px; }
/* Narrow-phone header trims (#784): icon-only Home + Back, drop the org pill and the
   account-menu name so the topbar stays a clean row of icons — like the mobile app. */
@media (max-width: 560px) {
  .rv-home .rv-home-lbl { display: none; }        /* icon-only Home */
  #back .back-lbl { display: none; }              /* icon-only ← Back */
  .topbar .brand-row > #org { display: none; }    /* drop the org pill */
  .rv-acct-nm { display: none; }                  /* avatar-only account menu */
  #brand .brand-sfx { display: none; }            /* "Meetings" — drop the " & Conventions" suffix */
  .topbar .brand-row { flex-wrap: nowrap; min-width: 0; }  /* one clean row, no second line */
  .topbar .brand-row > .brand { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
}

/* Recess strip (clerk) — amber, prominent while the meeting is paused. */
.recess-strip { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 10px 14px; border-radius: 12px; margin-bottom: 12px;
  background: color-mix(in srgb, var(--warn) 15%, var(--bg)); border: 1px solid var(--warn); }
.recess-strip input { margin: 0; }
/* #222: the idle "start recess" affordance — same shape as .recess-strip but neutral
   (panel/border, not warn), so a running meeting isn't styled as if it's alerting. */
.recess-idle { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 8px 14px; border-radius: 12px; margin-bottom: 12px;
  background: var(--surface-2); border: 1px solid var(--border); }

/* Clerk floor run-bar — the persistent live cockpit (drive agenda + publish). */
.runbar { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; justify-content: space-between;
  padding: 10px 12px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; margin-bottom: 12px; }
.runbar-where { min-width: 0; }
.runbar-ctrl { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.runbar-ctrl button { margin: 0; }

/* Chair view (3.1) — presiding-officer dashboard. Generic (any meeting). */
.desktop.chair-page { padding: 12px; }                     /* tighter than the 24px default — more room for the meeting */
/* Slim, borderless header — no card, just a thin strip; the default card wraps the
   picker above Home and wastes height. */
.chair-page .topbar { margin-bottom: 12px; flex-wrap: wrap; gap: 6px 12px; position: static; }
.chair-page .topbar > span { display: inline-flex; align-items: center; gap: 8px; flex-wrap: nowrap; }
.chair-page #meeting-sel { max-width: 190px; }
.chair-page .chair-now { margin-top: 0; }
.pill.good { background: var(--success-tint); color: var(--good); }
.pill.bad { background: var(--danger-tint); color: var(--bad); }
/* #1415 — the practice / support-access chip that stands in for a bar a state has lost (web: AppShell's
   topbar brand-row; vanilla: inline inside the winning bar). */
/* Base properties sit on the modifier pair only: landing/demo/conventions/verification/compare-electionbuddy
   define their own bare .chip, and a global .chip would leak display/gap/weight into them. */
.chip.practice, .chip.support { display: inline-flex; align-items: center; gap: 4px; font: var(--t2); font-weight: 700; padding: 3px 9px; border-radius: 999px; white-space: nowrap; }   /* #1426 F2 — not caps, 11px -> s2 (14) */
.chip.practice { background: var(--practice-tint); color: var(--practice); }
.chip.support { background: var(--danger-tint); color: var(--danger); }
.ag-row.ag-cur { background: var(--primary-tint); border: 1px solid var(--accent); }
.ag-row.done b { color: var(--muted); font-weight: 500; }
.ag-row:hover { background: var(--surface-2); }
.chair-now .chair-cur { font-size: 1.25rem; margin: 8px 0; }
.chair-kind { color: var(--muted); }
/* Teleprompter — large, high-contrast, easy to read from the podium. */
.chair-script { background: var(--surface-2); border: 1px solid var(--border); border-left: 4px solid var(--accent);
  border-radius: 12px; padding: 14px 16px; margin: 10px 0; }
.chair-script-text { font-size: 1.5625rem; line-height: 1.5; white-space: pre-wrap; }   /* read-aloud; size is device-adjustable (A− / A+) */
.chair-script-label { font-size: 0.6875rem; letter-spacing: .08em; text-transform: uppercase; color: var(--accent); font-weight: 700; margin-bottom: 6px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.chair-script-size { display: inline-flex; gap: 4px; }
.chair-script-size button { margin: 0; min-height: 30px; min-width: 34px; padding: 2px 8px; font-size: 0.8125rem; }
.chair-next { margin-top: 8px; }
.chair-action { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.chair-action:empty { display: none; }
.chair-action button { margin: 0; }
.vote-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; margin-bottom: 10px; }
/* Bigger touch targets for the chair's primary actions (podium / iPad). */
button.lg { padding: 14px 20px; font-size: 1rem; min-height: 48px; }
button.warn { background: var(--warning-tint); color: var(--warn); }
/* Sticky bottom action bar — Back / contextual primary / Advance, reachable from anywhere. */
.chair-page #main { padding-bottom: 88px; }
.chair-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 50; display: flex; gap: 10px; align-items: center;
  padding: 10px max(16px, env(safe-area-inset-left)) calc(10px + env(safe-area-inset-bottom));
  background: var(--surface); border-top: 1px solid var(--border); box-shadow: 0 -6px 22px rgba(0,0,0,.22); }
.chair-bar > button { margin: 0; min-height: 48px; min-width: 96px; }
.chair-bar-action { flex: 1; display: flex; justify-content: center; }
.chair-bar-action button { margin: 0; min-height: 48px; }
.chair-bar-action:empty { flex: 1; }
@media (max-width: 720px) { .chair-now .chair-cur { font-size: 1.125rem; } .chair-script { font-size: 1.125rem; }
  .chair-bar > button { min-width: 84px; } }
@media print { .chair-bar { display: none; } }
/* #1420 — a toast must never visually overlap the sticky chair bar. StickyBar publishes its own
   height as --chair-bar-h (mirroring how public/ui.js publishes --rv-banner-h); the toast host clears
   it here, the one place that matters, rather than each toast computing its own offset. */
.chair-page #rv-toasts { bottom: calc(18px + var(--chair-bar-h, 0px)); }

/* #1420 — Ruling + Recess grouped at the top of the chair body, read as header controls. */
.chair-header { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.chair-header-actions { gap: 10px; }

/* #1420 — the neutral quorum bar + threshold tick, replacing the old zero-count stat tiles. Neutral
   (not green) so it never competes visually with the vote card. */
.chair-quorum { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 10px 14px; }
.chair-quorum-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; font-weight: 600; }
.chair-quorum-met .chair-quorum-label { color: var(--good); }
.chair-quorum-below .chair-quorum-label { color: var(--warn); }
.chair-quorum-track { position: relative; height: 8px; border-radius: 999px; background: var(--surface-2); margin-top: 8px; overflow: hidden; }
.chair-quorum-fill { position: absolute; inset: 0; width: 0; background: var(--muted); border-radius: 999px; transition: width .2s ease; }
.chair-quorum-met .chair-quorum-fill { background: var(--good); }
.chair-quorum-below .chair-quorum-fill { background: var(--warn); }
.chair-quorum-tick { position: absolute; right: 0; top: -2px; bottom: -2px; width: 2px; background: var(--border); }

/* #1420 (Task 5, #1886 review blocker) — "The wide layout is a CSS grid that adds the rail at
   min-width: 900px" (spec), matching native's WIDE_BP = 900. Below 900px this collapses to one column
   — `.chair-main` stacks normally and `.chair-rail` is hidden (the "Run of show · N of M done" toggle
   row, `.chair-rail-toggle`, opens the same list in a sheet instead). At ≥900px `.chair-rail` becomes
   this grid's SECOND column — a true side rail, not a full-width panel stacked under the quorum bar
   (the review's screenshot evidence at 1280 and 1024). */
.chair-layout { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; gap: 16px; }
.chair-main { min-width: 0; }
.chair-rail { display: none; margin-top: 12px; }
.chair-rail-toggle { display: flex; width: 100%; justify-content: center; margin-top: 12px; }
/* Reuses the existing `.ag-row` cur/done/hover treatment (public/styles.css) — same visual language
   as the projector's AgendaRail, no new raw-colour literal for the current-row highlight. */
.chair-rail-list { display: flex; flex-direction: column; }
.chair-rail-row { display: flex; align-items: center; gap: 6px; min-height: 44px; padding: 5px 8px; border-radius: 8px; }
@media (min-width: 900px) {
  .chair-layout { grid-template-columns: minmax(0, 1fr) 320px; }
  .chair-rail { display: block; margin-top: 0; position: sticky; top: 8px; }
  .chair-rail-toggle { display: none; }
}

/* #1420 — "Also open (n)" (formerly "Other open votes"): renders only when non-empty (Task 4). */
.chair-other { margin-top: 12px; }

/* Elevation Station — phone-first: district chairs run this from the floor on a handset.
   Cards flow in one column on a phone, multi-column only where there's room. */
.desktop.elev-page { padding: 14px; }
.elev-page #units { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 12px; align-items: start; }
.elev-page #units > .panel { margin-top: 0; }   /* grid gap owns the spacing */
.elev-page .elev-row { display: flex; justify-content: space-between; align-items: center; gap: 10px;
  border-bottom: 1px solid var(--border); padding: 8px 0; flex-wrap: wrap; }
.elev-page .elev-who { flex: 1 1 60%; min-width: 0; }
.elev-page .elev-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.elev-page .elev-actions button { margin: 0; min-height: 44px; }   /* comfortable touch target */
@media (max-width: 560px) {
  .desktop.elev-page { padding: 10px; }
  .elev-page #units { grid-template-columns: 1fr; }
  .elev-page .elev-actions { width: 100%; }                        /* actions drop below the name, full-width */
  .elev-page .elev-actions button { flex: 1 1 auto; }
}

/* Contextual help drawer (#28) — a right-side panel that shows a help topic in
   context; opened by RV.help() / any [data-help] element. Themed via tokens. */
.rv-help-back { position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 1100; display: flex; justify-content: flex-end; }   /* above the fixed banner (1000) + account menu (901) so the header/close aren't covered */
.rv-help { width: min(560px, 100%); height: 100%; background: var(--surface); border-left: 1px solid var(--border); display: flex; flex-direction: column; box-shadow: -8px 0 30px rgba(0,0,0,.25); }
.rv-help-hd { display: flex; gap: 8px; align-items: center; padding: 10px 12px; border-bottom: 1px solid var(--border); }
/* all three controls share one height so they line up cleanly (no ragged header) */
.rv-help-q { flex: 1; min-width: 0; min-height: 2.25rem; box-sizing: border-box; padding: 0 10px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--text); }
.rv-help-full { flex: none; width: 36px; height: 36px; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; background: none; border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); cursor: pointer; text-decoration: none; }
.rv-help-x { flex: none; margin: 0; min-height: 2.25rem; box-sizing: border-box; padding: 0 12px; display: inline-flex; align-items: center; gap: 5px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-weight: 600; cursor: pointer; }   /* margin:0 resets the base button margin-top */
.rv-help-full:hover, .rv-help-x:hover { background: var(--accent); color: var(--on-primary); border-color: var(--accent); }
.rv-help-body { flex: 1; display: grid; grid-template-columns: 180px 1fr; min-height: 0; }
.rv-help-list { border-right: 1px solid var(--border); overflow: auto; padding: 8px; }
.rv-help-list a { display: block; padding: 6px 8px; border-radius: var(--radius); color: var(--muted); text-decoration: none; font-size: 0.875rem; }
.rv-help-list a:hover { background: var(--surface-2); color: var(--text); }
.rv-help-list a.on { background: var(--accent); color: var(--on-primary); }
.rv-help-content { overflow: auto; padding: 16px 20px; }
.rv-help-content h2 { margin: 0 0 10px; }
.rv-help-content h3 { margin: 16px 0 6px; }
.rv-help-content p { margin: 8px 0; }
.rv-help-content ul { padding-left: 20px; margin: 8px 0; }
.rv-help-content li { margin: 3px 0; }
.rv-help-content a { color: var(--accent); }
.rv-help-content blockquote { margin: 12px 0; padding: 10px 12px; border-left: 3px solid var(--accent); background: var(--surface-2); border-radius: var(--radius); }
.rv-help-content blockquote p:first-child { margin-top: 0; }
.rv-help-content blockquote p:last-child { margin-bottom: 0; }
.rv-help-content code { background: var(--surface-2); padding: 1px 5px; border-radius: 4px; }
@media (max-width: 620px) { .rv-help-body { grid-template-columns: 1fr; } .rv-help-list { display: none; } .rv-help-x span { display: none; } .rv-help-x { padding: 0; width: 34px; justify-content: center; } }

/* Inline contextual help dot (#28) — a subtle ❔ that opens the drawer on a
   specific sub-topic from within a panel. */
.rv-help-dot { border: none; background: none; cursor: pointer; color: var(--muted); font: inherit; margin: 0; padding: 0 2px; line-height: 1; }
.rv-help-dot:hover { color: var(--accent); }

/* marketing:start */
/* ── Marketing chrome (#1175; restyled to the approved G0 mockup by #1442 PR 1b): the shared header, menu and footer
   (markup in src/marketing/chrome.ts), the one-colour lockup, and the flat buttons every public page uses.
   Role tokens only (#1412): no legacy lp-prefixed custom properties, no gradient, glow, blur or shadow. ── */

/* #1442 — the public pages' skip link (§10) uses the shared .skip-link rule (#1422, just below this region): off-screen until focused, a 44px target, a --primary focus ring. */
#main-content:focus { outline: none; }   /* the skip target takes focus programmatically; no ring around the whole page (like .org-main:focus) */

/* The one-colour lockup (#1442 D5): a hidden sprite carries <symbol id="rv-lockup">; each <use> draws it in currentColor. */
.rv-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.lp-brand { display: inline-flex; align-items: center; min-height: 44px; flex: none; color: var(--text); text-decoration: none; }
.lp-lockup { display: block; height: 28px; width: 161px; }
.lp-lockup.sm { height: 22px; width: 127px; }
.lp-brand .tm { align-self: flex-start; margin: 9px 0 0 2px; font: var(--t1); line-height: 1; color: var(--text); }

/* Buttons (D6): flat --primary with --on-primary, no shadow, no lift; the ghost is a --border-strong outline. */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); min-height: 44px; padding: 0 20px;
  border: 1px solid transparent; border-radius: var(--radius-md); background: var(--primary); color: var(--on-primary);
  font: var(--t3); font-weight: 600; text-decoration: none; white-space: nowrap; cursor: pointer; }
.btn:hover { background: color-mix(in srgb, var(--primary) 90%, var(--ground)); }
.btn.ghost { background: transparent; color: var(--text); border-color: var(--border-strong); }
.btn.ghost:hover { border-color: var(--text-2); }
.btn.sm { padding: 0 var(--space-4); font: var(--t2); font-weight: 600; }
.btn:focus-visible, .lp-nav a:focus-visible, .lp-burger:focus-visible, .lp-foot a:focus-visible, .lp-brand:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px; }

/* Header: solid --ground with a 1px rule; content at 1120px. */
.lp-nav { position: sticky; top: 0; z-index: 30; background: var(--ground); border-bottom: 1px solid var(--border); }
.lp-nav-in { max-width: 1120px; margin: 0 auto; padding: 10px 20px; min-height: 64px; display: flex; align-items: center; gap: var(--space-5); }
.lp-nav .links { display: flex; align-items: center; gap: 2px; }
.lp-nav .links a:not(.menu-cta) { display: inline-flex; align-items: center; min-height: 44px; padding: 0 10px; border-radius: var(--radius-md);
  color: var(--text-2); font: var(--t2); text-decoration: none; }
.lp-nav .links a:not(.menu-cta):hover { color: var(--text); }
.lp-nav .links a[aria-current="page"] { color: var(--text); text-decoration: underline; text-decoration-color: var(--primary);
  text-decoration-thickness: 2px; text-underline-offset: 8px; }
.lp-nav .links .menu-cta { display: none; }
.lp-nav-right { display: flex; align-items: center; gap: var(--space-2); margin-left: auto; }
.lp-burger { display: none; width: 44px; height: 44px; place-items: center; margin: 0; padding: 0; background: none; color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md); font-size: 1.25rem; line-height: 1; cursor: pointer; }

/* Below 940px the links collapse into the menu, so the header never wraps (G0, owner-approved). */
@media (max-width: 939px) {
  .lp-burger { display: grid; }
  .lp-nav .links { display: none; position: absolute; top: 100%; left: 0; right: 0; flex-direction: column; align-items: stretch; gap: 0;
    padding: var(--space-1) 20px 20px; background: var(--ground); border-bottom: 1px solid var(--border); }
  .lp-nav.open .links { display: flex; }
  .lp-nav .links a:not(.menu-cta) { min-height: 52px; padding: 0; border-radius: 0; border-bottom: 1px solid var(--border); color: var(--text); font: var(--t3); }
  .lp-nav .links a[aria-current="page"] { text-decoration: none; box-shadow: inset 3px 0 0 var(--primary); padding-left: var(--space-3); }
}
/* Below 620px the two actions leave the bar and join the menu, as real buttons (G0, owner-approved). */
@media (max-width: 619px) {
  .lp-nav-in { padding: var(--space-2) var(--space-4); }
  .lp-nav-right .btn { display: none; }
  .lp-nav .links { padding: var(--space-1) var(--space-4) 20px; }
  .lp-nav .links .menu-cta { display: flex; min-height: 48px; margin-top: var(--space-3); }
  .lp-nav .links .menu-cta:not(.ghost) { margin-top: 20px; }
}

/* Footer */
.lp-foot { border-top: 1px solid var(--border); }
.lp-foot-in { max-width: 1120px; margin: 0 auto; padding: var(--space-6) 20px var(--space-7); display: grid; gap: var(--space-4); }
.lp-foot-top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-6); }
.lp-foot .foot-links { display: flex; flex-wrap: wrap; gap: 0 2px; margin: 0 calc(-1 * var(--space-2)); }
.lp-foot .foot-links a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--space-2); color: var(--text-2); font: var(--t2); text-decoration: none; }
.lp-foot .foot-links a:hover { color: var(--text); }
.lp-foot .foot-copy { color: var(--muted); font: var(--t2); }
@media (max-width: 619px) {
  .lp-foot-in { padding: 28px var(--space-4) 40px; }
  .lp-foot .foot-links { display: grid; grid-template-columns: 1fr 1fr; margin: 0; }
  .lp-foot .foot-links a { padding: 0; }
}

@media (prefers-reduced-motion: no-preference) {
  .btn, .lp-nav .links a, .lp-foot a { transition: background-color .15s, border-color .15s, color .15s; }
}
/* marketing:end */

/* #1418 projector ───────────────────────────────────────────────────────────────────────────────────
   The big screen, read by everyone in the room at once. Room scale: `--scr-u` is a unitless length —
   token PIXELS at a 960×540 reference, floored at 1× (a phone stays at least token size) and capped at
   4× (very large displays don't grow without bound). A step's font-size is
   `calc(var(--tN-size) * var(--scr-u))`; weight/line-height are the token's own numbers, hardcoded here
   per step (mirrors render.ts's `--tN` shorthand, which does the same for the same reason: CSS has no
   way to destructure a `font` shorthand back into its weight). Pinned dark (the server forces
   data-theme="dark" on /screen) — every colour below is a role variable. No borders on the stage. */
.scr {
  --scr-u: clamp(1px, min(100vw / 960, 100dvh / 540), 4px);
  --scr-space-1: calc(var(--space-1-size) * var(--scr-u));
  --scr-space-2: calc(var(--space-2-size) * var(--scr-u));
  --scr-space-3: calc(var(--space-3-size) * var(--scr-u));
  --scr-space-4: calc(var(--space-4-size) * var(--scr-u));
  --scr-space-5: calc(var(--space-5-size) * var(--scr-u));
  --scr-space-6: calc(var(--space-6-size) * var(--scr-u));
  --scr-space-7: calc(var(--space-7-size) * var(--scr-u));
  --scr-space-8: calc(var(--space-8-size) * var(--scr-u));
  height: 100dvh; overflow: hidden; box-sizing: border-box;
  display: grid; grid-template-rows: auto 1fr auto;
  background: var(--ground); color: var(--text); font-family: var(--font-sans);
  padding: var(--scr-space-5) var(--scr-space-6) var(--scr-space-6);
  gap: var(--scr-space-4);
}
.scr * { box-sizing: border-box; }
/* Room-scale type steps. Weight/letter-spacing/tabular-nums are the token's own fixed values —
   test:brand-tokens pins the SIZE/LINE numbers against tokens.ts; these companions are visual constants
   that don't need scaling, matching every other consumer of `--tN`. */
.scr-t1 { font-size: calc(var(--t1-size) * var(--scr-u)); line-height: calc(var(--t1-line) * var(--scr-u)); font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; }
.scr-t2 { font-size: calc(var(--t2-size) * var(--scr-u)); line-height: calc(var(--t2-line) * var(--scr-u)); font-weight: 400; }
.scr-t3 { font-size: calc(var(--t3-size) * var(--scr-u)); line-height: calc(var(--t3-line) * var(--scr-u)); font-weight: 400; }
.scr-t4 { font-size: calc(var(--t4-size) * var(--scr-u)); line-height: calc(var(--t4-line) * var(--scr-u)); font-weight: 600; }
.scr-t5 { font-size: calc(var(--t5-size) * var(--scr-u)); line-height: calc(var(--t5-line) * var(--scr-u)); font-weight: 600; }
.scr-t6 { font-size: calc(var(--t6-size) * var(--scr-u)); line-height: calc(var(--t6-line) * var(--scr-u)); font-weight: 600; }
.scr-t7 { font-size: calc(var(--t7-size) * var(--scr-u)); line-height: calc(var(--t7-line) * var(--scr-u)); font-weight: 600; }
.scr-t8 { font-size: calc(var(--t8-size) * var(--scr-u)); line-height: calc(var(--t8-line) * var(--scr-u)); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ── header ── */
.scr-head { display: flex; align-items: center; justify-content: space-between; gap: var(--scr-space-5); flex-wrap: wrap; min-height: calc(40 * var(--scr-u)); }
.scr-head-id { display: flex; align-items: center; gap: var(--scr-space-4); min-width: 0; }
.scr-seal { width: calc(40 * var(--scr-u)); height: calc(40 * var(--scr-u)); flex: none; object-fit: contain; background: var(--seal-ground); border-radius: var(--radius-md); }
.scr-head-name { min-width: 0; }
.scr-title { margin: 0; font-size: calc(var(--t5-size) * var(--scr-u)); line-height: calc(var(--t5-line) * var(--scr-u)); font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scr-sub { margin: 0; font-size: calc(var(--t2-size) * var(--scr-u)); line-height: calc(var(--t2-line) * var(--scr-u)); color: var(--muted); font-variant-numeric: tabular-nums; }
.scr-head-r { display: flex; align-items: center; gap: var(--scr-space-4); flex: none; }
.scr-points { display: inline-flex; align-items: center; gap: var(--scr-space-2); background: transparent; color: var(--warning); border: 1px solid var(--warning); border-radius: 999px; padding: 0.3em 0.8em; font-size: calc(var(--t1-size) * var(--scr-u)); font-weight: 500; white-space: nowrap; }
.scr-clock { font-size: calc(var(--t4-size) * var(--scr-u)); font-weight: 500; color: var(--text-2); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ── body: stage + rail ── */
.scr-body { display: grid; grid-template-columns: 1fr clamp(200px, 22vw, 340px); gap: var(--scr-space-6); min-height: 0; }
.scr-body.solo { grid-template-columns: 1fr; }
.scr-stage { min-height: 0; min-width: 0; display: flex; flex-direction: column; justify-content: safe center; overflow: hidden; position: relative; }
.scr-rail-wrap { min-height: 0; display: flex; flex-direction: column; border-left: 1px solid var(--border); padding-left: var(--scr-space-4); }
.scr-rail-title { flex: none; font-size: calc(var(--t1-size) * var(--scr-u)); line-height: calc(var(--t1-line) * var(--scr-u)); font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); padding-bottom: var(--scr-space-2); margin-bottom: var(--scr-space-2); border-bottom: 1px solid var(--border); }
.scr-rail { flex: 1; min-height: 0; overflow: auto; }
.scr-ag-line { font-size: calc(var(--t2-size) * var(--scr-u)); line-height: calc(var(--t2-line) * var(--scr-u)); color: var(--muted); padding: var(--scr-space-2) 0; border-left: calc(3.5 * var(--scr-u)) solid transparent; padding-left: calc(var(--scr-space-3) + var(--ag-indent, 0px)); }
.scr-ag-line.done { color: var(--muted); opacity: 0.7; }
.scr-ag-line.cur { color: var(--text); font-weight: 600; border-left-color: var(--primary); }

/* ── footer ── */
.scr-foot { display: flex; align-items: center; justify-content: flex-end; gap: var(--scr-space-2); font-size: calc(var(--t1-size) * var(--scr-u)); letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.scr-foot svg { width: calc(16 * var(--scr-u)); height: calc(16 * var(--scr-u)); }

/* ── vote / result shared bits ── */
.scr-eyebrow { display: flex; align-items: center; gap: var(--scr-space-2); font-size: calc(var(--t1-size) * var(--scr-u)); line-height: calc(var(--t1-line) * var(--scr-u)); font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); margin-bottom: var(--scr-space-3); }
/* #1951 fix round M1 — every icon beside room-scale text sizes off that text's OWN font-size (1em), so
   it grows with --scr-u like the words next to it, instead of sitting at Lucide's fixed 16/20/24px. */
.scr-eyebrow svg, .scr-tag svg, .scr-points svg, .rv-practice-corner svg, .rv-practice-changebar svg, .scr-recess-caption svg, .ss-t svg, .scr-recount svg { width: 1em; height: 1em; flex: none; }
.scr-eyebrow-live { color: var(--success); }
.scr-eyebrow-live svg { color: var(--success); }
.scr-live-dot { display: inline-block; width: calc(8 * var(--scr-u)); height: calc(8 * var(--scr-u)); border-radius: 50%; background: var(--success); }
@media (prefers-reduced-motion: no-preference) { .scr-live-dot { animation: scr-live-pulse 1.6s ease-in-out infinite; } }
@keyframes scr-live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.scr-q { font-size: calc(var(--t7-size) * var(--scr-u)); line-height: calc(var(--t7-line) * var(--scr-u)); font-weight: 600; margin: 0 0 var(--scr-space-4); }
.scr-q-scope { font-size: calc(var(--t4-size) * var(--scr-u)); color: var(--muted); font-weight: 400; }
.scr-q-amend { font-size: calc(var(--t4-size) * var(--scr-u)); color: var(--text-2); font-weight: 400; margin-top: var(--scr-space-2); }
.scr-fig { font-size: calc(var(--t8-size) * var(--scr-u)); line-height: calc(var(--t8-line) * var(--scr-u)); font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); }
.scr-fig-unit { font-size: calc(var(--t5-size) * var(--scr-u)); color: var(--text-2); margin-top: calc(-0.3 * var(--scr-u)); }
.scr-prog { margin-top: var(--scr-space-3); height: calc(9 * var(--scr-u)); background: var(--raised); border-radius: 999px; overflow: hidden; max-width: 640px; }
.scr-prog-fill { height: 100%; background: var(--primary); border-radius: 999px; }
@media (prefers-reduced-motion: no-preference) { .scr-prog-fill { transition: width 0.4s ease; } }
.scr-meta { font-size: calc(var(--t4-size) * var(--scr-u)); color: var(--text-2); margin-top: var(--scr-space-3); }
.scr-ballot-names { font-size: calc(var(--t4-size) * var(--scr-u)); color: var(--muted); margin-top: var(--scr-space-3); }
.scr-vote, .scr-result { max-width: 100%; }
/* #1951 fix round B1 — a flexible rows region: .scr-result never grows past the stage it's centred in
   (max-height caps it at the space .scr-stage actually has), so when a result has more rows than fit
   (election's slice(0, seats + runnersUp + 1) can still outgrow the stage), .scr-rows shrinks and clips
   its OWN overflow at the bottom instead of pushing the eyebrow/title/verdict above it off the top of
   the stage. #1961 closed the clip itself — see `--rs` below.
   #2087 round 2 (independent review, BLOCKING 1) — `.scr-vote` needs the SAME bounded-flex-column rule
   for the identical reason: `liveCounts`' `.scr-rows { flex: 1; min-height: 0 }` is inert on a
   block-level parent (`flex`/`min-height` only mean anything on a flex ITEM whose container is itself
   `display: flex`), so `.scr-rows` fell back to `height: auto` and `FitRows`' own `if (el.scrollHeight
   <= avail) return` (FitText.tsx) fired every single time — `avail` (clientHeight) and the natural
   content height were IDENTICAL by construction, not by coincidence. Measured before this line existed:
   clip unchanged to the tenth of a pixel, 20 rows, every viewport (2727.0px → 2727.0px at 1920x1080).
   This one rule is shared by `.scr-vote`'s five other bodies (ProgressBody/Nominations/VoiceVote/
   TellerVote/PaperLive) too — checked all five, 18 body×viewport combinations (independent review):
   zero elements report internal truncation, and the legible line count is IDENTICAL before and after
   in every case, including `.scr-prog` (an empty div with an explicit height — the one place a
   flex-shrink hazard would most plausibly bite). What is NOT identical: `Nominations` and `PaperLive`
   overflow 16-32px further than before, because margins no longer COLLAPSE between siblings inside a
   flex container the way they do between block-level siblings — a real, measured difference, harmless
   here (nothing legible is lost, nothing newly clips), but a difference, not "no change in shape". */
.scr-vote, .scr-result { display: flex; flex-direction: column; min-height: 0; max-height: 100%; }

/* rows shared by liveCounts / winner / elected / paper / counts. `--rs` (row scale, 0..1) is a #1961
   escape hatch: unset (falls back to 1) everywhere except the election result, where `FitRows`
   (web/src/surfaces/screen/FitText.tsx) measures the natural row-block height against the space flex
   layout actually left it and dials `--rs` down until every row fits whole — a projector must never
   draw a partly-cropped row. #1961 round 2 (independent review) — this used to fall back to
   `.scr-rows--floor { overflow-y: auto }` when even the shrink floor couldn't fit; that traded one
   defect for a worse one (a projector has no scroller, and `overflow-y: auto` still doesn't clip AT a
   row boundary, so the row at the fold stayed partly drawn). `FitRows` now drops undroppable-free rows
   from the tail and, failing that, shrinks past the old floor instead — `overflow: hidden` here is a
   guarantee (nothing left to draw past the fold), never a fallback that needs a scrollbar. */
.scr-rows { margin-top: var(--scr-space-4); display: flex; flex-direction: column; gap: calc(var(--scr-space-2) * var(--rs, 1)); flex: 1; min-height: 0; overflow: hidden; }
/* #1961 round 3 (independent review) — this `gap` was the one length inside `.scr-row` that `--rs`
   didn't scale: the grid gap between the name/votes line and the bar, a fixed ~32px per row at
   1920x1080 that no amount of shrinking removed. With enough undroppable (tagged) rows to force `--rs`
   toward `RS_HARD_MIN` (FitText.tsx), that fixed gap alone put a floor under the row height — around
   32px/row — so total height converged toward N × 32px, not toward 0, and a large-enough field (11
   rows: 8 seats + 2 alternates) still clipped past the stage bottom (115.0px @1920x1080), worse than
   the original defect. Scaling this gap is what makes "the shrink always finds a fit" actually true —
   swept the rest of `.scr-row`/`.scr-row-name`/`.scr-row-votes`/`.scr-row-bar`/`.scr-tag` for the same
   class of bug (S6 was the first instance, this is the second): `border-radius: 999px` on the bar and
   tag is the only other fixed length here, and it doesn't add height (it's clamped to the element's
   own — already-scaled — box), so it isn't a floor. */
.scr-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: calc(var(--scr-space-4) * var(--rs, 1)); position: relative; padding: calc(var(--scr-space-2) * var(--rs, 1)) 0; }
.scr-row-name { font-size: calc(var(--t6-size) * var(--scr-u) * var(--rs, 1)); font-weight: 600; }
.scr-row-votes { font-size: calc(var(--t7-size) * var(--scr-u) * var(--rs, 1)); font-weight: 700; font-variant-numeric: tabular-nums; }
.scr-row-neutral .scr-row-votes { color: var(--text); }
.scr-row-bar { grid-column: 1 / -1; height: calc(9 * var(--scr-u) * var(--rs, 1)); background: var(--raised); border-radius: 999px; overflow: hidden; }
.scr-row-bar-fill { height: 100%; background: var(--muted); border-radius: 999px; }
.scr-row-bar-fill--elected { background: var(--primary); }
@media (prefers-reduced-motion: no-preference) { .scr-row-bar-fill { transition: width 0.4s ease; } }
/* #1961 S6 (independent review of PR #2082) — the Elected/Runner-up pill used to stay at a fixed
   `--t2-size`, unscaled by `--rs` (the election-result-only shrink factor below), while `.scr-row-name`
   next to it shrank — at 1920x1080/4 rows the name fell to 34.4px against a static 28px pill, and the
   pill's own fixed height put a hard floor under how far a tagged row could shrink at all. `--rs`
   defaults to 1 everywhere `.scr-tag` is used outside the election result (CountsView's "Winner" tag),
   so this is a no-op there. */
.scr-tag { display: inline-flex; align-items: center; gap: calc(var(--scr-space-2) * var(--rs, 1)); font-size: calc(var(--t2-size) * var(--scr-u) * var(--rs, 1)); font-weight: 700; padding: 0.15em 0.7em; border-radius: 999px; margin-left: calc(var(--scr-space-3) * var(--rs, 1)); }
.scr-tag[data-tag="elected"] { background: var(--primary-tint); color: var(--primary); }
.scr-tag[data-tag="runner-up"] { background: var(--raised); color: var(--text-2); }

/* several open votes */
.scr-votes-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--scr-space-5); align-content: center; }
.scr-vote-card { background: var(--surface); border-radius: var(--radius-lg); padding: var(--scr-space-5); }
.scr-vote-card .scr-q { font-size: calc(var(--t5-size) * var(--scr-u)); }
.scr-vote-card .scr-fig { font-size: calc(var(--t7-size) * var(--scr-u)); }

/* nominations */
/* #2146 — the worst instance of the #1961/#2087 uncapped/unfitted class (~1346px clip @1920x1080, 20
   nominees): this had no cap, no fit mechanism, not even `.scr-rows`' own `overflow: hidden` — a bare
   flex column that simply grew past `.scr-vote`'s bound and clipped silently at the STAGE edge
   (`.scr-stage { overflow: hidden }`), never at its own. Fixed with the SAME `PagedRows` (FitText.tsx)
   `.scr-vote` already gives its other bodies room for (`.scr-vote, .scr-result` above is already the
   bounded flex column PagedRows needs) — `flex: 1; min-height: 0; overflow: hidden` here matches
   `.scr-rows`'s own rule exactly, and the row/seconds font-size plus the row gap now read `var(--rs, 1)`
   too, so PagedRows' comfortable-floor shrink step (before it ever pages) has real effect here instead
   of being a no-op the way an unscaled `.scr-nom-row` would make it. */
.scr-noms { margin-top: var(--scr-space-4); display: flex; flex-direction: column; gap: calc(var(--scr-space-2) * var(--rs, 1)); flex: 1; min-height: 0; overflow: hidden; }
.scr-nom-row { display: flex; justify-content: space-between; align-items: baseline; font-size: calc(var(--t6-size) * var(--scr-u) * var(--rs, 1)); font-weight: 600; }
.scr-nom-seconds { font-size: calc(var(--t4-size) * var(--scr-u) * var(--rs, 1)); font-weight: 400; color: var(--muted); }
.scr-declared { font-size: calc(var(--t6-size) * var(--scr-u)); font-weight: 600; margin-top: var(--scr-space-3); }

/* ── result verdicts ── */
.scr-verdict { display: flex; align-items: center; gap: var(--scr-space-2); margin: var(--scr-space-4) 0; }
.scr-verdict svg { width: calc(var(--t8-size) * 0.55 * var(--scr-u)); height: calc(var(--t8-size) * 0.55 * var(--scr-u)); flex: none; }
.scr-verdict-word { font-size: calc(var(--t8-size) * var(--scr-u)); line-height: calc(var(--t8-line) * var(--scr-u)); font-weight: 700; font-variant-numeric: tabular-nums; }
.scr-verdict-name.scr-verdict-word { font-variant-numeric: normal; }
.scr-verdict-plain { font-size: calc(var(--t7-size) * var(--scr-u)); font-weight: 600; color: var(--text-2); margin: var(--scr-space-4) 0; }
.scr-tone-primary { color: var(--primary); }
.scr-tone-danger { color: var(--danger); }
.scr-tone-text2 { color: var(--text-2); }
.scr-tone-success { color: var(--success); }
.scr-tone-warn { color: var(--warning); }
.scr-tone-over { color: var(--danger); }
.scr-tone-normal { color: var(--text); }
.scr-score { font-size: calc(var(--t7-size) * var(--scr-u)); font-weight: 700; font-variant-numeric: tabular-nums; margin-bottom: var(--scr-space-2); }
.scr-score-dash { color: var(--muted); font-weight: 400; padding: 0 0.3em; }

/* ── Now ── */
.scr-eyebrow + .now-title, .scr-now-title { font-weight: 800; line-height: 1.02; letter-spacing: -0.02em; overflow-wrap: break-word; margin: 0; }

/* ── holding card ── */
.scr-hold { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: var(--scr-space-4); height: 100%; }
.scr-hold-seal { width: calc(96 * var(--scr-u)); height: calc(96 * var(--scr-u)); object-fit: contain; background: var(--seal-ground); border-radius: var(--radius-md); }
.scr-hold-name { font-size: calc(var(--t7-size) * var(--scr-u)); line-height: calc(var(--t7-line) * var(--scr-u)); font-weight: 600; }
.scr-hold-sub { font-size: calc(var(--t5-size) * var(--scr-u)); color: var(--text-2); }
.scr-staff-line { font-size: calc(var(--t2-size) * var(--scr-u)); color: var(--muted); margin-top: var(--scr-space-3); }
.scr-fallback { display: flex; align-items: center; justify-content: center; }

/* ── recess / deck / boards (current data, new styling) ── */
.recess-solo { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--scr-space-6); text-align: center; }
.recess-solo .ss-count { color: var(--warning); }
.scr-recess-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: var(--scr-space-3); }
.scr-recess-wrap .deck { flex: 1; min-height: 0; }
.scr-recess-caption { flex: none; text-align: center; padding: var(--scr-space-3) var(--scr-space-5); font-size: calc(var(--t5-size) * var(--scr-u)); font-weight: 700; color: var(--warning); background: var(--surface); border-radius: var(--radius-md); }
.deck { position: relative; width: 100%; height: 100%; }
.ss-slide { position: absolute; inset: 0; opacity: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: var(--scr-space-5); padding: var(--scr-space-3) var(--scr-space-5); }
@media (prefers-reduced-motion: no-preference) { .ss-slide { transition: opacity 0.8s ease; } }
@media (prefers-reduced-motion: reduce) { .ss-slide { transition-duration: 0s; } }
.ss-slide.active { opacity: 1; }
.ss-img { max-width: 94%; max-height: 82%; object-fit: contain; }
.ss-t { font-size: calc(var(--t7-size) * var(--scr-u)); font-weight: 700; line-height: 1.05; }
.ss-s { font-size: calc(var(--t4-size) * var(--scr-u)); color: var(--muted); }
.ss-count { font-size: calc(var(--t8-size) * var(--scr-u)); font-weight: 700; font-variant-numeric: tabular-nums; color: var(--primary); }

/* public comment / debate */
.scr-speaker-name { font-size: calc(var(--t7-size) * var(--scr-u)); font-weight: 700; line-height: 1.05; margin: var(--scr-space-2) 0; }
.scr-speaker-clock { line-height: 1; font-size: min(calc(2 * var(--t8-size) * var(--scr-u)), 40vh); font-weight: 700; font-variant-numeric: tabular-nums; margin: var(--scr-space-3) 0; }
.scr-stance { font-weight: 700; }
.scr-upnext-label { margin-top: var(--scr-space-5); }
.scr-upnext { display: flex; flex-direction: column; gap: var(--scr-space-2); font-size: calc(var(--t4-size) * var(--scr-u)); }
.scr-signup-title { font-size: calc(var(--t6-size) * var(--scr-u)); font-weight: 700; margin: var(--scr-space-2) 0; }
.scr-qr { background: var(--paper); padding: var(--scr-space-4); border-radius: var(--radius-md); display: inline-block; width: clamp(140px, 24vh, 340px); height: clamp(140px, 24vh, 340px); }

/* boards */
.scr-board-stats { display: flex; gap: var(--scr-space-6); margin: var(--scr-space-3) 0 var(--scr-space-5); flex-wrap: wrap; }
.scr-board-stat { min-width: 0; }
.scr-board-num { font-size: calc(var(--t7-size) * var(--scr-u)); font-weight: 700; font-variant-numeric: tabular-nums; }
.scr-board-label { font-size: calc(var(--t2-size) * var(--scr-u)); color: var(--muted); }
.scr-board-list { max-height: 52vh; overflow: auto; display: flex; flex-direction: column; }
.scr-board-row { display: flex; justify-content: space-between; align-items: center; gap: var(--scr-space-4); padding: var(--scr-space-2) 0; border-bottom: 1px solid var(--border); font-size: calc(var(--t4-size) * var(--scr-u)); }
.scr-board-contest { margin-bottom: var(--scr-space-5); }
.scr-recount { display: inline-flex; align-items: center; gap: var(--scr-space-2); color: var(--muted); }

/* ── picker ── */
.scr-picker .scr-stage { justify-content: flex-start; overflow: auto; }
.scr-picks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--scr-space-3); max-width: 720px; }
.scr-pick { display: flex; align-items: center; justify-content: space-between; gap: var(--scr-space-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: var(--scr-space-4) var(--scr-space-5); }
.scr-pick-name { font-size: calc(var(--t5-size) * var(--scr-u)); font-weight: 600; }
.scr-pick-date { font-size: calc(var(--t2-size) * var(--scr-u)); color: var(--muted); }
.scr-pick-btn { min-height: 44px; display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }

/* ── #1418 constraint 4/8: legible at room distance, works on any display ── */
@media (max-width: 480px) {
  .scr-body { grid-template-columns: 1fr; }
  .scr-rail-wrap { display: none; }
}

/* ── #1422 — org home + settings: the org rail layout (tokens only; no raw colours) ─────────────────── */
/* Visually hidden but readable by assistive tech and still focusable (the logo file input, "Done:",
   the drawer's dialog title, the stacked table header). */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.skip-link { position: absolute; left: 8px; top: 8px; z-index: 1003; display: inline-flex; align-items: center; min-height: 44px; padding: 10px 16px;
  background: var(--surface); color: var(--text); border: 2px solid var(--primary); border-radius: var(--radius-md); font-weight: 600; text-decoration: none;
  transform: translateY(-200%); }
.skip-link:focus { transform: none; }
.skip-link:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
/* <1024px (base): single column, no inline rail. ≥1024px: rail | content (same query as JS's WIDE_QUERY,
   web/src/lib/useMediaQuery.ts) — not padded for the banners there either, body already has padding-top:
   var(--rv-banner-h). Matching the JS breakpoint exactly keeps a fractional width (browser zoom) from
   landing CSS and JS on different sides of 1024px. */
.org-layout { display: grid; grid-template-columns: minmax(0, 1fr); min-height: calc(100dvh - var(--rv-banner-h, 0px)); }
.org-rail { position: sticky; top: var(--rv-banner-h, 0px); align-self: start; height: calc(100dvh - var(--rv-banner-h, 0px)); overflow-y: auto;
  background: var(--surface); border-right: 1px solid var(--border); padding: 16px 12px 24px; }
.org-rail-head { display: flex; align-items: flex-start; gap: 10px; padding: 4px 6px 12px; }
.seal-tile { flex: 0 0 auto; width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
  background: var(--seal-ground); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; }
.seal-tile img { max-width: 26px; max-height: 26px; object-fit: contain; }
/* #1639 — no logo: the RosterVote app mark (itself an opaque teal rounded square) stands alone. No white
   ground, border or padding; .seal-tile's own overflow:hidden clips it to the same 32px box and radius, so it
   reads as the app icon rather than a logo sitting on a tile. */
.seal-tile.is-default { background: none; border: 0; }
.seal-tile.is-default img { width: 100%; height: 100%; max-width: none; max-height: none; object-fit: cover; }
.org-rail-name { font-size: 1rem; line-height: 1.375rem; font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.org-rail-list { list-style: none; margin: 0; padding: 0; }
.org-rail-group { margin-top: 16px; }
.org-rail-label { font: var(--t1); letter-spacing: var(--t1-tracking); text-transform: uppercase; color: var(--muted); padding: 0 10px 6px; }
.org-rail-item { display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 10px; border-radius: var(--radius-md);
  color: var(--text); text-decoration: none; font-size: 1rem; line-height: 1.375rem; font-weight: 500; }
.org-rail-item svg { flex: 0 0 auto; color: var(--text-2); }
.org-rail-item:hover { background: var(--raised); }
.org-rail-item[aria-current="page"] { background: var(--primary-tint); }
.org-rail-item[aria-current="page"] svg { color: var(--primary); }
.org-rail-item:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.org-col { min-width: 0; display: flex; flex-direction: column; }
.topbar.rv-topbar-slim { min-height: 56px; }
.org-main { width: 100%; max-width: 1040px; padding: 16px; }
.org-main:focus { outline: none; }
.org-main > h1 { font: var(--t6); margin: 0 0 16px; color: var(--text); }
/* #1634 — Records' h1 shares its row with the #mode (editor/read-only) pill; the rail-mode topbar has
   no `pills` slot (unlike the plain AppShell), so it moves into the main column instead. */
.org-main > .org-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 16px; }
.org-main > .org-head > h1 { font: var(--t6); margin: 0; color: var(--text); }
/* #1634 — the Follow pages' meeting picker moved here from the topbar's brand-row: keep its topbar width
   cap (the global `select { width: 100% }` would otherwise stretch it across the column), never wider
   than the column on a phone. */
.org-main > .org-head > select { width: auto; min-width: 0; max-width: min(360px, 100%); }
/* #1634 fix round — Records' sub-tab row (Overview/Districting/Candidacies/…) used to reuse
   .tabbar, the MOBILE voter shell's bottom-nav class (position: fixed; bottom: 0; …).
   That pinned it to the viewport bottom over whatever page content happened to be there — a
   pre-existing bug the rail's narrower main column made worse (it newly reached the Districting
   tab's Import button). This is an ordinary in-flow row of tab buttons; it gets its own class
   with no fixed-position inheritance. (#1726 — .tabbar itself was later confirmed to have no
   remaining consumer anywhere in the app and was deleted; this class stays deliberately
   separate and generic-free so nothing can repeat that mistake by reaching for it.) */
.records-tabbar { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
@media (min-width: 1024px) { .org-layout { grid-template-columns: 232px minmax(0, 1fr); } .org-main { padding: 24px 28px; } }
/* #1634 — printing a railed page (Meeting summary and Minutes are print documents): the rail and skip link
   never print, and the layout drops its grid. Otherwise a print page at least 1024px wide keeps the
   232px rail column and, with the rail hidden, auto-places the whole page into it. */
@media print { .org-layout { display: block; min-height: 0; } .org-rail, .skip-link { display: none; } }
/* Home's single foot element when there is no meetings card (#1422 §3, I5 cases 1–2). */
.home-fallback p { margin: 0; font-size: 1rem; color: var(--text); }
.home-fallback #launcher-note { margin-top: 8px; font: var(--t2); color: var(--muted); }   /* #1426 F2 */
/* #1422 — below 1024px: Menu button + seal + org name in the slim topbar; the rail lives in a left drawer. */
/* flex: none — the top bar is a flex row, so a long org name would otherwise shrink the button below the
   44×44 touch target (#1639: it measured 26×44 at 390px with a long name). */
button.rv-menu-btn { width: 44px; height: 44px; flex: none; padding: 0; margin: 0; display: inline-flex; align-items: center; justify-content: center; color: var(--text); }
.rv-topbar-org { min-width: 0; font-size: 1rem; font-weight: 600; color: var(--text); overflow-wrap: anywhere; }
.rv-drawer { position: fixed; top: 0; bottom: 0; left: 0; z-index: 1002; width: min(300px, 85vw); overflow-y: auto;
  background: var(--surface); border-right: 1px solid var(--border); animation: rv-drawer-in .15s ease-out; }
.rv-drawer .org-rail { position: static; height: auto; border-right: 0; padding-top: 0; }   /* the drawer itself scrolls */
.rv-drawer-hd { display: flex; justify-content: flex-end; padding: 8px 8px 0; }
button.rv-drawer-x { width: 44px; height: 44px; padding: 0; margin: 0; display: inline-flex; align-items: center; justify-content: center; }
button.rv-menu-btn:focus-visible, button.rv-drawer-x:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
@keyframes rv-drawer-in { from { transform: translateX(-100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) { .rv-drawer { animation: none; } }

/* #1422 §4 — Getting started as tiles: the next step on --primary / --primary-tint; done = circle-check in
   --success (never struck through); to do = circle in --muted. One column below 720px. */
.setup-card.is-tiles { max-width: none; border-left: 1px solid var(--border); }
.gs-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.gs-title { font: var(--t4); margin: 0; color: var(--text); }
.gs-count { font-size: 1rem; font-weight: 400; color: var(--muted); margin-left: 8px; }
.gs-lead { margin: 4px 0 0; font-size: 1rem; color: var(--text-2); }
button.gs-x, button.gs-show { min-width: 44px; min-height: 44px; margin: 0; display: inline-flex; align-items: center; justify-content: center; }
button.gs-show { padding: 8px 16px; font-size: 1rem; color: var(--text); }
.setup-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 12px; margin-top: 12px; }
@media (max-width: 719px) { .setup-tiles { grid-template-columns: minmax(0, 1fr); } }
.setup-tiles .setup-step { flex-direction: column; align-items: flex-start; gap: 6px; min-height: 44px; padding: 12px;
  border: 1px solid var(--border); border-radius: var(--radius-md); }
.setup-tiles .setup-step:hover { background: var(--raised); }
.setup-tiles .setup-step[aria-current="step"] { border-color: var(--primary); background: var(--primary-tint); }
.setup-tiles .setup-step:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.setup-ic { display: inline-flex; }
.setup-ic-done { color: var(--success); }
.setup-ic-todo { color: var(--muted); }
.setup-step-t { font-size: 1rem; font-weight: 600; color: var(--text); }
.setup-step-d { font-size: 1rem; font-weight: 400; color: var(--text-2); }
.gs-foot { margin: 12px 0 0; }
.setup-card.is-collapsed { border-left: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.gs-line { margin: 0; font-size: 1rem; color: var(--text-2); }
.gs-line b { color: var(--text); }

/* #1422 §5 — Home's meetings table. 14px cells (#1426 compact admin table), tabular numerals, 48px rows,
   12px caps headers. The .list-item card look is reset inside the table (the class stays as an e2e hook). */
.meet-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.meet-head h2 { margin: 0; font: var(--t4); color: var(--text); }
button.meet-new { min-height: 44px; margin: 0; display: inline-flex; align-items: center; gap: 8px; }
.meet-scroll { overflow-x: auto; margin-top: 12px; }
.meet-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; font-variant-numeric: tabular-nums; }
.meet-table th { font: var(--t1); letter-spacing: var(--t1-tracking); text-transform: uppercase; color: var(--muted); }
.meet-table tr.list-item { background: none; border: 0; border-radius: 0; margin: 0; padding: 0; }
.meet-table tbody tr.list-item:hover { background: var(--raised); }
/* #1426 Task 6 review — reverted a `height` -> `min-height` conversion here: measured in Chromium, on
   a <td>, `height: 48px` is always honoured as a genuine minimum (a table cell's `height` never clips
   taller content), while `min-height: 48px` in the SAME table-layout context tracks the cell's own
   content line-box + padding instead — its effective rendered height varies with what's inside, rather
   than acting as a floor. Measured 21px for a bare-text cell, 26px with this table's real row content
   (which nests button.meet-name-btn's own min-height: 44px); both are correct, content-dependent
   readings of the SAME declared-rule defect, not two different bugs. That defect was real at the CSS
   rule level, but it never shipped as a visible regression here specifically: .meet-table renders in
   exactly one place, and every row's first cell already holds that 44px-min-height button, so shipped
   rows measured ~52.5px total either way. Table cells are the one context where `height` was already
   correct; left in px (not rem) since this isn't a font-size and the F1 ratchet doesn't gate it. */
.meet-table td { height: 48px; padding: 4px 10px; vertical-align: middle; color: var(--text); }
button.meet-name-btn { min-height: 44px; margin: 0; padding: 4px 0; background: none; border: 0; border-radius: 0; font: inherit; font-size: 0.875rem;
  font-weight: 600; color: var(--text); text-align: left; cursor: pointer; }
button.meet-name-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.meet-integrity { display: inline-flex; align-items: center; gap: 4px; margin-left: 8px; color: var(--muted); }
.meet-integrity.is-bad { color: var(--danger); }
.meet-status { display: inline-flex; align-items: center; gap: 6px; }
.meet-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.meet-status.is-live .meet-dot { background: var(--success); }
.meet-status.is-practice .meet-dot { background: var(--practice); }
.meet-status.is-archived .meet-dot { background: var(--border-strong); }
.meet-table .chip.practice { margin-left: 8px; }
.meet-stack-only { display: none; }
@media (max-width: 719px) {
  .meet-table, .meet-table tbody, .meet-table tr, .meet-table td { display: block; }
  /* Visually hidden ONLY in the stacked layout (the header row has no place once each row is a block), so this
     is width-scoped and can't be the .sr-only class, which would hide the header at every width too. */
  .meet-table thead { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  .meet-table tr.list-item { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; padding: 8px 0; border-bottom: 1px solid var(--border); }
  .meet-table td { height: auto; padding: 0; border: 0; }
  .meet-table td.meet-name { flex-basis: 100%; }
  .meet-stack-only { display: inline; }
}

/* #1422 §6 — the settings rail: 200px column + sections (24px gap); overrides the shared .ctab-bar only under
   .set-rail (other surfaces' tab bars are untouched). 44px tabs, 16px/500, radius 8; the selected tab is
   --primary-tint + --text with a 3px inset --primary bar (a bottom bar on the phone strip). */
.set-layout { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 24px; align-items: start; }
.set-sections { min-width: 0; }
.ctab-bar.set-rail { flex-direction: column; flex-wrap: nowrap; gap: 4px; margin: 0; }
.ctab-bar.set-rail button { min-height: 44px; margin: 0; padding: 10px 12px; border: 0; border-radius: var(--radius-md); background: transparent;
  color: var(--text-2); font-size: 1rem; font-weight: 500; text-align: left; box-shadow: none; }
.ctab-bar.set-rail button:hover { background: var(--raised); }
.ctab-bar.set-rail button.active { background: var(--primary-tint); color: var(--text); box-shadow: inset 3px 0 0 var(--primary); }
.ctab-bar.set-rail button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
@media (max-width: 719px) {
  .set-layout { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .ctab-bar.set-rail { flex-direction: row; overflow-x: auto; padding: 4px; }
  .ctab-bar.set-rail button { flex: 0 0 auto; white-space: nowrap; }
  .ctab-bar.set-rail button.active { box-shadow: inset 0 -3px 0 var(--primary); }
}

/* #1422 §7 — the logo dropzone: click anywhere (a <label>), keyboard focus rings the zone, drag-over goes
   --primary / --primary-tint; the 72×72 preview sits on --seal-ground (white in both themes), image ≤56px, empty
   state = the image icon in --muted (the muted-on-sealGround pairing). 16px text (#1426). */
.field-label { display: block; margin: 16px 0 6px; font-size: 1rem; font-weight: 600; color: var(--text); }
.logo-drop { display: grid; grid-template-columns: 72px minmax(0, 1fr) auto; align-items: center; gap: 16px; min-height: 104px; padding: 16px; cursor: pointer; border: 1px dashed var(--border-strong); border-radius: var(--radius-lg); background: var(--ground); }
.logo-drop:hover { background: var(--raised); }
.logo-drop.is-over { border-style: solid; border-color: var(--primary); background: var(--primary-tint); }
/* Keyboard focus only (#1639): the file input is visually hidden, so :focus-within also lit the ring after a
   mouse click. :has is already used elsewhere in this file; the @supports fallback keeps a ring (the old
   behaviour) rather than none at all in a browser without :has. */
.logo-drop:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: 2px; }
@supports not selector(:has(*)) { .logo-drop:focus-within { outline: 2px solid var(--primary); outline-offset: 2px; } }
.seal-tile.logo-drop-tile { width: 72px; height: 72px; border-radius: var(--radius-lg); }
.logo-drop-tile img { max-width: 56px; max-height: 56px; object-fit: contain; }
.logo-drop-tile svg { color: var(--muted); }
.logo-drop-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.logo-drop-title { font-size: 1rem; font-weight: 600; color: var(--text); }
.logo-drop-hint { font-size: 1rem; color: var(--text-2); }
.logo-drop-choose { display: inline-flex; align-items: center; gap: 6px; margin-top: 4px; font-size: 1rem; font-weight: 600; color: var(--primary); }
@media (max-width: 719px) { .logo-drop { grid-template-columns: 72px minmax(0, 1fr); } .logo-drop-choose { grid-column: 2; } }
.branding-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.branding-actions button { min-height: 44px; }
.ro-field { min-height: 44px; padding: 10px 12px; font-size: 1rem; color: var(--text); background: var(--raised);
  border: 1px solid var(--border); border-radius: var(--radius-md); overflow-wrap: anywhere; }
.ro-dl { margin: 0; }
.ro-dl dd.ro-field { margin: 0; }
.field-note { margin: 6px 0 0; font-size: 1rem; color: var(--text-2); }

/* #1421/#1750 — SegmentedControl (web/src/lib/ui/SegmentedControl.tsx): a radiogroup, not a row
   of buttons. `flex-wrap: wrap` is the chosen overflow behaviour (spec, explicit ask) — a set
   that doesn't fit the row wraps to a second line rather than clipping or scrolling; scrolling
   would hide options behind an affordance the clerk has to discover mid-meeting, and truncating
   labels turns a fit problem into a legibility one. Selected = filled --primary/--on-primary;
   focus-visible = its own outline, deliberately not the same treatment as selected, so a
   keyboard user can tell "focused" and "chosen" apart when they land on the group via Tab. */
.rv-segmented { display: flex; flex-wrap: wrap; gap: 6px; }
.rv-segmented-option { margin: 0; min-height: 44px; padding: 0 var(--space-4); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); background: var(--surface); color: var(--text); font: inherit; font-weight: 600;
  cursor: pointer; }
.rv-segmented-option:hover { border-color: var(--text-2); }
.rv-segmented-option.is-selected { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.rv-segmented-option:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* #1421/#1751/#1812 — Menu primitive: the item-list dropdown panel + its rows. No box-shadow here
   on purpose: this file's few shadows are all raw colour literals (pre-existing, already on the
   raw-colour allowlist), and this is a brand-new surface which the ratchet holds to zero — a
   border against --surface separates the panel from the page without needing one. Danger items
   reuse the existing `button.danger` class (background/colour already defined above) rather than
   a new one.
   #1900 review, finding 1 (critical) — this panel is now Radix's `DropdownMenu.Content`, portaled
   into a Popper wrapper (`[data-radix-popper-content-wrapper]`) that Radix itself positions and
   sizes via `transform`/`position: fixed`. The panel must stay POSITION-NEUTRAL — no `position`,
   `top` or `right` here — or the wrapper measures the panel's box as 0×0 (an absolutely-positioned
   child contributes nothing to its relative/static parent's size), and Floating UI's shift/flip
   collision handling then runs against an empty box and does nothing. `.rv-menu-wrap`, the old
   hand-rolled `position: relative` anchor this panel used to need, is gone with it — Radix's own
   Trigger/Portal/Popper own anchoring now, and nothing in web/src/lib/ui/Menu.tsx renders that
   class any more. Same reasoning killed the `.meeting-menu-anchor .rv-menu-panel` override
   (formerly here, see Detail.tsx and Menu.tsx's own header comment): a fixed `right:auto; left:0`
   compensated for the old hand-rolled version's ALWAYS-`right:0` anchoring on one specific instance;
   Radix's `avoidCollisions` (Menu.tsx's `side`/`align`/`sideOffset`/`collisionPadding` props) now
   repositions any instance generically, so the one-off override would just be dead weight (and,
   while `position: absolute` from the rule above stuck around, actively wrong: it fought the very
   collision handling it was there to help with). `max-height` caps the panel to whatever room Radix
   actually finds on the constrained axis, scrolling internally rather than overflowing the
   viewport if a long entry list can't fully fit even after flipping. */
.rv-menu-panel { z-index: 20; min-width: 220px; max-width: calc(100vw - 16px);
  max-height: var(--radix-dropdown-menu-content-available-height); overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.rv-menu-item { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 44px;
  margin: 0; padding: 10px 12px; border: none; border-radius: var(--radius-sm); background: transparent;
  color: var(--text); font: inherit; text-align: left; cursor: pointer; }
.rv-menu-item:hover:not(:disabled) { background: var(--surface-2); }
.rv-menu-item:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.rv-menu-sep { height: 1px; margin: 4px 6px; background: var(--border); }

/* ── #1425 — StateView + Skeleton (tokens only) ── */
/* web/src/lib/ui/StateView.tsx: the one empty/loading/error component. `page` centres a whole-page
   state (a gate, a station with nothing to work on); `section` sits inside a panel or list, left-
   aligned with a smaller icon, for "No snapshots yet" under a working page (spec S1). Colour is
   never the only signal — the title text and icon shape carry the tone too. */
.rv-state--page { display: grid; justify-items: center; align-content: center; gap: var(--space-3);
  text-align: center; padding: var(--space-6) var(--space-5); min-height: 40vh; }
/* #1897 — the icon spans an EXPLICIT title + body row pair (grid-row 1 / -1 spans only explicit rows, so with none the body
   auto-placed under the icon and squeezed into its column in a narrow card), and every text child sits in the second column. */
.rv-state--section { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto auto; column-gap: var(--space-4);
  row-gap: var(--space-2); align-items: start; padding: var(--space-4) 0; }
.rv-state--section .rv-state-icon { grid-column: 1; grid-row: 1 / 3; }
.rv-state--section > :not(.rv-state-icon) { grid-column: 2; min-width: 0; }
.rv-state-icon { display: grid; place-items: center; border-radius: 50%; background: var(--raised); color: var(--text-2); }
.rv-state--page .rv-state-icon { width: var(--space-7); height: var(--space-7); }
.rv-state--section .rv-state-icon { width: var(--space-6); height: var(--space-6); }
.rv-state--warn .rv-state-icon { background: var(--warning-tint); color: var(--warning); }
.rv-state--error .rv-state-icon { background: var(--danger-tint); color: var(--danger); }
.rv-state-title { margin: 0; font: var(--t5); }
.rv-state--section .rv-state-title { font: var(--t4); }
.rv-state-body { margin: 0; font: var(--t3); color: var(--text-2); }
.rv-state--page .rv-state-body { max-width: 36ch; }
.rv-state-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-1); }
/* #1972 — the icon/label gap belongs on the SHARED `.rv-state-action` base, not only `.secondary`.
   A primary action rendered through BusyButton (e.g. minutesEmptyState's "Generate draft") is a
   `<button class="primary rv-state-action">`; `button.primary` (above) sets only background/color,
   so with the gap living solely on `.secondary` the primary button never got flex/gap at all and its
   icon ran into the label. `a.primary` (#1455, above) ALSO carries its own inline-flex+gap, but at a
   different, hand-picked value (6px) — #2100 found that `.rv-state-action`'s gap here can never
   override it on an `<a class="primary rv-state-action">`, no matter the source order: `a.primary`
   (a type selector + a class, specificity 0-1-1) is MORE specific than the bare `.rv-state-action`
   class (0-1-0), so the anchor form was stuck at 6px while the button form (no competing `gap` on
   `button.primary`) got this rule's 8px, and mobile's StateView.tsx Pressable had drifted to a third
   value again (12px, itself an accidental off-by-one against the brand `space` scale — see tokens.ts's
   `spaceVar`). `.rv-state-action.primary` below beats `a.primary` on specificity (0-2-0) and applies
   to both tags, so all three forms now agree on `var(--space-2)`. */
.rv-state-action { display: inline-flex; align-items: center; gap: var(--space-2); min-height: 44px; }
.rv-state-action.primary { gap: var(--space-2); }   /* #2100 — overrides a.primary's own 6px gap; see comment above */
.rv-state-action.secondary { padding: 0 var(--space-4); background: transparent; border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); color: var(--text); font: var(--t3); font-weight: 500;
  text-decoration: none; margin: 0; cursor: pointer; }
.rv-state-action:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.rv-state-note { margin: 0; font: var(--t2); color: var(--muted); }

/* Skeleton (web/src/lib/ui/Skeleton.tsx): a placeholder in the shape of the content it stands in
   for. `rows` matches a list row (avatar + two bars); `lines` matches document-page text. The
   shimmer gradient runs ONLY under prefers-reduced-motion: no-preference — see the block below —
   so a reduced-motion viewer gets static bars, with no animation rule to override or disable. */
.rv-skeleton-row { display: grid; grid-template-columns: var(--space-5) 1fr var(--space-8); gap: var(--space-3);
  align-items: center; min-height: 44px; border-bottom: 1px solid var(--border); }
.rv-skeleton-dot { width: var(--space-5); height: var(--space-5); border-radius: 50%; background: var(--raised); }
.rv-skeleton-bar { display: block; height: var(--space-3); border-radius: var(--radius-sm); background: var(--raised); }
.rv-skeleton-row:nth-child(4n+1) .rv-skeleton-bar:not(.short) { width: 70%; }
.rv-skeleton-row:nth-child(4n+2) .rv-skeleton-bar:not(.short) { width: 55%; }
.rv-skeleton-row:nth-child(4n+3) .rv-skeleton-bar:not(.short) { width: 80%; }
.rv-skeleton-row:nth-child(4n+4) .rv-skeleton-bar:not(.short) { width: 40%; }
.rv-skeleton-bar.short { width: var(--space-8); }
.rv-skeleton--lines .rv-skeleton-bar { margin-bottom: var(--space-2); }
@media (prefers-reduced-motion: no-preference) {
  .rv-skeleton-bar {
    background: linear-gradient(90deg, var(--raised), color-mix(in srgb, var(--raised) 60%, var(--border-strong)), var(--raised));
    background-size: 200% 100%;
    animation: rv-skeleton-shimmer 1.4s linear infinite;
  }
}
@keyframes rv-skeleton-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* #1454 observer view — the shared Integrity block (web/src/lib/IntegrityBlock.tsx), used by the observer view (seal
   check) and the meeting summary and minutes (leaf-hash check, .integrity--document). Tone edge + disc carry colour;
   the title always carries the meaning too. Roots wrap inside their own element, so nothing scrolls sideways at 390px. */
.integrity { display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: var(--space-3) var(--space-4); align-items: start;
  background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--border-strong);
  border-radius: var(--radius-lg); padding: var(--space-4); margin: 0 0 var(--space-4); min-width: 0; }
.integrity[data-tone="success"] { border-left-color: var(--success); }
.integrity[data-tone="danger"] { border-left-color: var(--danger); }
.integrity-disc { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; background: var(--raised); color: var(--text-2); }
.integrity[data-tone="success"] .integrity-disc { background: var(--success-tint); color: var(--success); }
.integrity[data-tone="danger"] .integrity-disc { background: var(--danger-tint); color: var(--danger); }
.integrity-text { min-width: 0; }
.integrity-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; min-height: 44px; }
.integrity-title { margin: 0; font: var(--t4); color: var(--text); }
.integrity-body { margin: var(--space-1) 0 0; font: var(--t2); color: var(--text-2); max-width: 70ch; }
.integrity-rootline { margin-top: var(--space-3); }
.integrity-root-label { display: block; font: var(--t1); letter-spacing: var(--t1-tracking); text-transform: uppercase; color: var(--muted); }
.integrity-root-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-1); min-width: 0; }
.integrity-root { flex: 1 1 18ch; min-width: 0; font-family: var(--font-mono); font-size: 0.8125rem; line-height: 1.25rem; color: var(--text);
  background: var(--raised); border-radius: var(--radius-sm); padding: var(--space-1) var(--space-2); overflow-wrap: anywhere; word-break: break-all; user-select: all; }
button.integrity-copy { margin: 0; min-height: 44px; min-width: 64px; padding: 0 var(--space-3); background: transparent; color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--radius-md); font: var(--t2); font-weight: 600; }
.integrity-root-missing, .integrity-coverage { margin: var(--space-2) 0 0; font: var(--t2); color: var(--muted); font-variant-numeric: tabular-nums; }
.integrity-steps { margin-top: var(--space-3); }
.integrity-steps > summary { display: flex; align-items: center; gap: var(--space-2); min-height: 44px; cursor: pointer; list-style: none;
  font: var(--t2); font-weight: 600; color: var(--primary); }
.integrity-steps > summary::-webkit-details-marker { display: none; }
.integrity-steps > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--radius-sm); }
.integrity-steps > summary svg { transition: transform .15s ease; }
.integrity-steps[open] > summary svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .integrity-steps > summary svg { transition: none; } }
.integrity-steps ol { margin: 0 0 var(--space-2); padding-left: var(--space-5); font: var(--t2); color: var(--text-2); max-width: 70ch; }
.integrity-steps li + li { margin-top: var(--space-1); }
.integrity-more { font: var(--t2); color: var(--primary); }
.integrity-actions { grid-column: 1 / -1; display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.integrity-actions a.integrity-verify, .integrity-actions button.integrity-download { margin: 0; min-height: 44px; width: 100%;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); font: var(--t3); font-weight: 600; }
.integrity-actions button.integrity-download { background: transparent; color: var(--text); border: 1px solid var(--border-strong); border-radius: var(--radius-md); }
.integrity-actions a.integrity-verify { border-radius: var(--radius-md); padding: 0 var(--space-4); }
.integrity-result { margin: 0; font: var(--t2); color: var(--text-2); max-width: 44ch; }
.integrity-result:empty { display: none; }
@media (min-width: 768px) {
  .integrity { grid-template-columns: 44px minmax(0, 1fr) minmax(200px, 240px); }
  .integrity-actions { grid-column: 3; }
}
@media print {
  .integrity { border-left: 1px solid var(--border); break-inside: avoid; }
  .integrity-disc { filter: grayscale(1); }
  .integrity .no-print { display: none !important; }
}

/* #1454 observer view — the page (web/src/surfaces/observe/App.tsx). A labelled switcher beside the h1, the meeting
   header, the stats line, then Ledger + (Results, Run of show). Two columns only when the page column itself is 960px or
   wider (a container query, so the rail's width is accounted for); otherwise one column with Results first. */
.obs-page { container-type: inline-size; min-width: 0; }
/* #2120 — the shared labelled meeting switcher (web/src/lib/ui/MeetingSwitcher.tsx), renamed from
   this surface-local .obs-switch so it can be dropped into any header, not only Observe's .org-head. */
.rv-meeting-switch { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; max-width: min(360px, 100%); }
.rv-meeting-switch label { font: var(--t1); letter-spacing: var(--t1-tracking); text-transform: uppercase; color: var(--muted); }
.rv-meeting-switch select { width: 100%; min-height: 44px; border: 1px solid var(--border-strong); border-radius: var(--radius-md); }
.obs-meeting { margin: 0 0 var(--space-4); min-width: 0; }
.obs-title { margin: 0; font: var(--t5); color: var(--text); overflow-wrap: anywhere; }
.obs-sub { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-1) 0 0; font: var(--t2); color: var(--text-2); }
.pill.obs-practice { display: inline-flex; align-items: center; gap: var(--space-1); background: var(--practice-tint); color: var(--practice); }
.obs-stats { margin: 0 0 var(--space-4); font: var(--t3); color: var(--text-2); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.obs-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-4); align-items: start; }
.obs-side { display: grid; gap: var(--space-4); min-width: 0; order: -1; }
@container (min-width: 960px) {
  .obs-grid { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); }
  .obs-side { order: 0; }
}
.obs-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); min-width: 0; overflow: hidden; }
.obs-card-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); color: var(--text-2); }
.obs-card-head h2 { margin: 0; font: var(--t4); color: var(--text); }
.obs-card-count { margin-left: auto; font: var(--t2); color: var(--muted); font-variant-numeric: tabular-nums; }
.obs-card > .rv-state { margin: 0 var(--space-4); }
.obs-note { margin: var(--space-1) 0 0; font: var(--t2); color: var(--muted); }

/* Results */
.obs-result { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); min-width: 0; }
.obs-result:last-child { border-bottom: 0; }
.obs-result-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-2); }
.obs-result-title { margin: 0; font: var(--t3); font-weight: 600; color: var(--text); overflow-wrap: anywhere; min-width: 0; }
.pill.obs-status { flex: none; background: var(--raised); color: var(--text-2); }
.obs-result-meta { margin: 2px 0 var(--space-2); font: var(--t2); color: var(--muted); }
.obs-rows { list-style: none; margin: 0; padding: 0; }
.obs-rows li { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: var(--space-1) 0; font: var(--t2); color: var(--text); }
.obs-cand { display: inline-flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); min-width: 0; overflow-wrap: anywhere; }
.obs-num { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
.pill.obs-tag--runnerup { background: var(--raised); color: var(--text-2); }
.obs-progress, .obs-winner { display: flex; align-items: flex-start; gap: var(--space-2); margin: var(--space-1) 0 0; font: var(--t2); color: var(--text-2); }
.obs-winner { color: var(--text); font-weight: 600; }
.obs-progress svg, .obs-winner svg { margin-top: 2px; }

/* Run of show */
.obs-agenda-list { list-style: none; margin: 0; padding: var(--space-2) 0; }
.obs-ag { display: flex; align-items: center; gap: var(--space-2); min-height: 36px; padding: var(--space-1) var(--space-4); border-left: 3px solid transparent; font: var(--t2); color: var(--text); }
.obs-ag[data-level="1"] { padding-left: calc(var(--space-4) * 2); }
.obs-ag[data-level="2"] { padding-left: calc(var(--space-4) * 3); }
.obs-ag[data-level="3"], .obs-ag[data-level="4"] { padding-left: calc(var(--space-4) * 4); }
.obs-ag--current { border-left-color: var(--primary); background: var(--primary-tint); }
.obs-ag--current .obs-ag-mark { color: var(--primary); }
.obs-ag--done { color: var(--text-2); }
.obs-ag--done .obs-ag-mark { color: var(--success); }
.obs-ag-mark { display: inline-flex; width: 16px; flex: none; }
.obs-ag-title { min-width: 0; overflow-wrap: anywhere; }
.obs-ag-kind { font: var(--t1); letter-spacing: var(--t1-tracking); text-transform: uppercase; color: var(--muted); }

/* Ledger table */
.obs-ledger-table { width: 100%; border-collapse: collapse; table-layout: fixed; font: var(--t2); font-variant-numeric: tabular-nums; }
.obs-ledger-table th { text-align: left; font: var(--t1); letter-spacing: var(--t1-tracking); text-transform: uppercase; color: var(--text-2);
  background: var(--raised); padding: var(--space-2) var(--space-3); }
.obs-ledger-table td { padding: var(--space-2) var(--space-3); border-top: 1px solid var(--border); vertical-align: top; color: var(--text); min-width: 0; overflow-wrap: anywhere; }
.obs-ledger-table tr { height: 44px; }
.obs-ledger-table th:nth-child(1) { width: 6.5em; }
.obs-ledger-table:not(.obs-ledger-table--narrow) th:nth-child(4) { width: 40%; }   /* a full 26-character ULID fits one line at 1280 */
.obs-time { color: var(--text-2); white-space: nowrap; }
.obs-event { display: inline-flex; align-items: flex-start; gap: var(--space-2); }
.obs-event svg { color: var(--muted); margin-top: 2px; }
.obs-item { color: var(--text-2); }
.obs-leaf { font-family: var(--font-mono); font-size: 0.75rem; line-height: 1.125rem; color: var(--text-2); overflow-wrap: anywhere; word-break: break-all; }
.obs-leaf--hidden { display: inline-flex; align-items: flex-start; gap: var(--space-1); font-family: var(--font-sans); font-size: 0.8125rem; color: var(--muted); word-break: normal; }
.obs-entry { display: flex; flex-direction: column; gap: 2px; }
.obs-card-foot { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2) var(--space-3); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--border); }
.obs-card-foot .obs-note { flex-basis: 100%; margin: 0; }
.obs-shown { margin: 0; font: var(--t2); color: var(--muted); font-variant-numeric: tabular-nums; }
button.obs-showall { margin: 0 0 0 auto; min-height: 44px; background: transparent; color: var(--text); border: 1px solid var(--border-strong); border-radius: var(--radius-md); font: var(--t2); font-weight: 600; }

/* #1454 — Summary and Minutes: the meeting name is an h2 (the page name is the one h1) at the old h1's visual size, and the
   shared Integrity block sits where the hand-rendered panel was. */
h2.doc-title { font-size: 1.375rem; font-weight: 600; margin: 0 0 4px; color: var(--text); overflow-wrap: anywhere; }
.doc-integrity { margin-top: 12px; }
.doc-integrity .integrity { margin: 0; }

/* ────────────────────────────────────────────────────────────────────────────────────────────────
   #2078 (#1444 sweep) — the users, support and media surfaces pick up the #1409 layout rules.
   Everything here is tokens only: the raw-colour ratchet (npm run test:raw-colours) counts literals
   per file, and this block is exactly the kind of code it exists to keep clean.
   ──────────────────────────────────────────────────────────────────────────────────────────────── */

/* Support: the warning-toned chip that replaces an inline raw-colour badge on the "via support" tag.
   (Spelling the old literal's function name here would itself be counted by the ratchet.)
   Mirrors .pill.good / .pill.bad above, which already pair a tint with its status colour. */
.pill.warn { background: var(--warning-tint); color: var(--warn); }

/* Support: the access state, said ONCE — a state chip followed by one sentence on the same line. */
.access-state { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2);
  margin: 0 0 var(--space-4); color: var(--text-2); }

/* Users: the head of the People column — its title and the page's one primary. */
.people-head { gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-3); }
/* A person's roles are secondary TEXT, not chips: #1409 reserves chips for state, and a green
   `.pill.open` (the "voting is open" pill) read as an approval badge on every role. */
.people-row { padding: var(--space-3); }
.people-who { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); min-width: 0; }
.people-roles { flex-basis: 100%; }
/* At phone width the row's name/email wraps freely, but the popover trigger must not break across
   two lines — it is the row's one control and reads as a label, not a sentence. */
.people-row [aria-haspopup="menu"] { white-space: nowrap; flex: none; }

/* A settings row: what the setting is (and what it means) on the left, its control on the right.
   Used for the org-wide two-factor policy, which is a setting about the list, not a row in it. */
.settings-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
  gap: var(--space-2) var(--space-4); margin-top: var(--space-4); padding-top: var(--space-4);
  border-top: 1px solid var(--border); }
.settings-row-label { min-width: 0; }
.settings-row-label label { display: inline-flex; align-items: center; gap: var(--space-1);
  font-weight: 600; color: var(--text); }
.settings-row-label p { margin: 2px 0 0; }
.settings-row-control { width: auto; flex: none; }

/* Media: ONE line of tabular figures. The bar is a detail that only earns its space past half the
   cap (web/src/surfaces/media/usage.ts decides), so below that there is nothing but the line. */
.media-usage { margin-bottom: var(--space-4); }
.media-usage-line { margin: 0; color: var(--text-2); }
.media-usage-figure { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--text); }
.media-usage-line[data-usage-level="warn"] .media-usage-figure { color: var(--warn); }
.media-usage-line[data-usage-level="danger"] .media-usage-figure { color: var(--bad); }
.media-usage-bar { height: 6px; margin-top: var(--space-2); background: var(--raised);
  border-radius: var(--radius-sm); overflow: hidden; }
.media-usage-fill { height: 100%; background: var(--accent); }
.media-usage-fill.warn { background: var(--warn); }
.media-usage-fill.danger { background: var(--bad); }
.media-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-3);
  margin: var(--space-3) 0 0; }
.media-upload-link { font-weight: 600; }

/* Media cards: ONE radius (--radius-md) for the card and the thumbnail it contains — the old card
   mixed 10px with a 6px image and a 999px meter. */
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-3); }
.media-card { border: 1px solid var(--border); border-radius: var(--radius-md); padding: var(--space-2);
  background: var(--raised); }
.media-thumb { width: 100%; height: 96px; object-fit: contain; border-radius: var(--radius-md);
  background: var(--raised); display: block; }
/* Middle-truncated in JS (usage.ts) so the extension survives; this keeps the cell to one line
   even when a name has no break opportunity at all. */
/* `text-overflow` as well as `overflow: hidden`: the JS truncation caps the CHARACTER count, but a
   24-character name of wide glyphs can still exceed the cell, and clipping mid-glyph reads as a
   rendering fault rather than as elision. */
/* #1426 F1 — exact px->rem unit conversion (13px/11px unchanged), not a resize: these two rules
   landed on main (#2080, the media-surface sweep) after this branch's rem codemod, so they were
   never converted. Keeping them in rem (not snapping to the F2 type scale here, which is a separate
   design call for #2080's own surface) is the minimal fix to keep PX_FREE_FILES honest. */
.media-name { margin-top: var(--space-2); font-size: 0.8125rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.media-meta { font-size: 0.6875rem; }
.media-unused { color: var(--muted); }
.media-delete { margin-top: var(--space-2); }

/* ────────────────────────────────────────────────────────────────────────────────────────────────
   #2118 (#1444 sweep) — the onboarding kit surface: one card style instead of five, and pills used
   as metadata become eyebrow text (#1409 reserves chips for STATE; this page has none). Moved here
   from web/onboarding-kit.html's own <style> block — every other #1444-swept surface's screen CSS
   already lives in this file, not a per-page <style> tag (see the #2078 block above). Tokens only
   (npm run test:raw-colours); no literal px font sizes (npm run test:type-ratchet's PX_FREE_FILES).
   ──────────────────────────────────────────────────────────────────────────────────────────────── */

/* #2118 PR review (finding 2) — an earlier version of this rule was a GENERIC, unscoped `.eyebrow`,
   promoted from the one that already existed scoped to `.ci-pane` (line ~507). That leaked: nine
   hand-authored public/*.html pages (landing, conventions, verification, demo, contact, privacy,
   timer, get-help, compare-electionbuddy — 17 elements) already use `class="eyebrow"` for their own
   unrelated copy pattern, none of them sets `line-height`, and this rule's `font` shorthand supplies
   one (`var(--t1)` resolves to a 1rem line box, sixteen device pixels) — overriding their previous
   eighteen even though specificity/declaration-identity meant `.ci-pane .eyebrow` (checkin) was never
   at risk. (Spelled out in words, not digits+px, here on purpose — this file is one of
   type-ratchet.ts's PX_FREE_FILES, and its F1 check scans this comment text too, not just live
   declarations; an earlier draft of this very sentence tripped it.)
   Scoped to this surface's own class instead of fixed globally, since nothing here needs `.eyebrow`
   to be reusable yet and a page-specific name can't collide with markup this PR never looked at. */
.ob-eyebrow { text-transform: uppercase; letter-spacing: .06em; font: var(--t1); color: var(--text-2); }

/* Two-column top: Getting started (steps) beside Guides (links) — one .panel either side, not a
   full-width panel stacked over another full-width panel. Stacks below the .ho-grid breakpoint. */
.ob-top { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-4); align-items: start; margin-top: var(--space-4); }
@media (max-width: 720px) { .ob-top { grid-template-columns: 1fr; } }
/* #2118 PR review (finding 1) — `.panel`'s own `margin-top: 14px` does not collapse on a grid item,
   so both of a two-item grid row (here) and every row after the first (.ho-grid below) gained an
   extra 14px nobody asked for, on top of the grid's own `gap`. Same trap and same fix as the
   established `.elev-page #units > .panel { margin-top: 0; }` (line ~894) — the grid's `gap` alone
   owns the spacing between/above `.panel` cards laid out in a grid. */
.ob-top > .panel { margin-top: 0; }
.ob-guides { margin: var(--space-2) 0 0; padding-left: 1.1em; }
.ob-guides li { margin: var(--space-1) 0; }

/* Emails to send: each email is a divided ROW inside the one outer .panel (Users' `.people-row`
   pattern, #2078) rather than a bordered `.mail-card` — #1409's "flat surfaces, not boxes in
   boxes". The "From" column reads as eyebrow text next to the subject instead of a `.pill` badge. */
.email-row { border-top: 1px solid var(--border); padding: var(--space-3) 0; margin: 0; }
.email-row:first-of-type { border-top: none; padding-top: var(--space-2); }
.email-row > summary { cursor: pointer; list-style: none; display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.email-row > summary::-webkit-details-marker { display: none; }
.email-row > summary::before { content: "▸"; color: var(--muted); }
.email-row[open] > summary::before { content: "▾"; }
.email-note { margin: var(--space-2) 0 0; }
.email-actions { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); flex-wrap: wrap; }
.email-from { flex: none; }

/* Print & post: a grid of handouts, each the SAME .panel card the rest of the page uses (HandoutCard
   keeps a second `.ho-card` class only as a stable e2e selector — see App.tsx's comment). */
.ho-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--space-4); margin-top: var(--space-3); }
@media (max-width: 720px) { .ho-grid { grid-template-columns: 1fr; } }
/* #2118 PR review (finding 1) — see .ob-top > .panel above: without this, every handout past the
   first row carried .panel's 14px margin-top ON TOP OF the grid's own 16px gap (a measured 14px →
   30px row gap, asymmetric against the 16px column gap). The grid's gap alone owns the spacing. */
.ho-grid > .panel { margin-top: 0; }
