/* Ancora — Graphite v2. Dark, cool, one teal accent. */
:root {
  /* ===================================================================
     GRAPHITE v2 — docs/redesign-log.md, "Leadflow Redesign" (Claude
     Design, 2026-08-17). Every value below is the mock's, measured, or a
     deliberate correction to it that the log records by name.

     Cool near-black neutrals with a faint blue cast (the previous
     Graphite was hue-less), one teal accent, amber for attention, coral
     for negative. IBM Plex Sans / Mono, named FIRST in each stack and
     falling back to the system faces: the CSP is font-src 'self' and
     nothing may fetch a font from Google, so the design renders as drawn
     wherever Plex is installed and degrades to the same metrics-class
     system face everywhere else. Self-hosting Plex is a separate
     decision (a fonts/ dir + @font-face), not one taken here.

     EVERY RATIO IN THIS FILE WAS MEASURED (WCAG relative luminance) when
     the palette moved. The previous 61-pair table is void; the new one is
     docs/b11-design-system.md §7, REGENERATED from this block by
     tools/contrast_table.py (the test compares them — change a colour,
     re-run the script, paste). Four of the mock's own values failed and
     were corrected — see --text-label, --text-dim, --border and (this
     block's follow-up) --border-input.
     =================================================================== */

  /* ---- neutral ramp (mock: #0e0f11 / #121417 / #16181b / #23272c) --- */
  --bg: #0e0f11;                      /* page canvas */
  --nav-bg: #121417;                  /* nav rail, aside panels, docked reply */
  --panel: #16181b;                   /* card, table, input surface */
  --surface-raised: #23272c;          /* active nav row, chips, secondary btn */
  --surface-hover: #1c1f24;           /* between panel and raised; hover tint */

  /* Recessed, NOT raised: a locked queue row is unavailable, and reading
     as darker-than-the-card says that where a lighter tint would read as
     hovered. Same reasoning as v1; the value is the canvas. */
  --surface-disabled: #0e0f11;

  /* The one accent-tinted surface: an unread notification row must be
     distinguishable from a HOVERED unread row. Teal at 8% on the panel. */
  --surface-unread: #1a2a2b;

  /* MUST STAY PURE WHITE, in every palette, forever. Quiet zone behind
     the enrollment QR; scanners need maximum contrast. NOT --panel so a
     themed panel cannot drag it along; tests/test_typography.py asserts
     the value. */
  --surface-scan: #ffffff;

  /* ---- borders ------------------------------------------------------
     The mock draws hairlines at rgba(255,255,255,.06-.08), which
     composite to 1.14-1.28:1 — present in a browser at 1180px, invisible
     on a dim laptop panel. Decorative hairlines are WCAG-exempt, but v1
     raised its own for exactly this reason. Shipped one notch stronger
     than drawn: .10 for hairlines (1.28-1.37), .14 for control edges and
     dividers (1.47-1.56). Controls that are IDENTIFIED ONLY by their
     border (inputs) get --border-input, a solid grey that clears the 3:1
     floor on all four surfaces. The mock's #5a6068 did NOT: it measures
     3.02 / 2.91 / 2.80 / 2.37 (the redesign log's first row for it was
     itself mis-measured at 2.94 on the panel). Lifted 18 units on the
     same cast so every surface an input can sit on clears with margin. */
  --border: rgba(255, 255, 255, 0.10);         /* decorative hairline */
  --border-strong: rgba(255, 255, 255, 0.14);  /* dividers, secondary btn */
  --border-input: #6c727a;                     /* 3.95 / 3.80 / 3.66 / 3.09 */

  /* ---- text ---------------------------------------------------------
     Measured on the four surfaces, worst case last (bg / nav / panel /
     raised):
       --text   15.91 / 15.31 / 14.76 / 12.46
       --text-2 11.99 / 11.54 / 11.12 /  9.39
       --muted   7.35 /  7.07 /  6.81 /  5.75
       --text-dim 5.14 / 4.94 / 4.77 / 4.02   ← LARGE/MONO ONLY, see below
     All body ink clears AA on every surface. */
  --text: #e8eaed;
  --text-2: #c8cdd3;                  /* long-form and quoted copy */
  --muted: #9aa1a9;

  /* THE SMALL-CAPS EYEBROW INK. The mock uses rgba(232,234,237,.42) for
     its 11px mono uppercase labels; that composites to #6e7073 on the
     panel and measures 3.58:1 — below AA for text that small. v1 hit the
     identical finding and lifted its label ink. Same fix: eyebrows take
     --muted (6.81 on the panel) and keep the mock's mono/tracked/upper
     treatment, which is what actually carries the look. */
  --text-label: #9aa1a9;

  /* Timestamps in the mono gutter, the × on a chip, "—" placeholders:
     the mock's #7e858d. It is 4.02 on the raised surface, so it is
     NEVER body copy — 12px+ mono figures and glyphs only, where the
     large-text/graphic allowance applies. Body copy stays on --muted. */
  --text-dim: #7e858d;

  /* Ink ON a saturated fill. The canvas colour, not "black": 10.50 on
     the accent, 8.80 on amber, 5.87 on coral. Every solid pill and the
     primary button use it. */
  --text-inverse: #0e0f11;

  /* ---- brand and intent (mock: teal / amber / coral) --------------- */
  --accent: #6fd0c4;                  /* 10.50 / 10.10 / 9.74 / 8.22 as text */
  --accent-hover: #8fe0d6;
  --accent-border: rgba(111, 208, 196, 0.30);
  --danger: #d9705f;                  /* 5.87 / 5.65 / 5.45 / 4.60 */
  --danger-hover: #e58a7a;
  --danger-border: rgba(217, 112, 95, 0.35);
  --danger-tint: rgba(217, 112, 95, 0.04);
  --success: #6fd0c4;                 /* the mock has no separate green; teal IS positive */
  --warn: #e0a45c;                    /* 8.80 / 8.46 / 8.16 / 6.89 */
  --warn-bg: rgba(224, 164, 92, 0.07);
  --warn-border: rgba(224, 164, 92, 0.30);
  --warn-tint: rgba(224, 164, 92, 0.05);      /* a whole row, faintly */

  /* ---- inline feedback (tint at .14 on the panel, hue as text) ------
     Measured hue-on-its-own-tint: positive 7.31, attention 6.33,
     negative 4.56. */
  --feedback-positive-bg: rgba(111, 208, 196, 0.14);
  --feedback-positive-border: rgba(111, 208, 196, 0.35);
  --feedback-positive-text: #6fd0c4;
  --feedback-negative-bg: rgba(217, 112, 95, 0.14);
  --feedback-attention-bg: rgba(224, 164, 92, 0.14);
  --note-positive-text: #6fd0c4;
  --note-attention-text: #e0a45c;
  --hint-underline: #7e858d;

  /* ---- status palette -----------------------------------------------
     The mock has THREE hues and uses tint-of-hue + hue-as-text for every
     status: teal (positive / info / scheduled), amber (attention /
     recovery / stale), coral (negative), grey (neutral). Nine v1 status
     roles collapse onto four fills, and that is the point — on a page
     where nothing is wrong the only colour is teal. Roles are kept as
     tokens so no template changes; several now alias the same fill. */
  --status-info-bg: rgba(111, 208, 196, 0.14);
  --status-info-text: #6fd0c4;
  --status-info-text-strong: #8fe0d6;
  --status-positive-bg: rgba(111, 208, 196, 0.14);
  --status-positive-text: #6fd0c4;
  --status-scheduled-bg: rgba(111, 208, 196, 0.14);
  --status-scheduled-text: #6fd0c4;
  --status-neutral-bg: rgba(255, 255, 255, 0.06);
  --status-neutral-text: #9aa1a9;
  --status-neutral-text-strong: #c8cdd3;
  --status-negative-bg: rgba(217, 112, 95, 0.14);
  --status-negative-text: #d9705f;
  --status-attention-bg: rgba(224, 164, 92, 0.14);
  --status-attention-text: #e0a45c;
  --status-recovery-bg: rgba(224, 164, 92, 0.14);
  --status-recovery-text: #e0a45c;
  --status-stale-text: #e0a45c;

  /* ---- navigation and focus ---------------------------------------- */
  --nav-text: #9aa1a9;
  --nav-hover-bg: rgba(255, 255, 255, 0.05);
  --nav-active-bg: #23272c;           /* mock: solid raised, not an accent tint */
  --nav-active-text: #e8eaed;
  --nav-user-text: #9aa1a9;
  /* SOLID accent, as v1: a tinted ring composites below the 3:1 floor. */
  --focus-ring: var(--accent-hover);
  --shadow-soft: rgba(0, 0, 0, 0.6);

  /* ---- layout constants (mock: 212px rail, 340px aside) ------------ */
  --nav-width: 212px;
  --aside-width: 340px;
  --control-height: 44px;             /* every button, input, nav row */
  --control-height-sm: 40px;          /* header buttons, chip actions */

  /* ---- radius (mock: 8 default, 9-10 cards, 5-6 pills, 20 capsule) -- */
  --radius: 8px;
  --radius-lg: 10px;                  /* settings cards */
  --radius-sm: 5px;                   /* mono tag pills */
  --radius-pill: 20px;                /* chips, count capsules */

  /* ---- type ---------------------------------------------------------
     The mono stack is PINNED rather than left to the OS: two operators
     work from different machines and the money columns must line up
     identically on both. Consolas and Cascadia Mono sit ahead of the
     generic keyword because the Windows fallback for bare `monospace`
     is Courier New. */
  --font-sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
               Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
               Consolas, "Cascadia Mono", "Liberation Mono", monospace;

  /* ---- type scale ----------------------------------------------------
     BLOCK 4. The reference spec's nine steps, which are six size/leading
     PAIRS crossed with weight and tracking:

       11/16 600 +0.06em   uppercase column headers and eyebrows
       11/16 500 +0.01em   badges and counts
       12/16 400  0        secondary meta
       13/20 400  0        table cell default
       13/20 500  0        row primary
       13/20 600  0        section headers
       14/20 600 -0.005em  screen title, grand total, pane title
       16/24 600 -0.01em   subheads
       28/32 600 -0.02em   the one display figure

     LEADING IS PAIRED TO SIZE AND FIXED IN PX, never unitless. A
     unitless line-height inherits the RATIO, so an 11px badge sitting
     inside 15px body copy used to get 22.5px of leading — a third more
     than its own line needs. Every rule that sets a size below therefore
     sets its leading in the same breath; that is the whole discipline.

     Position names for the pairs, role names for the tracking, matching
     the spacing scale's reasoning: --font-size-4 stays true if the scale
     is retuned, whereas --font-size-14px would become a lie, while
     tracking varies WITHIN a size (11px is +0.06em as an eyebrow and
     +0.01em as a badge) so it cannot be positional.

     LONG-FORM PROSE TAKES STEP 3, NOT STEP 4. The reference spec covers
     a dense table UI and has no prose step at all, so the four blocks
     that carry documents a tenant must READ BEFORE SIGNING — the ToS and
     DPA (.legal-body), the liability attestation (.attest-text), the
     enrollment instructions (.mfa-steps) and Jarvis's written answers
     (.jarvis-answer) — sit at 13/20 rather than 14/20. Same 20px
     leading, one step smaller, which buys a 1.54 ratio instead of 1.43.
     A wall of contract text at 1.43 is the wrong call: it is the one
     place in this app where somebody reads every word, and the leading
     is what makes that survivable. Step 4 stays correct for the short
     copy everywhere else. */
  --font-size-1: 11px;   --line-height-1: 16px;
  --font-size-2: 12px;   --line-height-2: 16px;
  --font-size-3: 13px;   --line-height-3: 20px;
  --font-size-4: 14px;   --line-height-4: 20px;
  --font-size-5: 16px;   --line-height-5: 24px;
  --font-size-6: 28px;   --line-height-6: 32px;
  /* v2 additions, from the mock's dominant sizes. Positional names,
     same reasoning as the rest: --font-size-7 is 'the next step up',
     not '15px'. 15/22 is body-large (row primaries, buttons, inputs);
     17/24 the card title; 22/28 the pane/lead title. */
  --font-size-7: 15px;   --line-height-7: 22px;
  --font-size-8: 17px;   --line-height-8: 24px;
  --font-size-9: 22px;   --line-height-9: 28px;

  --tracking-wide: 0.06em;      /* uppercase eyebrows; transcribed codes */
  --tracking-eyebrow: 0.08em;   /* v2: the mock's mono section labels (STAGE, EARLIER) */
  --tracking-badge: 0.01em;     /* badges and counts */
  --tracking-title: -0.005em;   /* 14px 600 */
  --tracking-subhead: -0.01em;  /* 16px 600 */
  --tracking-display: -0.02em;  /* 28px 600 */

  /* ---- spacing scale -----------------------------------------------
     TAKEN FROM THE VALUES ALREADY IN USE, not rounded to a clean ramp.
     Rounding would move pixels, and this pass is required to change
     nothing visible. The result is therefore NOT a tidy 4px ramp: it is
     4px-stepped from --space-9 upward, but the low end carries every
     odd value the app had reached for (5, 7, 9), and there are gaps at
     the top (34-58px were never used).

     Position names, not value names: --space-5 stays correct if the
     scale is later retuned, whereas --space-8px would become a lie.
     A future ramp should collapse the odd low steps, but that is a
     visual change and belongs to a design block, not here. */
  --space-1: 1px;
  --space-2: 2px;
  --space-3: 4px;
  --space-4: 5px;
  --space-5: 6px;
  --space-6: 7px;
  --space-7: 8px;
  --space-8: 9px;
  --space-9: 10px;
  --space-10: 12px;
  --space-11: 14px;
  --space-12: 16px;
  --space-13: 18px;
  --space-14: 20px;
  --space-15: 22px;
  --space-16: 24px;
  --space-17: 26px;
  --space-18: 28px;
  --space-19: 30px;
  --space-20: 32px;
  --space-21: 60px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  color: var(--text);
  background: var(--bg);
}

/* Layout */
body.with-nav { display: flex; min-height: 100vh; }

.sidenav {
  width: var(--nav-width);
  flex-shrink: 0;
  background: var(--nav-bg);
  padding: var(--space-14) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  position: sticky;
  top: 0;
  /* THE NAV IS TALLER THAN THE VIEWPORT AND THAT IS FINE — WHAT IT MUST
     NEVER DO IS CLIP ITSELF.

     Twenty links at 38px each is 760px before the brand, the four group
     labels, the three separators and the log-out form; the whole column
     measures 1103px, and no laptop viewport is that tall. So the
     overflow has to go SOMEWHERE the user can reach.

     B11 first shipped `height: 100vh` + `overflow-y: auto`, reasoning
     that a scroll container is what a too-tall nav needs. That was
     wrong in the one way that matters: it moved the overflow OUT of the
     document. The page's scroll height collapsed to the viewport
     (measured: 720px on a 720px window), so there was nothing to
     scroll, and the nav's own scrollbar is an overlay on macOS — it
     does not paint until you are already scrolling inside the element
     you cannot see. Settings and Superadmin sat at y=853 and y=893 with
     `elementFromPoint` returning nothing. The links worked, the routes
     answered 200, and they were unreachable.

     `align-self: flex-start` + `min-height` instead: the nav takes its
     natural height, so the document grows to 1103px and the PAGE
     scrolls — the mechanism that was quietly making this work before
     B11, when the same nav measured 928px. `min-height` still paints
     the full-height band when the column is short, which is every VA
     session (one link).

     Never give this element a fixed height with a clipping overflow. */
  align-self: flex-start;
  min-height: 100vh;
}
.sidenav .brand {
  display: flex;
  align-items: center;
  gap: var(--space-9);
  color: var(--text);
  font-size: var(--font-size-5);
  line-height: var(--line-height-5);
  font-weight: 600;
  padding: 0 var(--space-14) var(--space-16);
  letter-spacing: var(--tracking-subhead);
}
/* The brand mark: a 26px teal tile, the one place the accent is a fill
   in the rail. */
.sidenav .brand::before {
  content: "";
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--accent);
  flex: none;
}
/* v2 (mock 1a): a nav row is a 44px pill, 8px radius, in a 10px gutter.
   The active row is the SOLID raised surface, not an accent tint or a
   left rail — the accent in the nav is reserved for the count capsule and
   the mode dot, so it means "something for you", never "you are here". */
.sidenav a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: var(--control-height);
  margin: 0 var(--space-9);
  padding: 0 var(--space-10);
  border-radius: var(--radius);
  color: var(--nav-text);
  text-decoration: none;
  font-size: var(--font-size-7);
  line-height: var(--line-height-7);
}
.sidenav a:hover { color: var(--text); background: var(--nav-hover-bg); }
.sidenav a.active {
  color: var(--nav-active-text);
  background: var(--nav-active-bg);
  font-weight: 600;
}
/* THE MODE CARD (mock 1a). Read-only; links to Settings → Mode. The dot
   is the accent when outreach is live and a hairline ring when paused, so
   the state is legible without reading the words. */
.sidenav .nav-mode-card {
  display: block;
  margin: auto var(--space-11) 0;
  padding: var(--space-10) var(--space-11);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  text-decoration: none;
  min-height: 0;
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
}
.sidenav .nav-mode-card:hover { background: var(--surface-hover); }
.sidenav .nav-mode-title { display: flex; align-items: center; gap: var(--space-7); font-weight: 600; }
.sidenav .nav-mode-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  border: 1px solid var(--muted);
}
.sidenav .nav-mode-dot.is-on { background: var(--accent); border-color: var(--accent); }
.sidenav .nav-mode-sub { color: var(--muted); font-size: var(--font-size-2); line-height: var(--line-height-2); margin-top: var(--space-2); }

.sidenav .logout-form { padding: var(--space-10) var(--space-14) 0; }

.content {
  flex: 1;
  max-width: 1100px;
  padding: var(--space-18) var(--space-20) var(--space-21);
  margin: 0 auto;
  width: 100%;
}
body.no-nav .content { max-width: 480px; padding-top: 10vh; }

/* v2: the page title is 24/600 in the mock (font-size-9 is 22, -6 is 28;
   the mock's 24 sits between and is used for exactly one thing — the
   page's name — so it takes -9, the pane-title step, and the 28 display
   step stays for figures). */
h1 {
  font-size: var(--font-size-9);
  line-height: var(--line-height-9);
  letter-spacing: var(--tracking-display);
  font-weight: 600;
  margin: 0 0 var(--space-12);
}
/* Graphite v2: a page's own <h1> — the direct child of .content, on the
   ~40 screens that have no explicit .page-head yet — takes the page-head
   treatment by selector: the hairline under and the same spacing, so
   every screen opens the same way whether it was rebuilt from a mock or
   not. Screens with a real .page-head style their h1 inside it. */
.content > h1 {
  padding-bottom: var(--space-13);
  margin-bottom: var(--space-12);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: var(--space-9);
  flex-wrap: wrap;
}
.content > h1 .badge, .content > h1 .tag { margin-left: 0; }
h2 {
  font-size: var(--font-size-5);
  line-height: var(--line-height-5);
  letter-spacing: var(--tracking-subhead);
  margin: var(--space-16) 0 var(--space-9);
}
.muted { color: var(--muted); }

/* Flash messages */
.flashes { margin-bottom: var(--space-13); }
.flash {
  padding: var(--space-9) var(--space-11);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel);
  margin-bottom: var(--space-7);
}
.flash-success { border-color: var(--success); background: var(--feedback-positive-bg); }
.flash-error { border-color: var(--danger); background: var(--feedback-negative-bg); }
.flash-warning { border-color: var(--warn-border); background: var(--feedback-attention-bg); }

/* Stat cards */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-9);
  margin: 0 0 var(--space-13);
}
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-11) var(--space-12);
  min-width: 150px;
  flex: 1;
}
/* v2 (mock 1a): a stat tile's label is 13px muted sentence case, the way
   the KPI strip's is — not an uppercase eyebrow. The eyebrow is for
   SECTION labels (.eyebrow); a tile's label names one number. */
.card .label {
  color: var(--muted);
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
}
.card .stat {
  font-size: var(--font-size-6);
  line-height: var(--line-height-6);
  letter-spacing: var(--tracking-display);
  font-weight: 600;
  margin-top: var(--space-2);
}
.card .sub {
  color: var(--muted);
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
}

/* Tables */
.table {
  width: 100%;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.table th, .table td {
  text-align: left;
  padding: var(--space-8) var(--space-10);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.table th {
  background: var(--surface-raised);
  color: var(--text-label);
  font-size: var(--font-size-2);
  line-height: var(--line-height-2);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-weight: 600;
}
.table tr:last-child td { border-bottom: none; }
.table tr:hover td { background: var(--surface-hover); }

/* Buttons */
/* v2: every button is a 44px control at 15/600. The mock has three
   weights of button and nothing else: PRIMARY (solid teal, dark ink),
   SECONDARY (raised surface + strong hairline) and GHOST (transparent +
   hairline, muted ink). The default .btn is SECONDARY; that is what an
   unadorned form button should read as. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-7);
  min-height: var(--control-height);
  padding: 0 var(--space-13);
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--text);
  font-size: var(--font-size-7);
  line-height: var(--line-height-7);
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.btn:hover { background: var(--surface-hover); }
.btn-ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--muted);
}
.btn-ghost:hover { color: var(--text); background: var(--nav-hover-bg); }
.btn-sm { min-height: var(--control-height-sm); padding: 0 var(--space-11); font-size: var(--font-size-4); line-height: var(--line-height-4); }
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-inverse);
  padding: 0 var(--space-13);
}
.btn-primary:hover { background: var(--accent-hover); }
/* v2 (mock 1c "Close lead…"): a destructive action is an OUTLINE — coral
   ink and a coral hairline on a transparent ground — not a solid slab.
   Twenty-seven of these ship across the templates and three sit side by side on
   a lead page; three red slabs in a row is the accent-on-every-button
   defect in a different hue. The SOLID form is `.btn-danger-solid`, for
   the one control per screen that is truly the point of no return (the
   Danger-zone reset), and nothing else. */
.btn-danger {
  background: transparent;
  border-color: var(--danger-border);
  color: var(--danger);
}
.btn-danger:hover { background: var(--danger-tint); border-color: var(--danger); }
.btn-danger-solid {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--text-inverse);
}
.btn-danger-solid:hover { background: var(--danger-hover); }
.btn-link {
  background: none;
  border: none;
  color: var(--nav-text);
  cursor: pointer;
  padding: 0;
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
}
.btn-link:hover { color: var(--text); text-decoration: underline; }

/* Badges */
/* v2 (mock): a status badge is a MONO tag — 11px, weight 500, in a 5px
   pill on a tint of its own hue. Uppercase comes from the mock's copy
   ("HOT", "QUOTE"); it is applied here so templates keep passing lower-
   case slugs. Not a capsule: capsules (--radius-pill) are counts and
   chips, which are a different thing. */
.badge {
  display: inline-block;
  padding: var(--space-2) var(--space-6);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--font-size-1);
  line-height: var(--line-height-1);
  letter-spacing: var(--tracking-badge);
  font-weight: 500;
  text-transform: uppercase;
  background: var(--status-neutral-bg);
  color: var(--status-neutral-text);
  vertical-align: middle;
}
.badge-new { background: var(--status-info-bg); color: var(--status-info-text); }
.badge-active { background: var(--status-positive-bg); color: var(--status-positive-text); }
.badge-replied { background: var(--status-attention-bg); color: var(--status-attention-text); }
.badge-quote { background: var(--status-recovery-bg); color: var(--status-recovery-text); }
.badge-booked { background: var(--status-scheduled-bg); color: var(--status-scheduled-text); }
.badge-exhausted { background: var(--status-neutral-bg); color: var(--status-neutral-text); }
.badge-suppressed { background: var(--status-negative-bg); color: var(--status-negative-text); }
.badge-danger { background: var(--status-negative-bg); color: var(--status-negative-text); }
.badge-warn { background: var(--warn-bg); color: var(--status-stale-text); }

/* Warning banner */
.warn-banner {
  background: var(--warn-bg);
  border: 1px solid var(--warn-border);
  border-radius: var(--radius);
  padding: var(--space-10) var(--space-12);
  margin-bottom: var(--space-13);
}

/* Forms */
.form-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-7);
  max-width: 560px;
}
.form-grid label {
  font-weight: 600;
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
  margin-top: var(--space-7);
}
.form-grid input,
.form-grid select,
.form-grid textarea {
  padding: var(--space-7) var(--space-9);
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  font: inherit;
  background: var(--panel);
  width: 100%;
}
.form-grid input:focus,
.form-grid select:focus,
.form-grid textarea:focus {
  outline: 2px solid var(--focus-ring);
  border-color: var(--accent);
}
.form-grid .btn { margin-top: var(--space-10); justify-self: start; }
.form-grid textarea { min-height: 110px; resize: vertical; }

/* Two-column form layout for wide settings sections */
@media (min-width: 720px) {
  .form-grid.two-col {
    grid-template-columns: 180px 1fr;
    align-items: center;
    max-width: 760px;
  }
  .form-grid.two-col label { margin-top: 0; }
}

/* Auth pages */
.auth-box {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-18) var(--space-19);
  box-shadow: 0 4px 14px var(--shadow-soft);
}
.auth-box h1 { margin-bottom: 0; font-size: var(--font-size-6); line-height: var(--line-height-6); }
.auth-box .btn { width: 100%; margin-top: var(--space-11); }

/* Misc */
.secret-value { font-family: var(--font-mono); }

/* B3: current user label in the side nav */
.sidenav .nav-user {
  padding: var(--space-10) var(--space-14) 0;
  color: var(--nav-user-text);
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
}
.sidenav .nav-user + .logout-form { margin-top: 0; }

/* --- B11: pipeline LANE badges + inline call forms ------------------------
   Three lanes, and the colour says which one at a glance without reading
   the word. COLD is the only neutral: a cold lead has no heat by
   definition. WORKING climbs attention -> recovery -> scheduled ->
   positive, so the rung reads as progress. NEGATIVE splits by whether the
   lead can come back: the three recoverable ones are muted neutral-strong
   (they are OUT, not GONE), and only the three TERMINAL ones take the
   full negative fill — which is what keeps that fill meaning something.

   B4's .badge-contacted / .badge-nurture / .badge-dead are gone with the
   stages they named; test_badge_coverage reads the vocabulary from
   pipeline.PIPELINE_STAGES, so this block cannot silently fall behind it. */
.badge-cold { background: var(--status-neutral-bg); color: var(--status-neutral-text); }
.badge-engaged { background: var(--status-attention-bg); color: var(--status-attention-text); }
.badge-quote { background: var(--status-recovery-bg); color: var(--status-recovery-text); }
.badge-appointment { background: var(--status-scheduled-bg); color: var(--status-scheduled-text); }
.badge-client { background: var(--status-positive-bg); color: var(--status-positive-text); }
.badge-not_interested,
.badge-wrong_person,
.badge-ghosted {
  background: var(--status-neutral-bg); color: var(--status-neutral-text-strong);
}
.badge-bad_number,
.badge-do_not_call,
.badge-revoked {
  background: var(--status-negative-bg); color: var(--status-negative-text);
}

/* THE TWO MANUAL LANES — Referral (referral_*) and Referral partner
   (partner_*), four rungs each.

   They climb the SAME ramp the Working lane does, for the same reason:
   the rung should read as progress without reading the word. What they
   deliberately do NOT do is take a colour of their own per lane. A lane
   is a heading on the board and a badge next to a name; colouring the
   two lanes differently would put five hues on one page and make the
   RUNG — the thing that actually changes — the harder of the two to
   read. Which pipeline a lead is in is already unambiguous from the
   badge TEXT, because the slug carries the lane. */
.badge-referral_referred,
.badge-partner_referred {
  background: var(--status-neutral-bg); color: var(--status-neutral-text);
}
.badge-referral_pitched,
.badge-partner_pitched {
  background: var(--status-attention-bg); color: var(--status-attention-text);
}
.badge-referral_closed,
.badge-partner_closed {
  background: var(--status-scheduled-bg); color: var(--status-scheduled-text);
}
.badge-referral_sold,
.badge-partner_sold {
  background: var(--status-positive-bg); color: var(--status-positive-text);
}

/* `overflow` is NOT a pipeline stage and is deliberately absent from
   pipeline.PIPELINE_STAGES. routes_today.py stamps it onto a DISPLAY dict
   (`d["pipeline_stage"] = "overflow"`) so today.html's
   `badge-{{ q['pipeline_stage'] }}` has something to render for a pool
   row, which has no stage of its own — a pool row is not a lead until
   positive contact, so giving it a real stage would be the lie the whole
   overflow separation exists to prevent.

   It therefore has no rule from test_badge_coverage's vocabularies and
   rendered as a bare grey `.badge` — the exact silent failure that file
   was written about, in the one family it does not cover.

   SCHEDULED, matching .badge-src-overflow on the same row: both say "this
   came from the pool", and two different colours for one fact on one line
   is worse than none. */
.badge-overflow {
  background: var(--status-scheduled-bg); color: var(--status-scheduled-text);
}

/* --- B11: the three lanes ------------------------------------------------
   Hierarchy is size and weight, not rules and boxes: the lane title is the
   largest thing on the page after the h1, its count sits beside it at the
   same weight, and the note that explains the lane recedes to muted body
   text. No lane carries an accent fill — the badges inside it already
   carry the colour, and repeating it on the header would flatten the very
   distinction the badges are making. */
.lane { margin-bottom: var(--space-2); }
.lane-head {
  display: flex; align-items: baseline; gap: var(--space-7);
  flex-wrap: wrap;
  margin-bottom: var(--space-8);
}
.lane-title {
  font-size: var(--font-size-1); line-height: var(--line-height-1);
  font-weight: 600; margin: 0;
}
.lane-count {
  font-size: var(--font-size-2); line-height: var(--line-height-2);
  font-weight: 600; color: var(--muted);
}
.lane-note {
  font-size: var(--font-size-3); line-height: var(--line-height-3);
}
.stage-head {
  display: flex; align-items: center; gap: var(--space-8);
  font-size: var(--font-size-3); line-height: var(--line-height-3);
  margin: 0 0 var(--space-8) 0;
}
.stage-terminal {
  font-size: var(--font-size-4); line-height: var(--line-height-4);
  text-transform: uppercase; letter-spacing: var(--tracking-badge);
}

/* COLD age buckets. Seven cells, equal weight, because no bucket is more
   important than another until you look at the number in it. A bucket
   holding zero recedes; a bucket holding leads does not. */
.bucket-strip {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--space-8);
  grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
}
.bucket {
  display: flex; flex-direction: column; gap: var(--space-10);
  padding: var(--space-8) var(--space-7);
  border-radius: var(--radius);
  background: var(--status-neutral-bg);
  /* --status-neutral-text, NOT --muted. --muted is tuned against the page
     canvas (6.53 on --bg) and lands at 5.12 on this chip — passing, but
     the weakest pair in the whole system, and introduced for a label that
     is already the smallest type on the page. The neutral ramp has a
     token built for this exact background: 6.76. */
  color: var(--status-neutral-text);
}
.bucket-live { color: var(--text); }              /* 14.87 */
.bucket-count {
  font-size: var(--font-size-2); line-height: var(--line-height-2);
  font-weight: 600;
}
.bucket-label {
  font-size: var(--font-size-4); line-height: var(--line-height-4);
  color: var(--status-neutral-text);              /* 6.76 */
}
.call-log-form, .call-row {
  display: flex; gap: var(--space-7); flex-wrap: wrap; align-items: center;
}
.call-log-form select, .call-log-form input,
.call-row input { padding: var(--space-7) var(--space-9); }

/* --- B5/B6: VA queue + pay ----------------------------------------------- */
.badge-success { background: var(--status-positive-bg); color: var(--status-positive-text); }
.queue-worked td { color: var(--muted); }
.queue-worked a { color: var(--muted); }
/* BLOCK 5 folded this into the general `.table td.num` column rule —
   .pay-breakdown IS a .table, and two rules saying the same thing is
   how one of them drifts. */
.pay-breakdown .pay-total td { border-top: 2px solid var(--border); }

/* --- R1: notification center + nav bell badge ---------------------------- */
.nav-badge {
  display: inline-block;
  min-width: 18px;
  padding: var(--space-1) var(--space-5);
  /* A capsule, not a corner. Option B keeps exactly two round things —
     "no circles except the .badge pill and .nav-badge" — so the 4px
     radius sweep must not reach this one. */
  border-radius: var(--radius-pill);
  /* v2 (mock 1a): the count is TEAL with dark ink, in mono. It used to
     be danger-red, which made a "3 notifications" capsule read as an
     error. It means "there is something for you", which is the accent's
     job in the rail. */
  background: var(--accent);
  color: var(--text-inverse);
  font-family: var(--font-mono);
  font-size: var(--font-size-2);
  letter-spacing: var(--tracking-badge);
  font-weight: 500;
  text-align: center;
  line-height: var(--line-height-2);
}
.notif-unread td { background: var(--surface-unread); }
/* AND IT HAS TO SURVIVE THE POINTER. `.table tr:hover td` is (0,2,2) and
   `.notif-unread td` is (0,1,1), so hovering an unread row replaced its
   tint with the generic hover grey and it became indistinguishable from
   a hovered READ row — proved by injecting a rule of identical
   specificity: #241D0E became #242424. --surface-unread's own note in
   :root says "an unread notification row has to be distinguishable from
   a HOVERED unread row", which is the distinction the cascade was
   destroying. A fourth surface without a fourth token: the attention
   tint, which is neither of the greys and neither of the other two. */
.table tr.notif-unread:hover td { background: var(--status-attention-bg); }
.notif-body { white-space: pre-wrap; max-width: 480px; }
.badge-ntype-approval { background: var(--status-info-bg); color: var(--status-info-text); }
.badge-ntype-quote { background: var(--status-attention-bg); color: var(--status-attention-text); }
.badge-ntype-booking { background: var(--status-scheduled-bg); color: var(--status-scheduled-text); }
.badge-ntype-bounce_pause { background: var(--status-negative-bg); color: var(--status-negative-text); }
.badge-ntype-recovery { background: var(--status-recovery-bg); color: var(--status-recovery-text); }
.badge-ntype-referral { background: var(--status-positive-bg); color: var(--status-positive-text); }
.badge-ntype-task { background: var(--status-neutral-bg); color: var(--status-neutral-text); }
.badge-ntype-system { background: var(--status-neutral-bg); color: var(--status-neutral-text); }

/* R2/R3: text-log heat scale + detection */
.badge-hot { background: var(--danger); color: var(--text-inverse); }
.text-log-form { display: flex; gap: var(--space-7); flex-wrap: wrap; align-items: center; }
.text-log-form input[name="note"] { padding: var(--space-7) var(--space-9); }
.heat-radios { display: flex; gap: var(--space-9); flex-wrap: wrap; }
.heat-radio {
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  white-space: nowrap;
}

/* R4: VA queue source badges + calling-window lock */
/* B6: the only source a new queue row can have — an agent pressed Send.
   The accent, because it is the one every row on a healthy day carries and
   a queue of uniformly loud badges reads as an alarm. */
.badge-src-sent { background: var(--status-info-bg); color: var(--status-info-text); }
.badge-src-ghost { background: var(--danger); color: var(--text-inverse); }
.badge-src-callback { background: var(--status-info-bg); color: var(--status-info-text); }
.badge-src-stale { background: var(--warn-bg); color: var(--status-stale-text); }
.badge-src-volume { background: var(--status-neutral-bg); color: var(--status-neutral-text-strong); }
.badge-src-aged, .badge-src-fresh { background: var(--status-neutral-bg); color: var(--status-neutral-text-strong); }
/* Overflow was the ONE queue source rendering with no colour of its own:
   today.html and overflow_work.html emit .badge-src-overflow and nothing
   styled it, so those rows fell back to the base grey `.badge` and read as
   Volume/Aged/Fresh. Purple is the only pair in the existing badge palette
   no source was using (it belongs to booked/appointment elsewhere, which
   never appears in the source column). 6.15:1 — this comment said 7.57
   until BLOCK 4 re-measured it; the pair never changed, the number was
   wrong when it was written, and --status-scheduled-text's own
   annotation had 6.15 all along. */
.badge-src-overflow { background: var(--status-scheduled-bg); color: var(--status-scheduled-text); }
.queue-locked td { color: var(--muted); }
.queue-locked a { color: var(--muted); }
/* A locked row is dimmed on its BACKGROUND, never by fading the badges.
   `opacity: 0.55` on the badge was responsible for 156 of the 163 contrast
   failures on /today: it composites the badge's own colours toward the row
   behind them, so the declared #fff-on-#dc2626 (4.83, passes) rendered as
   roughly #ef9d9d on #ec8888 — about 1.2:1. No palette change can fix that,
   because white text faded over a white row stays white; even a black fill
   caps out around 2.8. The badges are legible at full strength and the row
   still reads as asleep. */
.queue-locked td { background: var(--surface-disabled); }
.locked-note { font-style: italic; white-space: nowrap; }

/* R5: VA scripts console */
.work-nav { display: flex; align-items: center; gap: var(--space-10); }
.work-nav .work-next { margin-left: auto; }
.work-phone {
  font-size: var(--font-size-6);
  line-height: var(--line-height-6);
  letter-spacing: var(--tracking-display);
  font-weight: 600;
}
.work-phone a { text-decoration: none; }
.work-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--space-12);
  align-items: start;
}
@media (max-width: 900px) { .work-grid { grid-template-columns: 1fr; } }
.script-section { margin-bottom: var(--space-11); }
.script-section h3 {
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-2);
  line-height: var(--line-height-2);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--text-label);
}
.script-section p {
  margin: 0; white-space: pre-wrap; line-height: var(--line-height-4);
}
/* ONE rule. This class was declared twice — here with `gap: var(--space-9)`
   and again in the Block 3 sweep with `gap: var(--space-7)` — so the gap
   the file appeared to specify was not the gap that rendered. Merged at
   the value that was actually winning. */
.script-meta {
  display: flex; align-items: center; gap: var(--space-7); flex-wrap: wrap;
  border-top: 1px solid var(--border); padding-top: var(--space-9); margin-top: var(--space-3);
}
.panel-line { white-space: pre-wrap; line-height: var(--line-height-4); }
.rebuttal {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-5) var(--space-9);
  margin-bottom: var(--space-7);
}
.rebuttal summary { cursor: pointer; font-weight: 600; }
.rebuttal p { margin: var(--space-7) 0 var(--space-2); white-space: pre-wrap; }
.work-link { margin-right: var(--space-5); }

/* Part 4 Wave A: signup / attestation / onboarding (S1), per-VA fields (S2) */
.attest-box { max-width: 760px; margin: 0 auto; }
.attest-text {
  white-space: pre-wrap; font: inherit;
  /* STEP 3, not step 4. See the prose note in :root. */
  font-size: var(--font-size-3); line-height: var(--line-height-3);
  /* `<pre>` carries the UA's `margin-block: 1em`, and 1em HERE is the
     element's own 13px — so this box sat 13px from its neighbours, a
     number on no scale, purely because nobody wrote a margin. Its twin
     `.legal-body` zeroes it explicitly; this one had never been looked
     at. Measured at 13px 0px before this line. */
  margin: var(--space-12) 0;
  /* `white-space: pre-wrap` wraps at spaces and cannot break INSIDE an
     unbreakable token. The CSA text contains a 64-character URL, and
     below about 420px of viewport the contract box overflows its own
     width and takes the page's horizontal scroll with it — measured at
     360px: box scrollWidth 315 against clientWidth 256, document 560
     against 360. `overflow-wrap` rather than `word-break` so ordinary
     words still break only at their own boundaries. */
  overflow-wrap: break-word;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--space-12) var(--space-14); max-height: 420px; overflow-y: auto;
}
/* The consent sentence renders on three pages and read as two designs:
   this one is a <label> that is a direct child of a `.form-grid`, so
   `.form-grid label` made it 600 weight and 13px, while `/accept` and
   `/agreement` put the label INSIDE a `.doc-consent-line` where
   `.doc-consent-line label { font-weight: normal }` keeps it at body
   weight. Same sentence, same act, two treatments. Normal weight is the
   right one: bolding the thing somebody is agreeing to reads as
   emphasis on the checkbox rather than on the words. */
/* `.form-grid label` is (0,1,1) and a bare `.attest-agree` is (0,1,0),
   so the first attempt at this lost the cascade and the sentence stayed
   13px/600 while its two siblings moved to 14px/400. Two classes deep
   settles it. */
.form-grid label.attest-agree {
  display: flex; gap: var(--space-7); align-items: flex-start;
  font-weight: 400;
  font-size: var(--font-size-4); line-height: var(--line-height-4);
  margin-top: 0;
}
.onboarding-list { list-style: none; margin: 0; padding: 0; }
.onboarding-list li { padding: var(--space-3) 0; }
.user-fields-form { margin-top: var(--space-7); }
.user-fields-form input { max-width: 180px; }

/* S5: negative money values (net ROI) render in red */
.warn { color: var(--danger); font-weight: 600; }

/* Part 4 Wave B (S8): calendar view + Google Calendar settings.

   B11 REMOVED .cal-toolbar / .cal-nav / .cal-views / .cal-heading. They
   were four page-specific names for the app's period-stepping control,
   which is now the house pattern `.toolbar` + `.btn-group` +
   `.toolbar-title` at the end of this file. The calendar is where the
   pattern was invented; it is no longer where it lives. */
.cal-grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--space-7);
  align-items: start;
}
.cal-grid-day { grid-template-columns: 1fr; }
@media (max-width: 900px) { .cal-grid { grid-template-columns: 1fr; } }
.cal-day {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--space-7) var(--space-9); min-height: 90px;
}
.cal-today { border-color: var(--accent); }
.cal-day-head {
  font-size: var(--font-size-2); line-height: var(--line-height-2);
  font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tracking-wide); color: var(--text-label);
  margin-bottom: var(--space-5);
}
.cal-item {
  margin-bottom: var(--space-5);
  font-size: var(--font-size-4); line-height: var(--line-height-4);
}
.cal-time {
  color: var(--muted);
  font-size: var(--font-size-2); line-height: var(--line-height-2);
  margin-right: var(--space-3);
}
.cal-ext { color: var(--muted); }
.cal-empty { font-size: var(--font-size-3); line-height: var(--line-height-3); }
.cal-legend { margin-top: var(--space-10); }
.gcal-status {
  display: flex; align-items: center; gap: var(--space-9); flex-wrap: wrap;
  border-top: 1px solid var(--border); padding-top: var(--space-9); margin-top: var(--space-10);
}

/* Part 4 Wave B: Run Quotes form (S4) */
/* `.rq-fields input[readonly]` MOVED TO THE END OF THIS FILE. It is
   (0,2,1) and so is BLOCK 5's base control rule
   `input:not([type=checkbox]):not([type=radio])` — a `:not()` carries
   its argument's specificity — so the tie went to whichever came last,
   and the base rule did. The readonly Name field on Run Quotes rendered
   at full --text ink, identical to the seven money fields the agent has
   to type into. See the batch 3 group 2 block. */
.rq-preview-form label { display: block; margin-bottom: var(--space-9); }
.rq-preview-form input, .rq-preview-form textarea {
  width: 100%; font: inherit;
  font-size: var(--font-size-4); line-height: var(--line-height-4);
}
.rq-template summary { cursor: pointer; }

/* Part 4 Wave D (S10): Jarvis advisor */
.jarvis-input-row { display: flex; gap: var(--space-7); align-items: stretch; }
.jarvis-input-row input {
  flex: 1; font: inherit;
  font-size: var(--font-size-4); line-height: var(--line-height-4);
  padding: var(--space-7) var(--space-9);
}
/* Half a pair: the listening state swapped the fill and the ink and
   left the BORDER at `.btn`'s --border, so the one control in the app
   that says "your microphone is live" wore a neutral hairline around a
   red fill. */
#jarvis-mic.listening {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--text-inverse);
}
.jarvis-actions { margin-bottom: 0; }
.jarvis-answer {
  white-space: pre-wrap;
  font-size: var(--font-size-3); line-height: var(--line-height-3);
  /* A MEASURE. The answer is a plain div in a full-width card, so a
     model's paragraph ran the whole ~1000px content column at 13px —
     about 170 characters a line, roughly twice the width prose stays
     readable at. The step-3 type is deliberate and unchanged (the :root
     prose note names this block); only the line length moves. */
  max-width: 72ch;
}
.jarvis-asof { font-size: var(--font-size-3); line-height: var(--line-height-3); }

/* Part 5 C2a: Needs Review desk */
.nr-table td { vertical-align: middle; }
.nr-phone-form { display: flex; gap: var(--space-5); align-items: center; }
.nr-phone-form input {
  font: inherit;
  font-size: var(--font-size-4); line-height: var(--line-height-4);
  width: 160px;
}
.nr-panel-line { display: flex; align-items: center; gap: var(--space-7); margin: 0 0 var(--space-9); }

/* Part 6 T1: appointment confirmations (the agent texts by hand — the app
   only reminds and records; nothing here sends anything) */
.confirm-line { display: flex; align-items: center; gap: var(--space-7); margin: 0 0 var(--space-7); }
.confirm-message {
  width: 100%; max-width: 52ch; font: inherit;
  font-size: var(--font-size-4); line-height: var(--line-height-4);
  padding: var(--space-7) var(--space-9); margin-bottom: var(--space-7);
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius); resize: vertical;
}

/* Part 6 T2: the Settings danger zone (reset to a clean slate). No new
   colour system — it borrows the existing danger colour for its border. */
.danger-zone { border-color: var(--danger); }
.danger-zone h3 {
  margin: var(--space-3) 0 var(--space-7);
  font-size: var(--font-size-4); line-height: var(--line-height-4);
}

/* Part 6 T2: the two Mode switches. SPEC promises "TWO clearly separated
   switches", and the markup said so without anything making it true —
   .switch-block was referenced but styled nowhere.

   THE DEFECT BATCH 2 FOUND HERE, measured in a browser: the first Mode
   switch on /settings rendered as a rounded card WITH NO TOP BORDER AND
   NO TOP PADDING — 1px on three sides, 0 on the fourth, content flush
   against a missing edge — while the second one was a complete box.

   Two competing definitions caused it. This block originally described
   a DIVIDER (a rule between the switches: border-top + padding-top,
   suppressed on the first one by `:first-of-type`). Block 3 then moved
   settings.html's inline <style> into this file, and that carried a
   CARD definition (border on all four sides, radius, padding). Both
   survived. The card won on the shorthands, but `:first-of-type` is a
   class AND a pseudo-class, so it outranked the card and went on
   zeroing a top edge that now belonged to a border box.

   One definition now. The card is the design that shipped and the one
   the markup reads as, so the divider and its first-of-type companion
   are gone rather than reconciled. */
.switch-block form { margin-top: var(--space-9); }

/* ===================================================================
   Block 3 — design sweep. Appended per CLAUDE.md ("new CSS goes at the
   end of static/style.css"). Every rule below is presentation only.
   =================================================================== */

/* --- Form controls inherit the app's typeface -----------------------
   Chrome's UA sheet puts `font: 400 13.3333px Arial` on every form
   control, so 40+ inputs and selects — including the VA's call-log
   disposition select — rendered in Arial at a size that appears in no
   stylesheet. `.btn` set a size but never a family, so every button in
   the app was Arial too. This is why the audit measured ten rendered
   font sizes against seven declared ones. */
input, select, textarea, button { font-family: inherit; }
.btn { font-family: inherit; }

/* --- Focus, visible ------------------------------------------------
   The app had exactly one focus rule in 492 lines, at 1.68:1 against
   the 3:1 WCAG 2.2 asks for. This is a keyboard-driven console. */
:focus-visible {
  outline: 2px solid var(--accent-hover);
  outline-offset: 2px;
  border-radius: var(--radius);
}
.btn:focus-visible,
.table a:focus-visible { outline-offset: 3px; }

/* GRAPHITE exposed a defect that predates it: there was NO base `a` rule
   in this stylesheet at all, so every link outside the sidenav, the
   settings TOC and two queue overrides rendered in the USER AGENT's
   default blue (#0000EE) and purple (#551A8B).

   On the old white panel that measured 9.40:1 and nobody noticed. On
   Graphite's #131313 it is 1.98:1 — and visited links 1.69:1 — i.e.
   unreadable. A palette cannot fix this from :root because there was no
   declaration to change.

   Visited is set to the same colour deliberately: these are lead and
   navigation links, "have I been here" carries no meaning in this app,
   and the UA purple is the worse of the two hazards. */
a { color: var(--accent); }
a:visited { color: var(--accent); }

/* --- Headings ------------------------------------------------------
   Bare <h3> rendered at 17.55px against <h2>'s 18px — a 0.45px
   "difference" across 20 templates. */
h3 {
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  letter-spacing: var(--tracking-title);
  font-weight: 600;
  /* A TOP MARGIN. This was `margin: 0 0 var(--space-5)` — written for a
     heading that LEADS a card — and `.table` sets no bottom margin, so on
     /pay "This week (Fri–Thu)" sat flush against the bottom border of the
     Today table with nothing between them. An h3 that leads its card is
     zeroed below, the way h2 already is. */
  margin: var(--space-14) 0 var(--space-5);
}
.card > h3:first-child, .auth-box > h3:first-child { margin-top: 0; }

/* --- VA console: the Log call card ---------------------------------
   The disposition select measured y=2177–2500px across the captures —
   below the fold on every one, while the post-dial flash tells the VA
   to "log the outcome when you hang up". It now leads the right-hand
   column and stays put while the script is read. */
/* SCOPED TO THE TWO-COLUMN CONSOLE. `position: sticky` was written for
   /today/work, where this card sits in `.work-grid`'s right-hand column
   and sticks WITHIN that column. /today/overflow/<id> reuses the class
   with no grid around it, so its containing block is the page and the
   card floated over the content for the whole scroll. */
.work-grid .work-log-card { position: sticky; top: var(--space-12); }

/* --- VA console: script state badges -------------------------------
   "preparing lines" (nobody has generated yet) and "generic lines" (the
   worker tried and fell back) mean different things and must keep
   reading as different things. Pending is the more informative state
   and was the LESS legible of the two, at 4.07:1 against the fallback's
   6.37:1. */
.badge-pending {
  background: var(--status-info-bg);
  color: var(--status-info-text-strong);
}

/* --- Queue: the day is done ---------------------------------------- */
.queue-done {
  margin-top: var(--space-10);
  padding: var(--space-10) var(--space-11);
  border: 1px solid var(--feedback-positive-border);
  background: var(--feedback-positive-bg);
  border-radius: var(--radius);
  color: var(--feedback-positive-text);
}

/* --- Truncation notices -------------------------------------------- */
.list-truncated {
  margin-top: var(--space-10);
  padding: var(--space-9) var(--space-10);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--surface-raised);
  color: var(--muted);
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
}

/* --- Lead detail: actions ------------------------------------------
   .lead-actions was referenced by the template and styled NOWHERE, so
   all of its layout lived in inline styles. Destructive actions are
   separated from the routine ones rather than sharing a row. */
.lead-actions { display: flex; flex-wrap: wrap; gap: var(--space-7); align-items: center; }
.lead-actions .spacer {
  flex: 1 1 auto;
  min-width: 12px;
}
.lead-actions-danger {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-7);
  align-items: center;
  margin-top: var(--space-9);
  padding-top: var(--space-9);
  border-top: 1px dashed var(--border);
}
.lead-actions-danger .danger-label {
  font-size: var(--font-size-2);
  line-height: var(--line-height-2);
  color: var(--muted);
  margin-right: var(--space-3);
}

/* --- Dashboard: five tables in one card ---------------------------- */
.attention-group { margin-bottom: var(--space-13); }
.attention-group:last-child { margin-bottom: 0; }
.attention-group > h3 {
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--text-label);
  margin: 0 0 var(--space-5);
}

/* --- Run Quotes ----------------------------------------------------
   Seven hand-typed prices went into a 560px single-file stack inside a
   1016px card, with the three low/high pairs unpaired. The money grid
   pairs them; the preview step gets the same input treatment as every
   other form in the app (it had none at all). */
.rq-money-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(160px, 1fr));
  gap: var(--space-9) var(--space-12);
  max-width: 640px;
  align-items: end;
}
.rq-money-grid .rq-span { grid-column: 1 / -1; }
.rq-money-grid label {
  display: block;
  font-size: var(--font-size-3); line-height: var(--line-height-3);
  margin-bottom: var(--space-3);
}
.rq-money-grid input {
  width: 100%;
  padding: var(--space-7) var(--space-9);
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  font: inherit;
}
.rq-preview-form input, .rq-preview-form textarea {
  padding: var(--space-7) var(--space-9);
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
}

/* --- Settings ------------------------------------------------------
   /settings was the only page in the app that scrolled horizontally at
   1280px: .section-actions could not wrap and its five ~170px controls
   could not shrink, so the table broke out of its card. */
.section-actions { display: flex; flex-wrap: wrap; gap: var(--space-7); align-items: center; }
.section-actions > * { min-width: 0; }
/* THE SCROLL BELONGS TO A WRAPPER, NOT TO THE TABLE. This was
   `.settings-section .table { display: block; overflow-x: auto }`, and
   `display: block` on a <table> makes the engine generate an ANONYMOUS
   table box around the thead/tbody — which takes initial values for
   everything the author set on the real element, `width: 100%` first
   among them. Measured: every settings table rendered 978px inside a
   1016px section, 38px short of the container it was told to fill, with
   the last column's contents crowded for no visible reason. The table
   keeps `display: table` now and the wrapper does the scrolling. */
/* Not scoped to /settings any more: /profit's period table is eleven
   columns of money and needs exactly the same treatment. */
.table-scroll { overflow-x: auto; }

/* Every settings save flashes a confirmation the user never sees: the
   POST redirects to a section anchor while .flashes renders at the top
   of a ~20,000px page. */
.flashes { position: sticky; top: 0; z-index: 20; }
.settings-section { scroll-margin-top: 72px; }

/* --- Empty states --------------------------------------------------
   CLAUDE.md: an empty state gets a short friendly line, never a bare
   empty table. */
/* v2: .empty-state is the pre-redesign name for the same thing .empty is
   (the mock's quiet "nothing here" panel); ten templates still write it,
   so it aliases the same declarations rather than being renamed in ten
   places tonight. */
.empty-state {
  padding: var(--space-13) var(--space-14);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--muted);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
}

/* --- Disabled controls read as disabled ---------------------------- */
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; }

/* --- Settings page ---------------------------------------------------
   Moved verbatim out of an inline <style> block in settings.html — the
   only one in the app, and it silently overrode .switch-block here, so
   the two Mode switches rendered with different treatments depending on
   which rule won. Same declarations, now in one place. */
.settings-section { flex: none; margin: 0 0 var(--space-10); border-radius: var(--radius-lg); padding: var(--space-13) var(--space-14); }
  .settings-toc {
    margin-bottom: var(--space-14);
    font-size: var(--font-size-4); line-height: var(--line-height-4);
  }
  .settings-toc a { color: var(--accent); text-decoration: none; }
  .settings-toc a:hover { text-decoration: underline; }
  .inline-form { display: inline; }
  .num-row { display: flex; gap: var(--space-7); margin-bottom: var(--space-5); }
  .num-row input { flex: 1; }
  .table input, .table select {
    padding: var(--space-4) var(--space-7); border: 1px solid var(--border-input); border-radius: var(--radius);
    font: inherit; background: var(--panel);
  }
  .tpl-block { border: 1px solid var(--border); border-radius: var(--radius);
    padding: var(--space-10) var(--space-12); margin-bottom: var(--space-11); }
  .tpl-block h3 {
    margin: 0 0 var(--space-7);
    font-size: var(--font-size-4); line-height: var(--line-height-4);
  }
  .tpl-block .slug {
    color: var(--muted); font-weight: 400;
    font-size: var(--font-size-3); line-height: var(--line-height-3);
  }
  .section-actions { display: flex; gap: var(--space-7); align-items: center; }
  .test-form { margin-top: var(--space-10); }
  .switch-block { border: 1px solid var(--border); border-radius: var(--radius);
    padding: var(--space-10) var(--space-12); margin-top: var(--space-11); }
  .switch-block h3 {
    margin: 0 0 var(--space-7);
    font-size: var(--font-size-4); line-height: var(--line-height-4);
  }

/* Visually hidden, still announced. Used to name the blank action column
   in tables — an empty final <th> is what Lighthouse's td-has-header trips
   on, because the buttons in that column have no header to belong to. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: calc(var(--space-1) * -1);
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Click-to-copy phone numbers (dialing happens in Ringy, so the number is
   the handoff). */
.copyable {
  cursor: pointer;
  border-bottom: 1px dashed var(--hint-underline);
  transition: background-color .15s ease;
}
.copyable:hover, .copyable:focus { background: var(--surface-hover); outline: none; }
.copyable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.copyable.copied { background: var(--status-positive-bg); border-bottom-color: var(--success); }
.copy-note {
  margin-left: var(--space-5);
  font-size: var(--font-size-1);
  line-height: var(--line-height-1);
  color: var(--note-positive-text);
  font-weight: 600;
}
.copy-note-warn { color: var(--note-attention-text); }

/* Part 11: the email signature, shown as it will send (Settings →
   Identity & signature, and the Run Quotes preview). A <pre> because the
   agent's line breaks are the formatting — wrapping is on so a long
   disclaimer line cannot push the card into a horizontal scroll. */
.signature-preview {
  white-space: pre-wrap;
  word-break: break-word;
  font: inherit;
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-10) var(--space-12);
  margin: var(--space-7) 0 0;
  max-height: 320px;
  overflow-y: auto;
}

/* PART 12: two-factor enrollment and recovery codes. */
.auth-box-wide { max-width: 560px; }
.mfa-steps {
  margin: var(--space-14) 0; padding-left: var(--space-14);
  font-size: var(--font-size-3); line-height: var(--line-height-3);
}
.mfa-steps > li { margin-bottom: var(--space-15); }
.mfa-qr {
  margin: var(--space-10) 0;
  padding: var(--space-10);
  background: var(--surface-scan);
  border: 1px solid var(--status-neutral-bg);
  border-radius: var(--radius);
  /* BLOCK, not inline-block. As an inline-block it sat in the middle of
     its step's sentence, so "…(1Password, Authy, Google" ran above the
     code and "Authenticator, or your password manager)." below it —
     one sentence with a QR code wedged through it. `max-content` keeps
     the white quiet zone snug to the code instead of spanning the card.
     The border stays --status-neutral-bg: that token is a background
     everywhere else and is the wrong role here on paper, but this edge
     sits against --surface-scan's pure white, where #242424 is the DARK
     half of the pair and reads at about 14:1. */
  display: block;
  width: max-content;
}
.mfa-qr svg { width: 180px; height: 180px; display: block; }
.mfa-secret {
  font-family: var(--font-mono);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  letter-spacing: var(--tracking-wide);
  background: var(--surface-raised);
  /* --border, not --status-neutral-bg. A BACKGROUND token was being used
     as a border here: #242424 against this block's own #1D1D1D ground is
     about 1.06:1, so the box had no visible edge at all — measured
     `border-bottom: solid rgb(36,36,36)`.

     Dashed, because this element also carries `.copyable` and the
     `border` SHORTHAND above was silently resetting the dashed
     `border-bottom` that IS the click-to-copy affordance. On an inline
     span an underline says "copy me"; on a block it has to be the box. */
  border: 1px dashed var(--border-input);
  border-radius: var(--radius);
  padding: var(--space-7) var(--space-9);
  margin: var(--space-7) 0 0;
  word-break: break-all;
}
.mfa-confirm { margin-top: var(--space-9); }
.mfa-codes {
  list-style: none;
  padding: var(--space-11) var(--space-12);
  margin: var(--space-12) 0;
  background: var(--surface-raised);
  /* Same background-token-as-border as .mfa-secret above, same 1.06:1,
     same fix. This one is not copyable, so it keeps a solid edge. */
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-5) var(--space-13);
  font-family: var(--font-mono);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  letter-spacing: var(--tracking-wide);
}
.mfa-done { margin-top: var(--space-14); }
.mfa-status { margin: 0 0 var(--space-10); }

/* PART 12 Step 4: legal documents and their acceptance boxes. Reuses the
   attestation page's typography — same kind of content, so it should not
   look like a different product. */
.legal-doc { max-width: 760px; margin: 0 auto; }
.legal-body {
  white-space: pre-wrap; font: inherit;
  /* STEP 3, not step 4. See the prose note in :root. */
  font-size: var(--font-size-3); line-height: var(--line-height-3);
  /* See the note on .attest-text: a 64-character URL in the CSA text
     cannot break, and the contract box overflows below ~420px. */
  overflow-wrap: break-word;
  margin: 0;
}
.legal-body-scroll {
  /* RECESSED, not level. This box sat at --panel inside a .card that is
     also --panel, so the only thing saying "this is a window onto a
     long document, and it scrolls" was a 1px hairline. The canvas token
     reads as a well; the border stays.

     420px, not 320px. The comment above this block says it "reuses the
     attestation page's typography — same kind of content, so it should
     not look like a different product", and then capped the CSA — the
     LONGER of the two documents — a hundred pixels shorter than the
     attestation. */
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius); padding: var(--space-12) var(--space-14); max-height: 420px; overflow-y: auto;
}
.legal-card { margin-bottom: var(--space-12); }
.legal-card h2 { margin-top: 0; }
.doc-consent { margin: var(--space-3) 0 var(--space-7); }
.doc-consent-line {
  display: flex; gap: var(--space-7); align-items: flex-start; margin: 0 0 var(--space-7);
}
.doc-consent-line label {
  font-weight: normal;
  /* Body copy, not a field label. `.form-grid label` was giving these
     13px and 600 weight — the treatment for the word above an input —
     to the sentence somebody is agreeing to. Same 14/20 as
     `.attest-agree`, so the same act reads the same on all three
     pages. */
  font-size: var(--font-size-4); line-height: var(--line-height-4);
  /* `.form-grid label { margin-top: var(--space-7) }` reaches this one
     too, so the sentence sat 8px lower than the box it belongs to. */
  margin-top: 0;
}

/* PART 12 Step 8: utility classes replacing the 95 inline style=
   attributes the templates used to carry. A strict CSP (style-src
   'self', no 'unsafe-inline') blocks every one of them, and
   'unsafe-inline' is exactly what makes CSS-based exfiltration and
   UI-redress possible — so the markup moved instead of the policy.
   Named for what they DO, so a new rule is greppable. */
.u-codeblock { white-space:pre-wrap; overflow-x:auto; max-height:320px; background:var(--surface-raised); border:1px solid var(--border); border-radius: var(--radius); padding:var(--space-9); font-size:var(--font-size-2); line-height:var(--line-height-2); }
.u-d-inline { display:inline; }
.u-d-none { display:none; }
.u-inline-ml-9 { display:inline; margin-left:var(--space-9); }
.u-inline-note { margin-left:var(--space-9); font-size:var(--font-size-5); line-height:var(--line-height-5); vertical-align:middle; }
.u-inline-note-tight { margin-left:var(--space-5); font-size:var(--font-size-5); line-height:var(--line-height-5); vertical-align:middle; }
.u-m-11-0-15 { margin:var(--space-11) 0 var(--space-15); }
.u-m-7-0-0 { margin:var(--space-7) 0 0; }
.u-measure-narrow-mt-7 { max-width:420px; margin-top:var(--space-7); }
.u-measure-wide { max-width:520px; }
.u-measure-none { max-width:none; }
.u-mb-9 { margin-bottom:var(--space-9); }
.u-mb-10 { margin-bottom:var(--space-10); }
.u-mb-11 { margin-bottom:var(--space-11); }
.u-mb-12-noflex { margin-bottom:var(--space-12); flex:none; }
.u-mb-12 { margin-bottom:var(--space-12); }
.u-ml-7 { margin-left:var(--space-7); }
.u-mt-9 { margin-top:var(--space-9); }
.u-mt-11 { margin-top:var(--space-11); }
.u-mt-12 { margin-top:var(--space-12); }
.u-mt-16 { margin-top:var(--space-16); }
.u-mt-5 { margin-top:var(--space-5); }
.u-mt-7 { margin-top:var(--space-7); }
.u-plainlink-ml-9 { text-decoration:none; margin-left:var(--space-9); }
.u-row-5 { display:flex; gap:var(--space-5); }
.u-row-5-wrap { display:flex; gap:var(--space-5); flex-wrap:wrap; }
.u-row-7-mt-9 { display:flex; gap:var(--space-7); margin-top:var(--space-9); }
.u-row-7-mt-7 { display:flex; gap:var(--space-7); margin-top:var(--space-7); }
.u-row-7-wrap-mb-10 { display:flex; gap:var(--space-7); flex-wrap:wrap; margin-bottom:var(--space-10); }
.u-td-none { text-decoration:none; }
/* EACH OF THESE IS PAIRED WITH A TYPE-QUALIFIED TWIN, and the twin is
   the one that does the work. `.form-grid input, .form-grid select,
   .form-grid textarea { width: 100% }` is specificity (0,1,1); a bare
   `.u-field-w-sm` is (0,1,0) and loses however far down the file it
   sits. Five fields were affected and all five rendered full width:
   State (meant 80px), ZIP and Lead cost on /leads/new, and both
   per-source Cost fields in Settings -> Lead sources (meant 120px).
   Measured at 520px each.

   `input.u-field-w-sm` is (0,1,1) — a tie, which source order then
   settles in the utility's favour because this block is 800 lines
   later. The bare class stays for the two `<th>` uses on a lead's
   timeline, which no other rule contests. */
.u-field-w-md, input.u-field-w-md { width:110px; }
.u-field-w-lg, input.u-field-w-lg { width:120px; }
.u-field-w-xl, input.u-field-w-xl { width:150px; }
.u-field-w-2xl, input.u-field-w-2xl { width:170px; }
.u-field-w-3xl, input.u-field-w-3xl { width:230px; }
.u-field-w-xs, input.u-field-w-xs { width:72px; }
.u-field-w-sm, input.u-field-w-sm { width:80px; }
.u-ws-pre-wrap { white-space:pre-wrap; }
/* Audit log surface (PART 12 Step 8). */
.audit-filters {
  display: flex; flex-wrap: wrap; gap: var(--space-7); align-items: center;
  margin: var(--space-10) 0 var(--space-12);
}
.audit-filters label {
  font-weight: 600;
  font-size: var(--font-size-3); line-height: var(--line-height-3);
  /* The bar is one flex row with a uniform 8px gap, so the space
     between "Category" and its own select was identical to the space
     between that select and the next label — five field pairs and two
     buttons reading as one undifferentiated run of controls. Widening
     the gap BEFORE each label is the whole fix; nothing else changes. */
  margin-left: var(--space-9);
}
.audit-filters label:first-of-type { margin-left: 0; }
.audit-table { font-size: var(--font-size-3); line-height: var(--line-height-3); }
.audit-when { white-space: nowrap; font-variant-numeric: tabular-nums; }
.audit-cat {
  margin-left: var(--space-5);
  font-size: var(--font-size-1); line-height: var(--line-height-1);
}
.audit-detail { word-break: break-word; max-width: 520px; }
.audit-pager { display: flex; gap: var(--space-7); margin-top: var(--space-12); }

/* Public pages a lead reaches from an email link (unsubscribe, and the
   not-found it falls back to). No nav, no session, so they need their own
   centring — base.html's `.no-nav` body covers the chrome, this covers the
   content block. */
.public-note {
  max-width: 480px;
  margin: var(--space-21) auto;
  padding: 0 var(--space-12);
}

/* GRAPHITE: tabular numerals, app-wide.
   Option B's central move is that every rendered number lines up — the
   money columns on /pay and /profit, the counts on /stats, the attempt
   counts and timestamps on the queue. Set on `body` rather than on a
   list of numeric selectors ON PURPOSE: a per-column opt-in is a list
   somebody forgets to add to, and a column that silently renders
   proportional is exactly the defect this is meant to prevent. Inherited
   everywhere, so a new numeric column is covered the day it is written.
   tests/test_typography.py fails if this is removed or overridden. */
body { font-variant-numeric: tabular-nums; }

/* PART 12 Step 9: the legal registry pages. */
.legal-versions { list-style: none; margin: var(--space-10) 0; padding: 0; }
.legal-versions li {
  display: flex; gap: var(--space-7); align-items: center;
  padding: var(--space-5) 0; border-bottom: 1px solid var(--border);
}
.u-fs-small { font-size: var(--font-size-3); line-height: var(--line-height-3); }
/* `.u-mt-12` WAS RE-DECLARED HERE, as var(--space-10). Two rules, one
   name, different values: the later one won, so every `u-mt-12` in the
   app resolved to 12px while its name — and the whole `u-mt-<N>` = 
   `--space-<N>` convention — promised 16px. Nothing errored and nothing
   could: a duplicate is legal CSS. Deleted, so the survivor at the top
   of this utility block is the only declaration and the name is true
   again; the two elements that used it (the /stats referral card and
   the CSA's closing note) move 12px -> 16px. */
.draft-note {
  color: var(--muted);
  font-size: var(--font-size-3); line-height: var(--line-height-3);
  margin-left: var(--space-7);
}

/* The VA on/off switch in Settings. Its own form, so it needs its own
   separation from the queue-settings form that follows it — the two are
   different decisions and must not read as one block. */
.va-switch-form { margin-bottom: var(--space-12); }

/* Operator console: the per-account action stack. Tokens only — every
   colour and every gap here resolves through :root, so a palette change
   stays an edit to :root and nothing else (tests/test_tokenization.py).

   BLOCK 1 STEP C removed the trial banner and the /billing page along
   with billing itself, and their rules went with them: an unused class
   is a promise of a treatment that no longer exists. */
/* Operator console: the per-account action stack. The buttons are
   btn-link and would otherwise sit on one line and wrap mid-label. */
.superadmin-actions form { margin: 0 0 var(--space-4); }
/* The per-account assistant-seat list. Each row is a name, its calling
   state and the control that changes it, so the list must not take the
   UA's bullet indent and run into the column beside it. */
.superadmin-seats { list-style: none; padding-left: 0; margin: 0; }
.superadmin-seats li { margin-bottom: var(--space-5); }
.superadmin-seats form { margin: var(--space-3) 0 0; }

/* ===================================================================
   BLOCK 5 — MARKUP AND LAYOUT. BATCH 1: the four screens Henry and
   Ervin are inside all day — the VA queue (/today), the call console
   (/today/work/<id>), /approvals and a lead's detail page.

   Blocks 3 and 4 were tokens: colour, spacing, then type. This block
   spends that vocabulary. NO NEW TOKENS, no token values edited, no
   `:root` edit — every declaration below resolves through what is
   already there.
   =================================================================== */

/* --- THE CONTROLS WERE NEVER IN THE THEME --------------------------
   THIS IS A LIVE CONTRAST FAILURE, not a refinement. Chrome's UA sheet
   colours form controls `fieldtext` on `field` — black on white. Two
   rules in this stylesheet (`.form-grid input/select/textarea` and
   `.table input/select`) override the BACKGROUND to var(--panel) and
   never touch the ink, so the draft reply on /approvals, the premium
   and commission fields on a lead, the note field and every settings
   input rendered BLACK ON #151515: 1.06:1. Measured in a browser, not
   read off the file — the declaration that fails is one this stylesheet
   never makes, which is exactly the shape of the missing `a` rule Block
   4 found, and no token guard can see either of them.

   Everything else — the VA's disposition select, both date pickers, the
   referral picker, every bare `note` field — kept the UA's WHITE box:
   legible, and a white rectangle in the middle of a near-black console.

   `color-scheme: dark` is the other half. It is what makes the parts a
   stylesheet cannot reach — the select's dropdown list, the date
   picker's calendar, the scrollbars, the caret — render dark instead of
   as a white sheet dropped over the page. It sits on `html` rather than
   `:root` deliberately: same element, but `:root { }` in this file is
   the token block and tests/test_tokenization.py requires every
   statement inside it to be a custom property. */
html { color-scheme: dark; }

/* v2: a text control is a 44px field at 15px, same radius as a button,
   so a form row of [input][button] sits on one baseline. Textareas keep
   the padding and grow. --border-input stays the solid grey (3:1 floor
   on the panel) — the mock's rgba(255,255,255,.1) is 1.32:1 and an input
   identified only by its border cannot be that faint. */
input:not([type="checkbox"]):not([type="radio"]),
select,
textarea {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  padding: var(--space-9) var(--space-11);
  font-size: var(--font-size-7);
  line-height: var(--line-height-7);
}
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
select { min-height: var(--control-height); }
input::placeholder, textarea::placeholder { color: var(--muted); }

/* --- One control padding -------------------------------------------
   Four different paddings shipped: 8/10 in .form-grid, Run Quotes,
   Jarvis and the confirmation textarea; 6/8 in the inline call, text
   and callback rows; 5/8 inside a settings table; and the UA's own on
   everything unstyled. The inline rows move onto the 8/10 the majority
   already used — they sit in table cells beside 14px text and were the
   one place a control looked smaller than its own label.

   The settings table keeps 5/8 and keeps it ON PURPOSE: those are grids
   of controls, dozens per section, where the row height IS the density.
   Two values, one of them named. */
/* The four inline-row controls were moved onto the 8/10 padding at
   their own declarations above, not re-declared here — a second rule
   for a selector that already sets the property is the defect
   tests/test_stylesheet_integrity.py exists for, and this block was
   caught by it. */

/* --- Numbers line up in a column, not just in a glyph ---------------
   `font-variant-numeric: tabular-nums` on body makes every digit the
   same WIDTH; it cannot make a left-aligned column of different-length
   numbers share a right edge. A count column reads by its last digit.
   The class is the opt-in for a column that holds a number and nothing
   else — never for a cell mixing a figure with words. */
.table td.num, .table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
/* nowrap on the CELL only. Applying it to the header too was a mistake
   that /profit caught immediately: its period table has eleven columns,
   and stopping "Variable pay" and "Approval rate" from wrapping pushed
   the table to 1089px inside a 1036px card and put a horizontal scroll
   on the page. A header is prose and may wrap; a figure is one token and
   must not. */
.table td.num { white-space: nowrap; }

/* --- A border inside a border separates nothing ---------------------
   A .table draws a 1px box. Inside a .card that is a second 1px box
   1 pixel of padding away from the first, and /dashboard's "Needs
   attention" card stacked FIVE of them. The header band's own surface
   already says "this is a table", and the card already says "this is a
   group", so the table's outer box is deleted where it is nested and
   the row hairlines — the ones that separate one lead from the next —
   stay. An explicit modifier rather than a `.card .table` descendant
   rule, so it reaches exactly the tables this batch touched and not
   /settings, whose tables are cards' whole content. */
.table-flush { border: 0; border-radius: 0; }

/* A card's title had 24px above it from the global h2 margin on top of
   the card's own 14px padding, and 14px below — the same heading sat
   38px from one edge and 14px from the other in every card in the app. */
.card > h2:first-child { margin-top: 0; }

/* --- A heading is one size ------------------------------------------
   Badges carry their own step. Bare meta inside an h1 did not, so
   "flagged 3h ago" beside a lead's name on the call console rendered at
   28px — the same size as the name, and larger than any other text on
   the page. */
h1 .badge { vertical-align: middle; }
h1 .muted {
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
  letter-spacing: normal;
  font-weight: 400;
  vertical-align: middle;
}
/* A `.btn` inside an h1 keeps the button's type. /leads and /pay both
   put an action in the heading and reach for `.u-inline-note` to space
   it, which also carries `font-size: var(--font-size-5)` — same (0,1,0)
   as `.btn`, 860 lines later, so the button rendered at 16px/24px in a
   row of 14px buttons. Measured. */
h1 .btn {
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  vertical-align: middle;
}

/* --- The value is the ink, the key is the label ---------------------
   A .sub line reads "Email: sarah@example.com" with both halves at
   --muted, so the word "Email" carries the same weight as the address
   it names. */
.card .sub strong { color: var(--text); font-weight: 600; }

/* --- /dashboard: one display figure, not five ----------------------
   Five stat cards, five 28px figures, and the type scale calls step 6
   "the ONE display figure". Emails sent, suppressions on file, bounce
   rate and worker heartbeat are STATUS — you read them to confirm
   nothing is wrong. Pending approvals is the only one that is a queue
   of work with a page behind it, so it is the only one that keeps the
   display step and the stronger boundary. The rest drop to the subhead
   step, which still reads as a figure under an uppercase label. */
.cards-status .stat {
  font-size: var(--font-size-5);
  line-height: var(--line-height-5);
  letter-spacing: var(--tracking-subhead);
}
.cards-status .card-primary { border-color: var(--border-input); }
.cards-status .card-primary .stat {
  font-size: var(--font-size-6);
  line-height: var(--line-height-6);
  letter-spacing: var(--tracking-display);
}

/* --- /today: the VA's queue is the page ----------------------------
   "Worked 1 / 125" was a muted sentence above the table. It is the
   VA's day, and the count they are paid against. */
.queue-progress {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-7);
  margin: 0 0 var(--space-10);
}
.queue-progress .queue-count {
  font-size: var(--font-size-5);
  line-height: var(--line-height-5);
  letter-spacing: var(--tracking-subhead);
  font-weight: 700;
  color: var(--text);
}

/* Work opens the console — the script, the number, the log form — and
   is the action every queue row exists for. The inline call-log form
   beside it is the shortcut for a disposition you already know, so it
   is demoted here and only here; the same macro renders as the primary
   action on /leads/<id> and in the callbacks table, where it is. */
.queue-inline .btn-primary {
  background: var(--panel);
  border-color: var(--border);
  color: var(--text);
}
.queue-inline .btn-primary:hover { background: var(--surface-hover); }

/* And a gold button on a row that is already worked, or cannot be called
   for another eleven hours, is an invitation to do nothing. Work stays
   reachable on both — the script is worth reading before the window
   opens — it just stops being the loudest thing on a row where there is
   nothing to do. */
.queue-worked .btn-primary,
.queue-locked .btn-primary {
  background: var(--panel);
  border-color: var(--border);
  color: var(--muted);
}
.queue-worked .btn-primary:hover,
.queue-locked .btn-primary:hover { background: var(--surface-hover); }

/* --- /today/work: the number is the point --------------------------
   The phone number and the lead's local time were both at the display
   step, so the clock read as loud as the thing you dial. The time only
   answers "may I call yet", which the badge under it already answers in
   words. */
.work-localtime {
  font-size: var(--font-size-5);
  line-height: var(--line-height-5);
  letter-spacing: var(--tracking-subhead);
  font-weight: 600;
}
/* The one card on the page you type into. --border-input is the 3:1
   control token; it is what the rest of the console's controls carry,
   and it makes the log panel read as the surface that takes input. */
.work-log-card { border-color: var(--border-input); }

/* --- /approvals: the draft is the content --------------------------
   The textarea holding the reply had no label at all — the page's whole
   subject arrived as an unnamed box between a name and three buttons. */
.approval-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-7);
  margin-bottom: var(--space-7);
}
.approval-head .approval-lead {
  font-size: var(--font-size-5);
  line-height: var(--line-height-5);
  letter-spacing: var(--tracking-subhead);
  font-weight: 600;
}
/* Two classes deep on purpose: `.form-grid label` already sets a weight,
   a size and a top margin, and a single-class selector would lose to it
   for the label that sits inside the form. */
.approval-card .approval-label {
  display: block;
  color: var(--text-label);
  font-size: var(--font-size-1);
  line-height: var(--line-height-1);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-weight: 600;
  margin-bottom: var(--space-3);
}
.approval-said { margin: 0 0 var(--space-10); }
/* The checkbox was a grid item in a one-column .form-grid, so it
   centred itself over the width of the card with its own label
   stranded on the next line. */
.approval-card .approval-override {
  display: flex;
  align-items: center;
  gap: var(--space-7);
  font-weight: 400;
  margin-top: var(--space-5);
}
/* the `width: auto` this rule used to carry is now general — see the
   checkbox rule in BATCH 2 GROUP 1; it was never an approvals problem. */

/* --- /leads/<id>: the timeline is content, not a footnote ----------
   Every other block on the page is a card; the timeline — the longest
   and most-read block — was a bare table under a bare heading. */
.timeline-card .table { margin-top: var(--space-5); }

/* ===================================================================
   BLOCK 5 — BATCH 2, GROUP 1: /settings (and the former /billing).

   The batch-1 base control rule fixed the ink on every input, select and
   textarea in the app. Auditing these screens in a browser — walking
   every element and reading computed style rather than reading the file
   — turned up four more members of the same family, all of them "the
   user agent is deciding this because nothing here does".
   =================================================================== */

/* --- 1. THE MONO STACK WAS PINNED AND THEN NOT APPLIED --------------
   48 live <code> elements — the merge fields on Templates, the
   blocklist phrases, the reset confirmation phrase, a document's
   sha256, a backup path — rendered in `font-family: monospace`, the
   BARE generic. That is the exact fallback --font-mono was created to
   prevent: its own comment says "the Windows fallback for bare
   `monospace` is Courier New", and the token was reached for by five
   rules and by no element selector, so anything that just wrote <code>
   got Courier New on one of the two machines this app runs on.

   The surface is here for the same reason the face is: an inline
   `{first_name}` in the middle of a sentence has to read as a token
   rather than as a word, and at 13px on the panel that needs a ground.
   `pre` takes the face only — every <pre> in the app that holds PROSE
   (the ToS, the DPA, the attestation, the signature preview) carries a
   class with `font: inherit`, which outranks this and keeps it prose. */
code, kbd, samp {
  font-family: var(--font-mono);
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
  background: var(--surface-raised);
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-4);
}
pre { font-family: var(--font-mono); }

/* --- 2. A CHECKBOX 572 PIXELS WIDE ---------------------------------
   `.form-grid input` sets `width: 100%`, which is right for a text
   field and absurd for a checkbox: the box drew at 13px and its HIT
   AREA ran the full width of the form, so the label sat a long way from
   the control it names and a click anywhere on that line toggled it.
   Measured live at 572px on seven controls on /settings, 322px on the
   attestation, 253px on the CSA and 150px on signup — every consent
   checkbox in the product except the two in a table cell.

   `accent-color` is the other half. Left at `auto` the browser picks
   the checked colour, and under color-scheme:dark Chrome picks its own
   blue — the single off-palette pixel in an app whose whole premise is
   that the only colour is the accent or a status.

   This replaces the one-off `.approval-card .approval-override input`
   from batch 1, which fixed the same bug in one place. */
input[type="checkbox"], input[type="radio"] {
  width: auto;
  /* `width: auto` alone is not enough and the first fix here proved it:
     six of the seven on /settings are GRID items in `.form-grid.two-col`,
     where auto means "stretch to the column" and they stayed 565px wide.
     `justify-self` is the grid answer, `flex: none` the flex one, and a
     checkbox lands in both kinds of parent in this app. */
  justify-self: start;
  flex: none;
  accent-color: var(--accent);
}

/* --- 3. A FIELDSET WILL NOT SHRINK ---------------------------------
   The UA gives every fieldset `min-inline-size: min-content`, which
   means it refuses to be narrower than its widest child wants to be —
   the classic reason a form breaks a page's horizontal scroll. There
   are 23 of them on /settings, each wrapping a template editor with a
   textarea in it, and /settings was already the one page in this app
   that scrolled sideways (Block 3 fixed that a different way). The UA
   box is neutralised here so `.tpl-block` is the only thing describing
   these. */
fieldset { min-inline-size: 0; border: 0; margin: 0; padding: 0; }

/* --- 4. THE FILE PICKER'S OWN BUTTON --------------------------------
   `input, select, textarea, button { font-family: inherit }` cannot
   reach a pseudo-element, so the "Choose File" button inside every file
   input rendered as Arial, white on #6B6B6B, with an `outset` bevel —
   UA chrome from a different decade sitting in a near-black console.
   The two file inputs are on /agent-leads/upload and /overflow, which
   belong to a later batch; the rule is one declaration block and the
   defect is live today, so it is fixed here and named there. */
::file-selector-button {
  font: inherit;
  background: var(--surface-raised);
  color: var(--text);
  border: 1px solid var(--border-input);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-9);
  margin-right: var(--space-9);
  cursor: pointer;
}
::file-selector-button:hover { background: var(--surface-hover); }

/* --- 5. A BARE LIST TAKES THE BROWSER'S 40px INDENT -----------------
   Every list in the app that carries a class is on the spacing scale.
   A classless <ul> takes the UA's
   `padding-inline-start: 40px`, a number that appears
   in no scale and lines up with nothing above or below it. Scoped to
   classless lists so `.onboarding-list`, `.mfa-steps`, `.mfa-codes` and
   `.legal-versions` keep the treatments they were given. */
.card ul:not([class]), .card ol:not([class]) {
  padding-left: var(--space-14);
  margin: var(--space-7) 0;
}
.card ul:not([class]) li, .card ol:not([class]) li {
  margin-bottom: var(--space-4);
}

/* /settings/reset stacked several <section class="card"> with no margin
   between them, so independent subjects rendered as one slab with
   hairlines across it. Fixed in the markup with `u-mb-12`, which is what
   every other page in the app already uses for exactly this; no new
   class was needed and none was added. */

/* --- 6. FIVE CLASSES THAT PROMISED A TREATMENT AND DELIVERED NONE ----
   A scan of every class every template writes against every selector in
   this file found five with no rule at all. A class that matches
   nothing is indistinguishable from a typo, and it reads to the next
   person as though something is being applied. They are spread across
   four screens in three different batches; the scan was app-wide, the
   guard that now enforces it is app-wide, and leaving four of them
   broken for three more commits to respect a batch boundary would be
   the wrong trade. Named here, each with what it was reaching for. */

/* /dashboard — a wrapping row of stage links that was relying on the
   whitespace between inline-blocks for its gaps. */
.stage-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin-bottom: var(--space-13);
}

/* /leads/<id> — the collapsed referral picker, which sat hard against
   the table under it. */
.referral-picker { margin-bottom: var(--space-10); }

/* `.cal-appt` IS GONE, from here and from calendar.html. Batch 2's
   orphan-class scan was right that the class had no rule; the fix was
   wrong. Every child of `.cal-appt` sets its own colour — the time is
   `.cal-time`, the title is an `<a>`, the outcome is a badge — and the
   element has NO direct text node, so `color: var(--text)` landed on
   nothing at all. Measured: zero text children. `.cal-ext` still
   paints, because its title is a bare `<span>` that inherits the muted
   ink, and that asymmetry is the whole distinction the legend
   describes. A rule that paints nothing is worse than an unstyled
   class: it reads as a decision. */

/* /jarvis — the ask form and the answer panel. The answer is the whole
   point of the page and arrived in an unstyled div. */
.jarvis-form { margin: 0; }
.jarvis-answer-card { margin-top: var(--space-12); }

/* --- 7. A HEADING LEVEL WITH NO RULE -------------------------------
   `<h4>` ships on /profit and /pay (the integrity panel's "Disposition
   mix") and the string "h4" did not appear in this stylesheet at all,
   so it took Chrome's `font-weight: bold` and 1.33em block margins — a
   weight and two spacings in no scale this app has. One step below h3,
   same construction. */
h4 {
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
  font-weight: 600;
  margin: var(--space-10) 0 var(--space-4);
}

/* --- 8. A LINK STYLED AS A BUTTON OUTRANKED BY :visited -------------
   `a:visited { color: var(--accent) }` is specificity (0,1,1) — one
   type selector plus one pseudo-class. `.btn`, `.btn-primary` and
   `.btn-danger` are all (0,1,0). The visited rule therefore OUTRANKS
   the component that is painting the button's background, and the
   component's own ink never lands: `a.btn.btn-primary:visited` would
   be --accent on --accent, 1.00:1, and `a.btn.btn-danger:visited`
   --accent on --danger, 1.36:1.

   HOW FAR THIS IS PROVEN, exactly. The cascade half is certain and
   arithmetic. The PAINTING half I could not confirm: engines restrict
   what a `:visited` rule may change for history-sniffing reasons, and
   a control page — literal colours, no custom properties, one URL this
   browser had loaded and one it had not — rendered both states
   identically, which says this environment does not resolve `:visited`
   at all rather than saying the app is safe. `getComputedStyle` is no
   help either: it deliberately reports the UNVISITED value, which is
   why the computed-style sweep that found the batch-1 textarea returns
   clean here and always would.

   So this is fixed on the cascade evidence and NOT reported as a
   confirmed visual defect. Three lines to make a component's ink
   outrank a link's, on links a VA clicks all day — Work on every queue
   row — is worth it whatever the engine decides. The `a` and
   `a:visited` rules themselves are untouched: tests/test_tokenization
   .py requires them to exist with a tokenized colour. */
.btn:visited { color: var(--text); }
.btn-primary:visited, .btn-danger-solid:visited { color: var(--text-inverse); }
.sidenav a:visited { color: var(--nav-text); }
.sidenav a.active:visited, .sidenav a:hover:visited { color: var(--text); }
.queue-worked a:visited, .queue-locked a:visited { color: var(--muted); }

/* --- 9. THE CLASS THAT ASKED FOR MONO AND GOT THE BODY STACK --------
   `.secret-value` exists to put a value in `--font-mono`. On the two
   "Field map (JSON)" editors it is a <textarea> inside `.form-grid`,
   and `.form-grid textarea { font: inherit }` is (0,1,1) against the
   class's (0,1,0) — so the shorthand won and the JSON rendered in the
   proportional body stack, where braces, quotes and indentation stop
   lining up. Measured live: `span.secret-value` on the same page
   renders `ui-monospace`, `textarea.secret-value` renders
   `-apple-system`. One page showing the class working and failing at
   the same time.

   A computed-style sweep does not catch this either — the element
   renders in a font the app legitimately uses, just not the one it
   asked for. */
textarea.secret-value, input.secret-value { font-family: var(--font-mono); }

/* --- 10. THE DENSE TABLE PADDING WAS DOCUMENTED AND NOT SHIPPED -----
   Batch 1 wrote, here and in SPEC.md, that `.table input` keeps its
   5/8 padding on purpose because a settings table is a grid of dozens
   of controls where the row height IS the density. That was true when
   it was written and false by the time it shipped: the same batch's
   base control rule is `input:not([type="checkbox"]):not([type="radio"])`,
   specificity (0,2,1) — a `:not()` takes the specificity of its
   argument — against `.table input`'s (0,1,1). The base rule won and
   every settings-table field rendered at 8/10. Measured: 8px 10px on
   the password, quota, fixed-cost, started-on and day-offset fields.

   The exclusion is repeated here so the documented density is the one
   that renders, and so the rule stops reaching checkboxes and radios
   in the same stroke. */
.table input:not([type="checkbox"]):not([type="radio"]) {
  padding: var(--space-4) var(--space-7);
}
.form-grid input[type="checkbox"], .form-grid input[type="radio"] {
  padding: 0;
}

/* --- 11. A LABEL THAT CANNOT TAKE A MARGIN --------------------------
   `.form-grid label { margin-top: var(--space-7) }` does nothing to
   the Name / Subject / Body labels in Settings -> Templates, because
   those labels are inside a <fieldset> rather than being direct grid
   items, so they are never blockified and a vertical margin on an
   inline box is ignored. Measured: display:inline, margin-top:8px,
   zero effect. The child combinator keeps this away from the <label>
   nested inside the block's <h3>, which has to stay inline beside its
   slug. */
.tpl-block > label { display: block; margin-top: var(--space-7); }

/* The same block's heading renders at two sizes for the same role: the
   Templates editors put the name straight in the <h3> (14px) and the
   VA-script editors wrap it in a <label>, which `.form-grid label`
   then drops to 13px. Same class, same structural position, two
   sizes. */
.tpl-block h3 label {
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  margin-top: 0;
}

/* ===================================================================
   BLOCK 5 — BATCH 2, GROUP 2: the legal, acceptance and MFA forms.
   These are read-then-sign screens and a one-time secret display. The
   instruction was not to over-design them; almost everything below is
   a correctness fix.
   =================================================================== */

/* A BLOCK cannot wear an underline as an affordance. `.copyable` says
   "click me" with `border-bottom: 1px dashed`, which is right on the
   six inline spans it was written for — a phone number in a sentence.
   On the recovery-codes page it is a <p>, so that dashed line drew
   across the full 498px of the card and read as a section divider
   rather than as the control that hands you the only copy of your
   backup codes you will ever be shown. A chip reads as a control. */
p.copyable:not(.mfa-secret) {
  display: inline-block;
  border: 1px dashed var(--border-input);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-9);
}

/* ===================================================================
   BLOCK 5 — BATCH 3, GROUP 1: /pay, /pay/statement, /suppressions.
   =================================================================== */

/* A value that must not break mid-token. The pay week table puts a date
   and a conditional note in one cell, and at eight columns the date was
   wrapping as "2026-08-" / "14" — the note may wrap, the date may not. */
.u-nowrap { white-space: nowrap; }

/* ===================================================================
   BLOCK 5 — BATCH 3, GROUP 3: the ops and public screens.
   =================================================================== */

/* A DISCLOSURE THAT DID NOT LOOK CLICKABLE. `cursor: pointer` was set on
   `summary` in exactly two scoped places — `.rebuttal summary` and
   `.rq-template summary` — and nowhere else, and Chrome's UA sheet does
   not supply it. The "raw body" toggle on /dead-letters, which is the
   whole point of that screen, had a text cursor. Measured: `auto`. */
details > summary { cursor: pointer; }

/* A ROW header is content, not a column label. `.table th` is the
   uppercase, 12px, tracked, surface-tinted treatment written for the band
   across the top of a table; applied to the new `<th scope="row">` cells
   in the pay breakdown it shrank "Appointments scheduled" to a shouted
   eyebrow and gave a body row the header band's background. The cells are
   headers for accessibility and body copy for the eye. */
.table th[scope="row"] {
  background: none;
  color: var(--text);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  letter-spacing: normal;
  text-transform: none;
  font-weight: 400;
}

/* ===================================================================
   BLOCK 5 — BATCH 3, GROUP 2: the lead desks.
   =================================================================== */

/* Moved from the Run Quotes block above so it outlives the base control
   rule on a specificity tie. A readonly field must not look typeable. */
.rq-fields input[readonly] { background: var(--panel); color: var(--muted); }

/* ===================================================================
   B5 — the agent-facing setup document, served as plain text.
   =================================================================== */

/* Preformatted markdown, never parsed. It wraps because the alternative
   on a phone is a horizontal scrollbar on every paragraph. */
.doc-text {
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  overflow-x: auto;
}

/* ===================================================================
   B6 — Send to VA: two tabs, and a bulk action that has to look like
   one. The tabs carry the count because the number IS the decision —
   "Send all 34" is a different act from "Send all 3".
   =================================================================== */

.tabs {
  display: flex;
  gap: var(--space-5);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-6);
}

.tab {
  padding: var(--space-4) var(--space-5);
  color: var(--muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  font-weight: 500;
}

.tab:hover { color: var(--text); }

/* The selected tab is heavier AND underlined: colour alone is the
   distinction people who cannot see the accent lose. */
.tab.active {
  color: var(--text);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

.tab-count {
  color: var(--muted);
  font-weight: 400;
}

.bulk-send { margin-bottom: var(--space-5); }

/* ===================================================================
   B7 — the assistant's context card. It replaces an email history they
   can no longer see, so it has to read as the answer to "what happened
   with this person", not as a footnote under the phone number.
   =================================================================== */

.work-context { margin-bottom: var(--space-6); }

/* ===================================================================
   B11 RUN 1 — THE REDESIGN.

   docs/b11-design-system.md is the contract; this block implements it.
   NO COLOUR VALUE MOVED. Every token in :root keeps the value BLOCK 4
   measured, all 61 contrast pairs still hold, and the redesign is
   entirely structure, hierarchy and restraint. Anything here that looks
   like a new colour is a new USE of an old one, and the design doc
   names which measured pair it lands on.

   Run 2 extends this after B8-B10 land. Nothing below reserves space,
   styling or a token for a screen that does not exist yet.
   =================================================================== */

/* --- Criterion 2: hierarchy through size and weight -----------------
   THE TYPE SCALE HAD SAID "13/20 = table cell default" SINCE BLOCK 4
   AND THE STYLESHEET HAD NEVER IMPLEMENTED IT. `.table td` set no size
   at all, so it inherited body's step 4 — which meant a page's body
   copy, a table cell and a card's sub-line were all one voice, and the
   only thing below them was the 12px header band. One step of
   separation is what makes a table read as data inside a page rather
   than as more page.

   This is not a taste call. It is the scale's own documented intent,
   applied to the four rules that had been quietly opting out of it. */
.table td {
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
}

/* --- The nav is SIX FLAT ROWS (Graphite v2) --------------------------
   B11 run 1 grouped the eighteen links under Work / Money / System /
   Jarvis, and that block lived here. The redesign's owner chose six flat
   items with everything else folded in (docs/redesign-log.md, commit 2),
   so .nav-group and .nav-group-label are gone — the row primitive is
   `.sidenav a` above and the mode card is `.sidenav .nav-mode-card`.
   `.sidenav .nav-more` is gone too: the "fold-in list" it styled was the
   old nav kept alive one shade dimmer, which is not a fold-in, so the
   rows and their rules were deleted together. A test asserts neither the
   group rules nor the more-list come back. */

/* --- The house control: Prev / Today / Next + Day / Week ------------
   Promoted out of .cal-toolbar / .cal-nav / .cal-views / .cal-heading,
   which were four page-specific names for a pattern the app will reach
   for again. ANY screen with a period to step through or a
   mutually-exclusive view to pick uses this and nothing else.

   The joined edge is the whole idea: three loose buttons read as three
   choices, one segmented control reads as one axis. */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-9);
  margin-bottom: var(--space-11);
}
.toolbar-title {
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  letter-spacing: var(--tracking-title);
  font-weight: 600;
}
.btn-group { display: inline-flex; }
/* Collapse the shared edges. calc() rather than a negative token: the
   spacing scale is a scale of distances and -1px is not one. */
.btn-group .btn {
  border-radius: 0;
  margin-left: calc(var(--space-1) * -1);
}
.btn-group .btn:first-child {
  margin-left: 0;
  border-top-left-radius: var(--radius);
  border-bottom-left-radius: var(--radius);
}
.btn-group .btn:last-child {
  border-top-right-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
}
/* THE ACCESSIBLE STATE IS THE STYLING HOOK. Before B11 the current
   view was painted by a hand-written `btn-primary` in the template and
   announced by a separately hand-written `aria-current` — two spellings
   of one fact, either of which could be forgotten. Now the template
   states which segment is current and the stylesheet decides what
   current looks like, so they cannot disagree.

   This is a legitimate sparing use of the accent: exactly one segment
   in a group can carry it, and a segmented control with no filled
   segment does not say where you are. */
.btn-group .btn[aria-current="page"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-inverse);
  font-weight: 600;
  position: relative;
  z-index: 1;
}
.btn-group .btn[aria-current="page"]:hover { background: var(--accent-hover); }

/* ====================================================================
   GRAPHITE v2 PAGE PATTERNS (mock 1a). Four shapes every list screen is
   built from, so Today, Leads, Reports and the extrapolated screens share
   one vocabulary instead of each inventing a header.
   ==================================================================== */

/* .page-head: title + one-line subtitle on the left, actions on the
   right, hairline under. Replaces a bare <h1> at the top of a screen. */
.page-head {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  padding-bottom: var(--space-13);
  margin-bottom: var(--space-12);
  border-bottom: 1px solid var(--border);
}
.page-head h1 { margin: 0; }
.page-head .page-sub {
  color: var(--muted);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  margin-top: var(--space-2);
}
.page-head .page-actions {
  margin-left: auto;
  display: flex;
  gap: var(--space-9);
  align-items: center;
}
.page-head .page-actions input[type="search"],
.page-head .page-actions input[type="text"] { width: 230px; }

/* .kpi-strip: 4-6 equal tiles, label over a 28px figure. The figure
   takes an intent colour ONLY when it is a call to action (replies
   waiting = amber); a plain count stays --text. */
.kpi-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-9);
  margin-bottom: var(--space-13);
}
.kpi {
  flex: 1;
  min-width: 150px;
  padding: var(--space-11) var(--space-12);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.kpi .kpi-label { color: var(--muted); font-size: var(--font-size-3); line-height: var(--line-height-3); }
.kpi .kpi-value {
  font-size: var(--font-size-6);
  line-height: var(--line-height-6);
  letter-spacing: var(--tracking-display);
  font-weight: 600;
  margin-top: var(--space-2);
  font-variant-numeric: tabular-nums;
}
.kpi .kpi-value.is-attention { color: var(--warn); }
.kpi .kpi-value.is-negative { color: var(--danger); }
.kpi .kpi-value a { color: inherit; text-decoration: none; }
.kpi .kpi-value a:hover { color: var(--accent); }

/* .section-head: a 16/600 title with an optional dim hint beside it,
   above a list. Not an <h2> in a card — the list IS the section. */
.section-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-10);
  margin: var(--space-13) 0 var(--space-9);
}
.section-head h2 { margin: 0; font-size: var(--font-size-5); line-height: var(--line-height-5); }
.section-head .section-hint { color: var(--muted); font-size: var(--font-size-3); line-height: var(--line-height-3); }

/* .action-row: THE queue row. A 4px intent rail, a title with mono tags,
   one line of muted context that truncates, and exactly ONE primary
   action on the right — the mock's rule is that every row has one next
   thing to do. `.is-attention` / `.is-negative` colour the rail AND tint
   the border; the default is the plain panel. */
.action-list { display: flex; flex-direction: column; gap: var(--space-9); }
.action-row {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  padding: var(--space-12) var(--space-13);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.action-row .row-rail {
  width: 4px;
  align-self: stretch;
  min-height: 42px;
  border-radius: 3px;
  background: var(--surface-raised);
  flex: none;
}
.action-row.is-attention { border-color: var(--warn-border); }
.action-row.is-attention .row-rail { background: var(--warn); }
.action-row.is-negative { border-color: var(--danger-border); }
.action-row.is-negative .row-rail { background: var(--danger); }
.action-row.is-positive .row-rail { background: var(--accent); }
.action-row .row-main { min-width: 0; flex: 1; }
.action-row .row-title {
  font-size: var(--font-size-8);
  line-height: var(--line-height-8);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: var(--space-7);
  flex-wrap: wrap;
}
.action-row .row-title a { color: inherit; text-decoration: none; }
.action-row .row-title a:hover { color: var(--accent); }
.action-row .row-sub {
  color: var(--muted);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  margin-top: var(--space-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.action-row .row-actions { display: flex; gap: var(--space-7); flex: none; align-items: center; }
.action-row form { display: contents; }

/* .tag: the mock's mono pill in an intent hue, for use inside a
   .row-title. .badge already carries this treatment for STATUS values
   built from data; .tag is for hand-placed labels ("HOT", "QUOTE",
   "JARVIS DRAFT") and takes an explicit intent class. */
.tag {
  display: inline-block;
  padding: var(--space-2) var(--space-6);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--font-size-1);
  line-height: var(--line-height-1);
  font-weight: 500;
  letter-spacing: var(--tracking-badge);
  text-transform: uppercase;
  background: var(--status-neutral-bg);
  color: var(--status-neutral-text);
  vertical-align: middle;
}
.tag-attention { background: var(--status-attention-bg); color: var(--status-attention-text); }
.tag-accent { background: var(--status-positive-bg); color: var(--status-positive-text); }
.tag-negative { background: var(--status-negative-bg); color: var(--status-negative-text); }

/* .eyebrow: the mock's 11px mono tracked uppercase section label
   (STAGE, WORKING LANE, EARLIER). --muted, not the mock's .42 alpha —
   see the token block. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--font-size-1);
  line-height: var(--line-height-1);
  font-weight: 600;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-label);
  margin-bottom: var(--space-9);
}

/* ====================================================================
   LANE RAIL + GRID LIST (mock 1b). The Leads screen: a 196px rail of lane
   links with mono counts on the left, a dense grid of lead rows on the
   right. Reused by any screen with a lane/section rail.
   ==================================================================== */
.lane-layout {
  display: flex;
  gap: var(--space-16);
  align-items: flex-start;
}
.lane-rail {
  width: 196px;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-right: var(--space-11);
  border-right: 1px solid var(--border);
  position: sticky;
  top: var(--space-14);
}
.lane-rail .eyebrow { margin: var(--space-11) var(--space-5) var(--space-3); }
.lane-rail .eyebrow:first-of-type { margin-top: var(--space-9); }
.lane-link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  min-height: var(--control-height-sm);
  padding: 0 var(--space-9);
  border-radius: 7px;
  color: var(--text-2);
  text-decoration: none;
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
}
.lane-link:hover { background: var(--nav-hover-bg); color: var(--text); }
.lane-link.is-current { background: var(--surface-raised); color: var(--text); font-weight: 600; }
.lane-link-sm { min-height: 36px; font-size: var(--font-size-3); line-height: var(--line-height-3); }
.rail-count {
  font-family: var(--font-mono);
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
  font-weight: 500;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.lane-select { margin-top: var(--space-12); }
.lane-select select { width: 100%; }
.lane-main { flex: 1; min-width: 0; }

.grid-list { display: flex; flex-direction: column; }
.grid-head, .grid-row {
  display: grid;
  grid-template-columns: 1.6fr .8fr .7fr .9fr 120px;
  gap: var(--space-9);
  align-items: center;
  padding: var(--space-11) var(--space-12);
}
.grid-head {
  padding-top: var(--space-3);
  padding-bottom: var(--space-7);
  font-family: var(--font-mono);
  font-size: var(--font-size-2);
  line-height: var(--line-height-2);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-label);
}
.grid-row {
  border-top: 1px solid var(--border);
  position: relative;
}
.grid-row:hover { background: var(--surface-hover); }
.grid-row.is-attention { background: var(--warn-tint); }
.grid-row.is-negative { background: var(--danger-tint); }
.grid-lead { min-width: 0; }
.grid-name {
  font-size: var(--font-size-7);
  line-height: var(--line-height-7);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: var(--space-6);
  flex-wrap: wrap;
}
.grid-name a { color: inherit; text-decoration: none; }
.grid-name a:hover { color: var(--accent); }
.grid-contact {
  color: var(--muted);
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
  margin-top: var(--space-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.grid-contact a { color: inherit; }
.grid-cell { color: var(--text-2); font-size: var(--font-size-4); line-height: var(--line-height-4); }
.grid-sub { color: var(--muted); font-size: var(--font-size-3); line-height: var(--line-height-3); margin-top: var(--space-1); }
.grid-actions { display: flex; justify-content: flex-end; }
.grid-actions .btn { width: 100%; }
/* The disclosure sits in the row's grid as a full-width last cell. */
.grid-more { grid-column: 1 / -1; font-size: var(--font-size-3); line-height: var(--line-height-3); }
.grid-more summary { cursor: pointer; list-style: none; display: inline-block; }
.grid-more summary::-webkit-details-marker { display: none; }
.grid-more summary::before { content: "▸ "; color: var(--text-dim); }
.grid-more[open] summary::before { content: "▾ "; }
.grid-more-body {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-9) var(--space-14);
  padding: var(--space-7) 0 var(--space-3);
  color: var(--text-2);
}
.grid-more-body .lead-fact-label { color: var(--muted); }
.grid-note { flex-basis: 100%; color: var(--text-2); }

/* Settings (mock 1g): the section IS the card, 10px, 18/20 padding, with
   the h2 at the card-title step. .settings-note is the quiet footer that
   tells an agent what is managed for them. */
.settings-section > h2:first-child {
  margin-top: 0;
  font-size: var(--font-size-8);
  line-height: var(--line-height-8);
  letter-spacing: var(--tracking-subhead);
}
.settings-toc .toc-divider { color: var(--text-dim); margin: 0 var(--space-5); }
.settings-note {
  margin: var(--space-15) 0;
  padding: var(--space-11) var(--space-13);
  background: var(--nav-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--muted);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
}
#operator { margin-top: var(--space-20); }

/* ====================================================================
   LEAD DETAIL — TWO PANES (mocks 1c/1d). History and the log forms in the
   main column, a 340px sticky aside on --nav-bg with STAGE / CONTACT /
   DETAILS. Cards inside the main column are the ordinary .card.
   ==================================================================== */
.lead-back { width: var(--control-height-sm); padding: 0; margin-bottom: var(--space-9); }
.lead-head {
  display: flex;
  align-items: center;
  gap: var(--space-11);
  padding-bottom: var(--space-12);
  margin-bottom: var(--space-13);
  border-bottom: 1px solid var(--border);
}
.lead-head-main { min-width: 0; flex: 1; }
.lead-head h1 { margin: 0; display: flex; align-items: center; gap: var(--space-7); flex-wrap: wrap; }
.lead-head .page-sub { margin-top: var(--space-2); }
.lead-layout {
  display: flex;
  gap: var(--space-16);
  align-items: flex-start;
}
.lead-main { flex: 1; min-width: 0; }
.lead-aside {
  width: var(--aside-width);
  flex: none;
  position: sticky;
  top: var(--space-14);
  display: flex;
  flex-direction: column;
  gap: var(--space-13);
  padding: var(--space-15) var(--space-15);
  background: var(--nav-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.lead-aside .aside-panel .sub { margin: var(--space-3) 0; font-size: var(--font-size-4); line-height: var(--line-height-4); }
.lead-aside .aside-panel .sub strong { color: var(--text); font-weight: 500; }
@media (max-width: 1100px) {
  .lead-layout { flex-direction: column; }
  .lead-aside { width: 100%; position: static; }
}
/* the timeline is the first thing in the main column, and it is a list */
.lead-main .timeline-card { margin-top: 0; }

/* ====================================================================
   PIPELINE BOARD (mock 1e). Columns per stage, a stack of small cards in
   each; the whole card is the link. Compact variant for the Negative
   lane. The Cold bucket strip is the existing .bucket-strip, now with
   each bucket a link into the Leads rail filter.
   ==================================================================== */
.board {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-11);
  margin-bottom: var(--space-13);
}
.board-compact { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.board-col-head-compact { flex-wrap: wrap; row-gap: var(--space-3); }
.board-col { display: flex; flex-direction: column; gap: var(--space-9); min-width: 0; }
.board-col-head { display: flex; align-items: center; gap: var(--space-7); min-height: var(--line-height-7); }
.board-col-title { font-size: var(--font-size-7); line-height: var(--line-height-7); font-weight: 600; }
.board-card {
  display: block;
  padding: var(--space-10) var(--space-11);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--text);
  text-decoration: none;
}
.board-card:hover { background: var(--surface-hover); border-color: var(--border-strong); }
.board-card.is-attention { border-color: var(--warn-border); }
.board-card.is-positive { border-color: var(--accent-border); }
.board-card.is-negative { border-color: var(--border); }
.board-card-name { font-size: var(--font-size-7); line-height: var(--line-height-7); font-weight: 600; display: flex; gap: var(--space-6); align-items: center; flex-wrap: wrap; }
.board-card-sub { color: var(--muted); font-size: var(--font-size-3); line-height: var(--line-height-3); margin-top: var(--space-3); }
.board-card-sub.is-attention { color: var(--warn); }
.board-card.is-positive .board-card-sub { color: var(--accent); }
.board-more, .board-empty { font-size: var(--font-size-3); line-height: var(--line-height-3); padding: var(--space-5) var(--space-3); margin: 0; }
.bucket-link { display: flex; flex-direction: column; align-items: center; color: inherit; text-decoration: none; }
.bucket-link:hover .bucket-count { color: var(--accent); }
@media (max-width: 900px) { .board { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* VA console (mock 1f): the head's one CTA is the number — copy it, dial
   it in Ringy. A .btn-primary-shaped .copyable at 48px, because it is the
   thing the whole screen exists for; the mock's "Call in Ringy". The
   existing .work-nav / .script-section rules carry the rest. */
.work-cta { margin-left: auto; flex: none; }
.work-dial { min-height: 48px; padding: 0 var(--space-15); font-size: var(--font-size-5); line-height: var(--line-height-5); border-radius: 9px; cursor: pointer; }
.script-section p strong { color: var(--accent); }

/* Auth (mock 1i): brand tile, 28/600 title, 52px fields, one Continue. */
.auth-brand { display: flex; align-items: center; gap: var(--space-9); font-size: var(--font-size-8); line-height: var(--line-height-8); font-weight: 600; letter-spacing: var(--tracking-subhead); margin-bottom: var(--space-20); }
.auth-brand-tile { width: 30px; height: 30px; border-radius: var(--radius); background: var(--accent); flex: none; }
.auth-sub { margin: var(--space-5) 0 var(--space-17); font-size: var(--font-size-7); line-height: var(--line-height-7); }
.auth-form input:not([type="hidden"]) { min-height: 52px; font-size: var(--font-size-5); line-height: var(--line-height-5); border-radius: 9px; }
.auth-submit { min-height: 52px; font-size: var(--font-size-5); line-height: var(--line-height-5); border-radius: 9px; }
.auth-note { margin-top: var(--space-17); padding: var(--space-12) var(--space-13); background: var(--panel); border: 1px solid var(--border); border-radius: 9px; font-size: var(--font-size-4); line-height: var(--line-height-4); }
.auth-note-title { font-weight: 600; margin-bottom: var(--space-5); }

/* .empty: the mock's quiet "nothing here" panel. */
.empty {
  padding: var(--space-13) var(--space-14);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--muted);
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
}

/* .lead-fact-label survives the B11 card (Graphite v2 replaced the card
   with mock 1b's grid list — see .grid-list above): it labels the money
   band inside a grid row's disclosure. Everything else in the old
   .lead-card* family had no renderer left and was removed. */
.lead-fact-label {
  display: block;
  color: var(--muted);
  font-size: var(--font-size-2);
  line-height: var(--line-height-2);
}

/* One column once two cards no longer fit. --card-min is 340px and the
   grid's own auto-fill handles the reflow, so this only has to fix the
   two INNER grids, which would otherwise squeeze four money figures
   into a phone's width. */
@media (max-width: 700px) {
  .lead-money { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lead-facts { grid-template-columns: 1fr; }
}

/* ===================================================================
   B9 — the appointment tracker and its two calendars.

   The outcome pills reuse the status palette rather than inventing
   hues: an appointment's outcome IS a status, and a second vocabulary
   of colours for the same idea is how a palette stops meaning
   anything. `scheduled` borrows the scheduled tint it is named after.
   =================================================================== */

.badge-appt-scheduled { background: var(--status-scheduled-bg); color: var(--status-scheduled-text); }
.badge-appt-showed { background: var(--status-positive-bg); color: var(--status-positive-text); }
.badge-appt-no_show { background: var(--status-negative-bg); color: var(--status-negative-text); }
.badge-appt-cancelled { background: var(--status-neutral-bg); color: var(--status-neutral-text); }
.badge-appt-sold { background: var(--status-positive-bg); color: var(--status-positive-text); }
.badge-appt-not_sold { background: var(--status-attention-bg); color: var(--status-attention-text); }

/* The actions cell stacks its little forms rather than letting them
   collapse onto one line and read as a sentence. */
.appt-actions { display: flex; flex-direction: column; gap: var(--space-4); }

/* The calendar rows. Not `.lead-card` — a calendar entry is one line
   about one day, not a record with four bands, and reusing the card
   would have made the two screens look like the same thing. */
.appt-days { list-style: none; margin: 0; padding: 0; }
.appt-row {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-9) var(--space-11);
  margin-bottom: var(--space-7);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-4) var(--space-10);
}
.appt-when { display: flex; align-items: center; gap: var(--space-7); }
.appt-date {
  font-size: var(--font-size-4);
  line-height: var(--line-height-4);
  font-weight: 600;
  letter-spacing: var(--tracking-title);
}
.appt-who { display: flex; align-items: center; gap: var(--space-7); }
.appt-people {
  font-size: var(--font-size-2);
  line-height: var(--line-height-2);
}
.appt-money {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-9);
  align-items: baseline;
  justify-content: flex-end;
}
.appt-figure {
  font-size: var(--font-size-3);
  line-height: var(--line-height-3);
  font-weight: 600;
}
/* The structural label tier: uppercase, tracked, --text-label. */
.appt-figure-label {
  display: block;
  color: var(--text-label);
  font-size: var(--font-size-1);
  line-height: var(--line-height-1);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-weight: 600;
}

@media (max-width: 700px) {
  .appt-row { grid-template-columns: 1fr; }
  .appt-money { justify-content: flex-start; }
}

/* The VA console's CTA wears `.copyable btn btn-primary` at once, and
   `.copyable:hover` (0,2,0) tied with `.btn-primary:hover` (0,2,0) — so
   the later rule won and the teal primary went grey the moment you
   reached for it, on the one control that screen exists for. Naming both
   classes (0,3,0) breaks the tie in the button's favour for hover AND
   focus; the copy affordance keeps its own :focus-visible ring. */
.copyable.btn-primary:hover,
.copyable.btn-primary:focus {
  background: var(--accent-hover);
}

/* An event's `detail` used to print raw into one cell — fine for
   `k=v k=v`, unreadable for the JSON a reset/delete row stores. The
   filter splits it; these are the rows. The key is the mono gutter ink
   at 12px, which is what --text-dim exists for. */
.kv-list { display: flex; flex-direction: column; gap: var(--space-2); }
.kv { display: flex; gap: var(--space-7); align-items: baseline; }
.kv-k {
  flex: none;
  min-width: 120px;
  font-family: var(--font-mono);
  font-size: var(--font-size-2);
  line-height: var(--line-height-2);
  color: var(--text-dim);
}
.kv-v { color: var(--text-2); word-break: break-word; }


/* The lead-webhook URL. It is one long unbreakable token, so it has to be
   allowed to wrap inside the card rather than push the page sideways. */
.webhook-url { margin-top: var(--space-7); word-break: break-all; }

/* A campaign row inside its source's row on /stats. Indented and dimmed
   so the eye reads the source line as the total and these as its parts. */
.sub-row td { color: var(--text-2); }
.sub-mark { color: var(--text-dim); }

/* The setup wizard: one step per screen, with the whole walk visible so a
   reader can see what is done without clicking through it. */
.setup-progress { display: flex; gap: var(--space-10); align-items: baseline;
  margin-bottom: var(--space-12); }
.setup-rail { list-style: none; display: flex; flex-wrap: wrap;
  gap: var(--space-10); padding: 0; margin: 0 0 var(--space-14); }
.setup-rail li { display: flex; gap: var(--space-5); align-items: baseline; }
.setup-rail li.is-current a { color: var(--text); font-weight: 600; }
.setup-rail li.is-done a { color: var(--text-2); }
.setup-actions { display: flex; gap: var(--space-10); margin-top: var(--space-14); }

/* VA PRACTICE MODE.
   Loud on purpose. The requirement is "visually unmistakable as practice —
   a persistent banner, not a subtle badge", and the failure this prevents
   is an agent training on the console, forgetting, and later believing a
   real outcome was logged. It uses the warning role rather than the accent
   so it cannot be mistaken for a primary action, and it is sticky so it
   survives scrolling down a long script. */
.practice-banner {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-10);
  margin: 0 0 var(--space-12);
  padding: var(--space-10) var(--space-12);
  border: var(--space-2) solid var(--warn);
  border-radius: var(--radius);
  background: var(--warn-bg);
  color: var(--text);
}
.practice-banner strong {
  padding: var(--space-2) var(--space-9);
  border-radius: var(--radius-pill);
  font-family: var(--font-mono);
  font-size: var(--font-size-1);
  line-height: var(--line-height-1);
  letter-spacing: var(--tracking-eyebrow);
  background: var(--warn);
  color: var(--bg);
  white-space: nowrap;
}
.practice-banner span { flex: 1 1 auto; }

/* TIME TO FIRST CALL — Today's uncalled cards.
   A full card rather than a row, because the whole point is that nothing
   needs clicking: the number, where they are, where they came from and
   the script are all here. The clock is the loudest thing on the card
   after the name, and it goes red past the agent's own target. */
.newlead-list { display: grid; gap: var(--space-10); margin-bottom: var(--space-14); }
.newlead-card { border-left: var(--space-3) solid var(--accent); }
.newlead-card.is-called { opacity: 0.5; }
.newlead-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-10);
  margin-bottom: var(--space-9);
}
.newlead-name { margin: 0 0 var(--space-3); }
.newlead-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-5); }
.newlead-clock { text-align: right; white-space: nowrap; }
.newlead-mins {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--font-size-6);
  line-height: var(--line-height-6);
  color: var(--text);
}
.newlead-mins.is-late { color: var(--danger); }
.newlead-mins-label {
  font-size: var(--font-size-1);
  line-height: var(--line-height-1);
  color: var(--muted);
}
.newlead-context { margin-bottom: var(--space-9); }
