/* ============================================================================
   The Vault System — portal components (D-3)
   Buttons, status chips, tables, cards, stat tiles, form fields, toasts.
   Tokens only. A literal themed colour in this file is a review-blocker (§1b).

   HOW THIS FILE IS MEANT TO BE USED
   Two vocabularies, on purpose:

   1. `.fb-*` classes — the Vault components. New markup uses these.
   2. Overrides of the Bootstrap/FlatLab classes the 26 pages already carry
      (.btn-primary, .badge-success, .table, .card, .form-control, .alert).
      Pages adopt the design without being rewritten; D-5 and D-6 replace the
      markup page by page, and D-6.5 deletes FlatLab wholesale afterwards.

   SPECIFICITY IS NOT OPTIONAL HERE.
   D-2's lesson: FlatLab's `ul.sidebar-menu li a.active` (0,2,3) silently beat
   a plain `.sidebar-menu > li > a.active` (0,2,2), so the active pill rendered
   #35404d for as long as anyone had looked at it — and the screenshot looked
   fine. Every override below either states the rule it is beating or is scoped
   high enough to win outright. Verify with computed styles, never by eye.

   THE INVARIANT (§1b) — never violate, never theme:
     orange --fb-alert    = A DECISION IS PENDING. Nothing else is ever orange.
     green  --fb-verified = approved / protected. Nothing else is ever green.
   The decision pair is green approve vs considered-NEUTRAL deny — never two
   alarming colours side by side.
   ========================================================================= */

/* ── Buttons ─────────────────────────────────────────────────────────────
   Three roles, per DESIGN_SYSTEM.md: primary (brand), quiet (surface +
   line), destructive (denied). Radius 10, the control token. */

.btn,
.btn.btn-default,
.btn.btn-primary,
.btn.btn-danger,
.btn.btn-success,
.btn.btn-info,
.btn.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 38px;
  padding: 0 16px;
  font: 600 13.5px/1 var(--fb-font-ui);
  border-radius: var(--fb-radius-control);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

/* Primary — the brand action.
 *
 * D-11: `--fb-on-brand`, not `--fb-on-solid`. Q-10 introduced the per-semantic
 * on-tokens and vault-tokens.css says in as many words that they REPLACE "the
 * old blanket --fb-on-solid: #ffffff", quoting 3.49:1 for white on brand in
 * dark as one of the numbers that disproved it. The token landed; this call
 * site was never repointed, so the button kept rendering the exact failure the
 * token comment describes — 3.49:1 on the Sign in button, the primary action
 * of the sign-in screen. Found by auditing the composed page, not the tokens:
 * check-contrast.mjs passes because it evaluates the PAIR
 * (--fb-on-brand, --fb-brand) that this rule was not using.
 *   light  white on #2f6bff  4.50 -> 4.50 (--fb-on-brand is white here)
 *   dark   white on #4f83ff  3.49 -> 4.93 (--fb-on-brand is ink here)  */
.btn.btn-primary,
.fb-btn-primary {
  background: var(--fb-brand);
  border-color: var(--fb-brand);
  color: var(--fb-on-brand);
}

/* Hover takes the brand fill one step deeper — E-1b, the Q-19 ruling.
 *
 * E-1 pointed this at --fb-nav-active, which is right in light (Harbor
 * #17607e -> #124d66) and inverts in dark: --fb-nav-active is a DEEP ice
 * carrying LIGHT ink, so hovering flipped a bright button to a dark one and
 * its text with it. Nothing failed AA; the button changed identity rather
 * than state, which is the one thing a hover may not do.
 *
 * --fb-brand-hover is the token that fixes it: light keeps today's #124d66,
 * dark takes a derived darker ice (#4ca8c9, same relative-luminance step as
 * the light pair) that is still bright and still carries --fb-on-brand ink.
 * The ink token therefore no longer switches on hover either — it is
 * --fb-on-brand at rest AND on hover, in both themes, 9.21:1 light /
 * 6.63:1 dark, proven by the pairing check-contrast.mjs gained at E-1b. */
.btn.btn-primary:hover,
.fb-btn-primary:hover {
  background: var(--fb-brand-hover);
  border-color: var(--fb-brand-hover);
  color: var(--fb-on-brand);
}

/* Quiet — the default. Surface with a line, so a page full of buttons has one
   obvious primary and the rest recede. */
.btn.btn-default,
.btn.btn-secondary,
.fb-btn-quiet {
  background: var(--fb-surface);
  border-color: var(--fb-line);
  color: var(--fb-ink);
}

.btn.btn-default:hover,
.btn.btn-secondary:hover,
.fb-btn-quiet:hover {
  background: var(--fb-neutral-wash);
  border-color: var(--fb-line);
  color: var(--fb-ink);
}

/* Destructive.
 * D-11: same repointing as .btn-primary. White on the dark theme's #f27d72
 * was 2.64:1 — the worst of the set, on the button that deletes things.
 *   light  white on #b3261e  6.54 -> 6.54 (--fb-on-denied is white here)
 *   dark   white on #f27d72  2.64 -> 6.51 (--fb-on-denied is ink here)   */
.btn.btn-danger,
.fb-btn-destructive {
  background: var(--fb-denied);
  border-color: var(--fb-denied);
  color: var(--fb-on-denied);
}

.btn.btn-danger:hover,
.fb-btn-destructive:hover {
  background: var(--fb-denied);
  border-color: var(--fb-denied);
  color: var(--fb-on-denied);
  filter: brightness(.92);
}

/* Approve — the ONLY green button, and only ever for approving. Paired with a
   NEUTRAL deny, never with a red one: two alarming colours side by side make
   the safe choice and the destructive choice equally loud. */
/* D-11: white on the verified fill was 3.17:1 in light and 2.38:1 in dark —
 * the same 2.38 vault-tokens.css quotes as the number that retired the blanket
 * --fb-on-solid. Now --fb-on-verified: 5.43 light, 7.21 dark. */
/* D-11: the hover used to swap the FILL to --fb-verified-ink, which is a dark
 * green in light but a BRIGHT one in dark (#4fd18b) — so white-on-hover
 * measured 1.94:1 in dark, the worst pairing in the portal, on the button that
 * releases money. No existing on-token fixes it, because the right answer
 * flips per theme while every on-token is one value.
 *
 * So the hover stops swapping the hue and darkens the fill instead, which is
 * the pattern .btn-danger:hover above already uses — the on-token stays valid
 * because the fill stays the same colour, only dimmer:
 *   light  #03a74b -> #039a45, --fb-on-verified 4.68  (still >= 4.5)
 *   dark   #2fbf71 -> #2bb068, --fb-on-verified 6.15
 * Fill hues are untouched in the token file; this is a state, not a repaint. */
.btn.btn-success,
.fb-btn-approve {
  background: var(--fb-verified);
  border-color: var(--fb-verified);
  color: var(--fb-on-verified);
}

.btn.btn-success:hover,
.fb-btn-approve:hover {
  background: var(--fb-verified);
  border-color: var(--fb-verified);
  color: var(--fb-on-verified);
  filter: brightness(.92);
}

/* FlatLab's .btn-info is an undifferentiated fifth colour with no meaning in
   this system. Render it quiet rather than inventing a semantic for it. */
.btn.btn-info {
  background: var(--fb-surface);
  border-color: var(--fb-line);
  color: var(--fb-ink);
}

.btn.btn-info:hover {
  background: var(--fb-neutral-wash);
  color: var(--fb-ink);
}

.btn.btn-sm { height: 32px; padding: 0 12px; font-size: 12.5px; }
.btn.btn-xs { height: 27px; padding: 0 10px; font-size: 12px; }

/* ── Focus ─────────────────────────────────────────────────────────────────
   DESIGN_SYSTEM.md §Shape & elevation: "Focus: 2px solid --fb-brand, offset
   2px, on every interactive element."

   E-1b replaces what D-3 shipped, which was `box-shadow: 0 0 0 3px
   var(--fb-brand-wash)` — a 3px ring in the brand WASH, not the brand. On the
   light theme that ring is #e3eef2 against a #ffffff card: 1.13:1, which is
   not a focus indicator, it is a rumour of one. The spec's own value is the
   fill at 2px, and an `outline` rather than a `box-shadow` because an outline
   follows the element's border-box on a wrapped inline (a link that breaks
   across two lines gets a ring around both halves) and because it survives
   `overflow: hidden` ancestors that clip a shadow.

   "Every interactive element" is stated once, here, rather than re-typed per
   component, so this reaches controls no .fb-* rule covers — table links,
   pagination, DataTables' own controls, the avatar menu. The per-component
   rules below add nothing but a matching border/colour where the resting
   state needs one; none of them re-declares the ring.

   THE !important IS NOT DEFENSIVE — IT IS CANCELLING ONE KNOWN RULE.
   `css/bootstrap-reset.css` line 138 is:

       input, textarea, select, button { outline: none !important; }

   An !important cannot be beaten by specificity, only by another !important,
   so until now NOTHING in this portal could draw a focus ring on a button or
   a form field: every button on every page and every field in every form had
   no focus indicator at all (WCAG 2.4.7). D-3's box-shadow ring is what hid
   that — a shadow is not an outline, so it slipped past the reset while
   rendering at 1.13:1 and telling nobody. This is the SECOND !important any
   Vault stylesheet uses; the first is the `.form-control { color }` cancel a
   few hundred lines below, added for the identical reason, and both
   disappear with FlatLab. Anchors are not in the reset's selector list, so
   they take the ring without it.

   ONE documented exception, inside the nav mass — see vault-chrome.css. */
a:focus-visible,
summary:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 2px solid var(--fb-brand);
  outline-offset: 2px;
}

button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--fb-brand) !important;
  outline-offset: 2px;
}

.btn:focus-visible,
.fb-btn-primary:focus-visible,
.fb-btn-quiet:focus-visible {
  outline: 2px solid var(--fb-brand) !important;
  outline-offset: 2px;
  box-shadow: none;
}

.btn:disabled,
.btn.disabled {
  opacity: .55;
  cursor: not-allowed;
}

/* ── Status chips ────────────────────────────────────────────────────────
   11.5px/600 caps on a wash, radius from the pill token (spec §Shape, and
   §Signature 5 for the four states). The colour IS the meaning — see the
   invariant. E-1b took the weight from 700 to the 600 the spec states; the
   caps, the size and the pill radius were already right. */

.fb-chip,
.badge.badge-success,
.badge.badge-warning,
.badge.badge-danger,
.badge.badge-secondary,
.badge.badge-default,
.badge.badge-info {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 11px;
  font: 600 11.5px/1.35 var(--fb-font-ui);
  letter-spacing: .3px;
  text-transform: uppercase;
  border-radius: var(--fb-radius-pill);
  white-space: nowrap;
  border: none;
}

/* APPROVED / protected. */
.fb-chip-approved,
.badge.badge-success {
  background: var(--fb-verified-wash);
  color: var(--fb-verified-ink);
}

/* PENDING — the only orange in the system. */
.fb-chip-pending,
.badge.badge-warning {
  background: var(--fb-alert-wash);
  color: var(--fb-alert-ink);
}

/* DENIED. */
.fb-chip-denied,
.badge.badge-danger {
  background: var(--fb-denied-wash);
  color: var(--fb-denied);
}

/* Brand tint — a state that is NOT a verdict. The reviewer's D-5 ruling:
   "green is verdict-only. Active is not approved." An enabled record, a live
   environment, a 2FA method in use — all real information, none of it a
   decision anyone made about a transaction, so none of it gets the green. */
.fb-chip-brand {
  background: var(--fb-brand-wash);
  color: var(--fb-brand-ink);   /* Q-13 — a chip label is text, not a graphic */
}

/* AUTO-RULE / neutral — a machine decided, which is information, not alarm. */
.fb-chip-auto,
.badge.badge-secondary,
.badge.badge-default,
.badge.badge-info {
  background: var(--fb-neutral-wash);
  color: var(--fb-muted);
}

/* NOTE for D-5 — these chip rules do NOT reach the status pills in the data
   tables yet. Those are `<span class="btn btn-success btn-xs">` carrying INLINE
   styles (`background-color`, `border-radius:12px`, `font-size:11px`) written
   by the page scripts, and an inline style beats any stylesheet. They are also
   the wrong element: a status is not a button. Deliberately NOT chased with
   !important here — the fix is to emit `<span class="fb-chip fb-chip-*">` from
   the page scripts, which is D-5's sweep. Recorded rather than bodged.

   The same sweep owes a semantic decision: "Active/Inactive" currently renders
   in verified-green, and green means approved/protected (§1b), not "row is
   enabled". */

/* A status dot for chips that lead with one. */
.fb-chip .fb-dot,
.fb-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  display: inline-block;
  background: currentColor;
  flex: none;
}

/* ── Cards ───────────────────────────────────────────────────────────────
   Radius 14. Light gets a 1px line and no shadow — elevation via structure,
   not float; shadows are for overlays only. */

.card,
.fb-card {
  background: var(--fb-surface);
  border: 1px solid var(--fb-line);
  border-radius: var(--fb-radius-card);
  box-shadow: none;
}

.card-header,
.fb-card-header {
  background: transparent;
  border-bottom: 1px solid var(--fb-line);
  color: var(--fb-ink);
  font: 600 15px/1.3 var(--fb-font-display);
  padding: 14px 18px;
  border-radius: var(--fb-radius-card) var(--fb-radius-card) 0 0;
}

.card-body,
.fb-card-body { padding: 18px; color: var(--fb-ink); }

.card-footer {
  background: transparent;
  border-top: 1px solid var(--fb-line);
  padding: 14px 18px;
}

/* ── Stat tiles ──────────────────────────────────────────────────────────
   For D-5's dashboard, which currently animates hardcoded fake numbers
   (495/947/328/10328) through js/count.js. The number is mono and tabular so
   it does not jitter as it changes. */

.fb-stat {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px;
  background: var(--fb-surface);
  border: 1px solid var(--fb-line);
  border-radius: var(--fb-radius-card);
}

.fb-stat-label {
  font: 600 11px/1 var(--fb-font-ui);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--fb-muted);
}

.fb-stat-value {
  font-family: var(--fb-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 28px;
  font-weight: 600;
  line-height: 1.1;
  color: var(--fb-ink);
}

/* A stat may carry ONE semantic, and only its real one.
 *
 * D-11: these take the *-ink tokens, not the raw fills. Measured on the real
 * rendered dashboard rather than inferred from the token table — the token
 * checker compares declared PAIRS and this pairing (a fill used as large text
 * on the page ground) was never one of them:
 *   --fb-alert    #fe6901 on #ffffff = 2.90:1, on the #f4f7fc page = 2.70:1
 *   --fb-verified #03a74b on #ffffff = 3.17:1, on the #f4f7fc page = 2.95:1
 * The bar is 3.0 (28px is large text), so pending failed on both grounds and
 * verified failed on the one it is painted on. The ink tokens exist for
 * exactly this — DESIGN_SYSTEM.md calls them "text on wash" — and clear it
 * with room: --fb-alert-ink 5.39/5.02, --fb-verified-ink 6.21/5.78. Both pass
 * 4.5 too, so a smaller tile stays compliant. Same derivation as Q-13's
 * --fb-brand-ink: the fill hue is untouched, only TEXT moves to the ink.
 * is-denied stays on the fill at 3.91/3.64 — it passes, and there is no
 * --fb-denied-ink to move it to. */
.fb-stat-value.is-pending  { color: var(--fb-alert-ink); }
.fb-stat-value.is-verified { color: var(--fb-verified-ink); }
.fb-stat-value.is-denied   { color: var(--fb-denied); }

/* Q-9 (2026-08-28): a TERMINAL failure count may carry denied-red. Same paint
   as .is-denied on purpose — an operator should not have to learn two reds —
   but a separate NAME, so a call site still reads whether it means "a decision
   went against someone" or "this is over and it did not work". The boundary is
   terminality, not severity: anything still retrying stays uncoloured. Mirrors
   the `failed` variant in js/fb-chips.js. */
.fb-stat-value.is-failed   { color: var(--fb-denied); }

/* D-5 additions, now that the dashboard actually uses these tiles. */

.fb-stat-note {
  font-size: 12px;
  line-height: 1.4;
  color: var(--fb-muted);
}

/* A tile that navigates. It is an <a>, so it needs the link colour and
   underline suppressed and a focus ring that is visible on a card. */
a.fb-stat {
  text-decoration: none;
  transition: border-color .15s ease, background .15s ease;
}

a.fb-stat:hover {
  border-color: var(--fb-brand);
  text-decoration: none;
}

a.fb-stat:focus-visible {
  outline: 2px solid var(--fb-brand);
  outline-offset: 2px;
  border-color: var(--fb-brand);
  box-shadow: none;
}

/* Four across on a desktop, two on a tablet, one on a phone. auto-fit rather
   than a fixed count so a future fifth tile does not need a new breakpoint. */
.fb-stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 16px;
}

/* Dashboard header strip. */
.fb-dash-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 18px;
}

.fb-dash-head .fb-page-title { margin: 0 0 4px; }

.fb-dash-sub { margin: 0; font-size: 13px; color: var(--fb-muted); }

/* The honest empty state. A table that says nothing is a table that looks
   broken. Scoped to match `.table > tbody > tr > td` (0,1,3) above rather
   than reaching for !important — a bare .fb-empty (0,1,0) would lose on
   padding and the row would render at data-row height. */
.table > tbody > tr > td.fb-empty,
.fb-empty {
  padding: 22px 14px;
  text-align: center;
  color: var(--fb-muted);
  font-size: 13px;
}

/* ── Tables ──────────────────────────────────────────────────────────────
   Scoped past FlatLab: its `.table > thead > tr > th` is (0,1,3), so a bare
   `.table th` (0,1,1) would lose — exactly the D-2 trap. */

.table {
  width: 100%;
  background: var(--fb-surface);
  color: var(--fb-ink);
  border-collapse: collapse;
  font-size: 13.5px;
}

/* `.adv-table table.display thead th` in style.css (0,2,3) and
   `table.display thead th` in demo_table.css both beat a plain
   `.table thead th` (0,1,2) — measured: the header rule was rendering #dddddd,
   not the line token. The datatable shapes are matched explicitly. */
.table > thead > tr > th,
.table thead th,
.adv-table table.display thead th,
table.display thead th {
  background: transparent;
  border-bottom: 1px solid var(--fb-line);
  border-top: none;
  padding: 11px 14px;
  font: 600 11px/1.2 var(--fb-font-ui);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--fb-muted);
  vertical-align: middle;
}

.table > tbody > tr > td,
.table tbody td {
  border-top: 1px solid var(--fb-row-line);
  padding: 12px 14px;
  vertical-align: middle;
  color: var(--fb-ink);
}

/* ── Zebra striping ──────────────────────────────────────────────────────
   REVERSED 2026-09-01, Matt's call (UI-5). This block used to say "striping
   reads as noise on a token surface; a row line is enough" and blanked both
   FlatLab's and Bootstrap's striping. The composed pages argued the other
   way: these tables are long, dense and numeric — transactions, payees, the
   login and 2FA reports — and reading across a row is the job. The old
   white / light-grey alternation comes back, token-driven: --fb-row-alt,
   defined in css/vault-tokens.css for both themes and recorded by the spec's
   UI-5 amendment and its new §Signature 6 (the row's four treatments, ordered
   loudest-last).

   ONE mechanism for every table in the portal — nth-of-type on the row:
     · a plain `.table` (index.html's dash-recent, the detail pages);
     · a `.table-striped` (most of them, and the js-rendered detail tables);
     · a DataTables page (banks / customers / transactions / users / payees /
       email-log / login-report / 2fa-report). DataTables removes the rows it
       is not showing from the DOM, so nth-of-type counts the VISIBLE rows —
       striping stays correct across paging, sorting and filtering, which is
       exactly what a :nth-child on a static list would get wrong. Its own
       `odd`/`even` classes are assigned in that same order; they are
       neutralised below rather than driven, so striping is decided in one
       place;
     · the two dev-tooling pages whose tables carry their own class rather
       than `.table` (integration.html / merchant-simulator.html: the cert
       summaries and the scenario matrices). Those pages style their tables
       in a page-local <style> block; this stylesheet is appended to <head>
       by js/layout.js and so lands after it.

   Specificity is deliberate. The stripe rules sit at (0,2,2), which beats
   bootstrap-reset.css:405 (`.table-striped tbody tr:nth-of-type(odd)`,
   a hardcoded rgb(240,241,243)) and bootstrap.min.css's rgba(0,0,0,.05) on
   ORDER — layout.js appends this file last — and beats demo_table.css's
   `tr.odd { #E2E4FF }` / `tr.even { white }` (0,1,1) outright. They stay at
   (0,2,2) rather than reaching higher so that `:hover` below, one class
   heavier, still wins on every row of every table.

   The row dividers STAY. With a stripe this quiet (dL* 1.83 light / 2.43
   dark — the point is legibility, not stripes you notice) the --fb-row-line
   hairline is still what separates two rows at a glance, and it is what the
   old look Matt asked for had. It reads on the alternate row as well as on
   the surface (1.11:1 light / 1.14:1 dark — in dark, marginally better than
   on the plain row). */
.table > tbody > tr:nth-of-type(odd),
.cert-summary-table > tbody > tr:nth-of-type(odd),
.scenario-matrix table > tbody > tr:nth-of-type(odd) {
  background: var(--fb-row-alt);
}

/* Transparent, not --fb-surface: the even row shows whatever ground the table
   is drawn on, which is --fb-surface everywhere today and stays right if a
   table is ever dropped on another one. This rule exists to overrule the
   vendored `tr.even { background-color: white }` — a hardcoded white that
   would paint a paper row into the middle of a Night Watch table. */
.table > tbody > tr:nth-of-type(even),
.cert-summary-table > tbody > tr:nth-of-type(even),
.scenario-matrix table > tbody > tr:nth-of-type(even) {
  background: transparent;
}

/* DataTables tints the SORTED column's cells (demo_table.css
   `tr.odd td.sorting_1..3` / `tr.even td.sorting_1..3`, lavenders). style.css
   already blanks some of those; blank the whole family here so the stripe is
   what shows through the sorted column too, in both themes. (0,2,3) beats
   demo_table's (0,2,2) without an !important. */
.table > tbody > tr > td.sorting_1,
.table > tbody > tr > td.sorting_2,
.table > tbody > tr > td.sorting_3 {
  background: transparent;
}

/* Hover is the louder step, and it is the SAME token it always was. It has to
   stay clearly apart from the stripe, so both values were derived against it:
   light, hover is dL* 3.27 past the alternate row (and 5.10 past the plain
   one) in the same direction; dark, the stripe steps down toward the graphite
   ground and the hover is the only thing that lifts — dL* 5.34 above the
   alternate row. Nothing on a table row is louder than the orange seam below,
   which is the rule the spec reserves for a pending decision. */
.table > tbody > tr:hover { background: var(--fb-neutral-wash); }

.table a { color: var(--fb-brand); }

/* ── The orange seam (spec §Signature 5) ─────────────────────────────────
   "The selected/attention row takes a 3px --fb-alert left rule — the one
   loud thing."

   THE INVARIANT STILL BINDS, and it is the whole reason this needs a rule of
   its own rather than a note: orange marks A PENDING DECISION. `.is-attention`
   therefore means "this row is waiting on a human", not "this row is
   highlighted" and not "this row is selected in a multi-select". A checkbox
   selection gets the hover wash and nothing else; a row a reviewer has to
   answer gets the seam. If a call site cannot say which of those it means, it
   does not get the seam.

   The rule is carried by a border rather than an inset box-shadow so it is a
   rule and not a shadow (§Shape reserves shadows for overlays), and EVERY
   first cell carries it transparent so turning it orange moves nothing —
   without that base rule an attention row would jog 3px sideways relative to
   its neighbours, which reads as a rendering fault rather than as emphasis.
   `td:first-child` is (0,2,3) and beats FlatLab's `.table > tbody > tr > td`
   (0,1,3); the seam itself is (0,3,3) and beats both. */
.table > tbody > tr > td:first-child {
  border-left: 3px solid transparent;
}

.table > tbody > tr.is-attention > td:first-child,
.table > tbody > tr.fb-row-attention > td:first-child {
  border-left-color: var(--fb-alert);
}

/* The same seam off a table — a card or panel that is itself the thing
   awaiting a decision. Same 3px, same token, same meaning. */
.fb-card.is-attention,
.fb-panel.is-attention,
.card.is-attention {
  border-left: 3px solid var(--fb-alert);
}

/* ── Meter ───────────────────────────────────────────────────────────────
   A quantity in a table cell: mono number, and a bar whose LENGTH carries the
   magnitude. Used by the payee risk score, which previously ran a five-step
   green→amber→red ramp — three reserved semantics spent on a measurement that
   is not a verdict about anything (§1b). Length is the honest encoding: it
   compares at a glance without claiming a decision was made.

   Deliberately brand-coloured at every value. If risk should have a banded
   colour of its own, that is a product decision — queued as Q-8, not invented
   here. */

.fb-meter {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 74px;
}

/* E-2 audit find: this had tabular-nums but no mono family, so the risk
   score — a number that matters — rendered in the UI face with tabular
   figures it could not use. §Type: every number that matters is mono. */
.fb-meter-value {
  font-family: var(--fb-font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--fb-ink);
  font-variant-numeric: tabular-nums;
}

.fb-meter-track {
  display: block;
  height: 4px;
  border-radius: var(--fb-radius-pill);
  background: var(--fb-row-line);
  overflow: hidden;
}

.fb-meter-fill {
  display: block;
  height: 100%;
  border-radius: var(--fb-radius-pill);
  background: var(--fb-brand);
}

/* A numeric column: right-aligned so the digits line up. */
.fb-num { text-align: right; }

/* A cell that must not wrap — a phone number, a date, a routing number. */
.fb-nowrap { white-space: nowrap; }

/* Card header carrying a title on the left and an action on the right. */
.fb-card-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.fb-card-head-row .fb-page-title { margin: 0; }

/* "Not applicable" — a fact about the row, not a value. */
.fb-cell-na { font-size: 12px; color: var(--fb-muted); }

/* A row for a record that is switched off. Dimmed, not recoloured: the pages
   used an inline `color:#999`, which is a fixed grey that reads as ordinary
   body text on a dark surface — an inactive bank looked exactly like an active
   one in dark mode. Opacity dims against whatever ground is behind it. */
.table > tbody > tr.fb-row-muted > td,
tr.fb-row-muted td { opacity: .55; }

/* Row action buttons. `white-space: nowrap` plus a real gap, so the icons stop
   being spaced by whitespace text nodes. */
.fb-actions {
  white-space: nowrap;
}

.fb-actions .btn + .btn { margin-left: 4px; }

/* Icon-only buttons are square: a 27px-tall btn-xs with 10px side padding is a
   lozenge around a single glyph. */
.fb-actions .btn.btn-xs {
  width: 27px;
  padding: 0;
}

/* Numbers in tables — amounts, routing numbers, trace numbers, masked
   accounts — are mono and tabular so columns align down the page. */
.table .fb-mono,
.table .fb-amount { font-family: var(--fb-font-mono); font-variant-numeric: tabular-nums; }

/* ── Form fields ─────────────────────────────────────────────────────── */

.form-control,
.fb-input {
  display: block;
  width: 100%;
  height: 38px;
  padding: 0 12px;
  font: 400 13.5px/1 var(--fb-font-ui);
  color: var(--fb-ink);
  background: var(--fb-surface);
  border: 1px solid var(--fb-line);
  border-radius: var(--fb-radius-control);
  box-shadow: none;
  transition: border-color .15s ease, box-shadow .15s ease;
}

/* D-3 FOLLOW-UP, found by measuring in D-4 rather than by looking:
   `bootstrap-reset.css` line 98 is `.form-control { color: #777 !important; }`.
   An !important cannot be beaten by specificity, only by another !important,
   so every input in the portal — every field in every modal and every form —
   was rendering its text at #777 in BOTH themes. In dark that is #777 on
   #0d1626: about 3.4:1, under the 4.5:1 AA floor for body text, and it is the
   colour a customer's bank details are typed in. This is the only !important
   any Vault stylesheet uses to set a colour, and it exists solely to cancel
   that one. Both disappear with FlatLab at D-6.5. */
.form-control,
.fb-input { color: var(--fb-ink) !important; }

textarea.form-control { height: auto; padding: 10px 12px; line-height: 1.5; }

/* Selects need two rules, and the second one is not obvious.
   Bootstrap sets `select.form-control:not([size]):not([multiple])` (0,3,1),
   which beats a plain `.form-control` (0,1,0) — measured: selects rendered
   30px beside 38px inputs. Matching its shape handles the ordinary case. */
select.form-control:not([size]):not([multiple]),
select.fb-input:not([size]):not([multiple]) {
  height: 38px;
  padding-right: 30px;
}

/* The second rule exists because DataTables emits its page-length dropdown as
   `<select class="form-control" size="1">`, and Bootstrap's
   `select.form-control[size]` (0,2,1) forces `height:auto` on the assumption
   that any [size] means a multi-row list box. size="1" is just a dropdown, so
   it should match its siblings. Found by measuring: the rule above correctly
   did NOT apply, and the control stayed 30px on every table page. */
select.form-control[size="1"]:not([multiple]) {
  height: 38px;
  padding-right: 30px;
}

.form-control::placeholder { color: var(--fb-muted); }

/* A text field shows focus on :focus, not :focus-visible — a caret in a box
   is a focus state however it was reached, and the browser's own heuristic
   would hide the ring from a mouse user who is about to type. */
.form-control:focus,
.fb-input:focus {
  /* Bootstrap's own :focus sets background-color:#fff and color:#495057 at
     (0,2,0) specificity, which beats the tokened base rule above — so in
     dark, focusing a field flipped it white under near-white ink text
     (stage punch list, 2026-08-31). States must re-assert the tokens, not
     assume they inherit. */
  background: var(--fb-surface);
  color: var(--fb-ink);
  border-color: var(--fb-brand);
  /* bootstrap-reset.css line 138 again — see the focus block above. */
  outline: 2px solid var(--fb-brand) !important;
  outline-offset: 2px;
  box-shadow: none;
}

.form-control:disabled,
.form-control[readonly] {
  background: var(--fb-neutral-wash);
  color: var(--fb-muted);
  cursor: not-allowed;
}

.form-group { margin-bottom: 16px; }

.form-group > label,
.fb-field-label {
  display: block;
  margin-bottom: 6px;
  font: 600 12.5px/1.2 var(--fb-font-ui);
  color: var(--fb-ink);
}

/* Validation. Denied red carries "this is wrong", which is a different
   statement from "this was denied" — same colour, and that is deliberate:
   both mean the thing in front of you did not go through. */
.form-control.is-invalid,
.has-error .form-control,
.form-control.error {
  border-color: var(--fb-denied);
}

/* An invalid field keeps the denied border and the brand ring: the ring says
   "this is where you are", the border says "this value is wrong", and they
   are two different statements. Painting the ring red as well made focus
   itself look like an error. */
.form-control.is-invalid:focus,
.has-error .form-control:focus {
  border-color: var(--fb-denied);
  box-shadow: none;
}

/* ── Interactive-state token integrity (stage punch list, 2026-08-31) ─────
   Bootstrap and the FlatLab-era style.css paint several STATES (focus,
   hover, autofill) with literal light colours that the tokened base rules
   never see, because a pseudo-class selector outranks the base rule. Each
   pin below re-asserts the tokens for a component the portal actually uses;
   the full extraction that found these lives in the §6 punch-list entry. */

/* Chrome/Safari autofill paints its own light background that background:
   cannot override — the inset box-shadow is the documented workaround, and
   -webkit-text-fill-color beats the UA's autofill ink. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--fb-surface) inset;
  box-shadow: 0 0 0 1000px var(--fb-surface) inset;
  -webkit-text-fill-color: var(--fb-ink);
  caret-color: var(--fb-ink);
  transition: background-color 9999s ease-out 0s;
}

/* Topbar/user menus. Bootstrap: hover/focus -> #f8f9fa. */
.dropdown-item { color: var(--fb-ink); }
.dropdown-item:hover,
.dropdown-item:focus {
  background: var(--fb-neutral-wash);
  color: var(--fb-ink);
}

/* The bank/routing autocomplete menus are .list-group / -item-action.
   Bootstrap bases them on #fff and hovers them to #f8f9fa. */
.list-group-item {
  background: var(--fb-surface);
  color: var(--fb-ink);
  border-color: var(--fb-row-line);
}
.list-group-item-action:hover,
.list-group-item-action:focus {
  background: var(--fb-neutral-wash);
  color: var(--fb-ink);
}
.list-group-item-action:active {
  background: var(--fb-brand-wash);
  color: var(--fb-ink);
}

/* DataTables pagination: style.css hovers these to #eee at higher
   specificity than any tokened base rule; same selector shape wins here
   because this file loads later. */
.dataTables_paginate.paging_bootstrap.pagination li:hover a,
.dataTables_paginate.paging_bootstrap.pagination li:focus a {
  background: var(--fb-neutral-wash);
  color: var(--fb-ink);
}

.invalid-feedback,
.fb-field-error,
.help-block.error {
  display: block;
  margin-top: 5px;
  font-size: 12.5px;
  color: var(--fb-denied);
}

.form-control.is-valid { border-color: var(--fb-verified); }

/* ── Inline alerts ───────────────────────────────────────────────────── */

.alert {
  padding: 12px 14px;
  border: 1px solid transparent;
  border-radius: var(--fb-radius-control);
  font-size: 13.5px;
}

.alert.alert-success {
  background: var(--fb-verified-wash);
  border-color: transparent;
  color: var(--fb-verified-ink);
}

.alert.alert-danger {
  background: var(--fb-denied-wash);
  border-color: transparent;
  color: var(--fb-denied);
}

/* Bootstrap's "warning" is this system's PENDING. */
.alert.alert-warning {
  background: var(--fb-alert-wash);
  border-color: transparent;
  color: var(--fb-alert-ink);
}

/* "info" has no semantic here — neutral, not a fifth colour. */
.alert.alert-info {
  background: var(--fb-neutral-wash);
  border-color: var(--fb-line);
  color: var(--fb-ink);
}

/* ── Toasts (toastr) ─────────────────────────────────────────────────────
   toastr ships its own CSS with `#toast-container > .toast-success` etc. at
   (1,1,0) — an id, so nothing class-based can beat it. These match that shape
   deliberately rather than reaching for !important. */

#toast-container > div {
  opacity: 1;
  border-radius: var(--fb-radius-card);
  box-shadow: var(--fb-shadow-overlay);
  background-image: none !important;   /* toastr's inline SVG icons */
  padding: 14px 16px 14px 16px;
  font: 500 13.5px/1.4 var(--fb-font-ui);
  border-left: 3px solid transparent;
}

#toast-container > .toast-success {
  background: var(--fb-verified-wash);
  color: var(--fb-verified-ink);
  border-left-color: var(--fb-verified);
}

#toast-container > .toast-error {
  background: var(--fb-denied-wash);
  color: var(--fb-denied);
  border-left-color: var(--fb-denied);
}

#toast-container > .toast-warning {
  background: var(--fb-alert-wash);
  color: var(--fb-alert-ink);
  border-left-color: var(--fb-alert);
}

#toast-container > .toast-info {
  background: var(--fb-surface);
  color: var(--fb-ink);
  border: 1px solid var(--fb-line);
  border-left: 3px solid var(--fb-brand);
}

#toast-container > div:hover { box-shadow: var(--fb-shadow-overlay); }

/* ── Theme toggle ────────────────────────────────────────────────────── */

.fb-theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--fb-line);
  border-radius: var(--fb-radius-control);
  background: var(--fb-surface);
  color: var(--fb-muted);
  cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}

.fb-theme-toggle:hover {
  background: var(--fb-neutral-wash);
  color: var(--fb-ink);
  border-color: var(--fb-line);
}

.fb-theme-toggle:focus-visible {
  /* A <button>, so bootstrap-reset.css line 138 applies — see the focus
     block near the top of this file. */
  outline: 2px solid var(--fb-brand) !important;
  outline-offset: 2px;
  border-color: var(--fb-brand);
  box-shadow: none;
}

/* Show the icon for the theme you would switch TO, which is the affordance
   people expect from a toggle. */
.fb-theme-toggle .fa-moon-o { display: inline; }
.fb-theme-toggle .fa-sun-o  { display: none; }
:root[data-theme="dark"] .fb-theme-toggle .fa-moon-o { display: none; }
:root[data-theme="dark"] .fb-theme-toggle .fa-sun-o  { display: inline; }

/* ── Brand lockup ────────────────────────────────────────────────────────
   Two lockups ship — navy wordmark on light, lighter blues on dark — because
   the wordmark is always two-tone blue and neither pair reads on both grounds
   (DESIGN_SYSTEM.md §The mark). Swapped by CSS, not JS, so the right one is
   correct at first paint and does not flash on a theme change.

   D-2 wrote these rules scoped to `.header .logo`. D-5 needed the same swap on
   login.html, which has no header, so the pattern is a class any surface can
   apply. The chrome keeps its own scoped copy; this is the general one. */
.fb-lockup .fb-logo-dark { display: none; }
:root[data-theme="dark"] .fb-lockup .fb-logo-light { display: none; }
/* inline, NOT block: the lockup containers center with text-align, which a
   block-level svg ignores — dark mode was shoving the login logo to the left
   edge while light sat centered (Matt, 2026-08-31). The header has its own
   flex layout and its own rules in vault-chrome.css; this only governs
   .fb-lockup contexts (login, reset, 404/500). */
:root[data-theme="dark"] .fb-lockup .fb-logo-dark  { display: inline; }

/* ── Page furniture ──────────────────────────────────────────────────── */

.fb-page-title,
h3.page-title {
  font: 600 18px/1.3 var(--fb-font-display);
  color: var(--fb-ink);
}

.text-muted { color: var(--fb-muted); }
.text-danger { color: var(--fb-denied); }

/* ── Detail & form page furniture (D-5c) ─────────────────────────────────
   Nine pages repeated the same handful of inline styles ~80 times between
   them. Collected here so the pages stop carrying colour, and so the next
   person changing a section rule changes it once.

   The counts, before: 19 identical `<h4 style="… border-bottom: 2px solid
   #eee">` section headings, 13 lock hints at `color:#999`, 12 field labels at
   `color:#666`, 8 panels on `#f5f5f5`. */

/* A heading that opens a group of fields inside a card. */
.fb-section-title {
  margin: 0 0 15px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--fb-line);
  font: 600 15px/1.3 var(--fb-font-display);
  color: var(--fb-ink);
}

/* A second-level heading inside such a group. */
.fb-subsection-title {
  margin: 20px 0 10px;
  font: 600 12.5px/1.3 var(--fb-font-display);
  color: var(--fb-muted);
}

/* The label above a read-only value on a detail page. */
.fb-field-caption {
  margin-bottom: 5px;
  font: 600 11px/1.4 var(--fb-font-ui);
  letter-spacing: .6px;
  text-transform: uppercase;
  color: var(--fb-muted);
}

/* A grey block that groups related content on a page — the page-level sibling
   of .fb-modal-context. Was `background-color: #f5f5f5`, which is a light-mode
   grey that turns into a bright slab on a dark surface. */
.fb-panel {
  padding: 15px;
  margin-bottom: 20px;
  background: var(--fb-neutral-wash);
  border-radius: var(--fb-radius-control);
}

.fb-panel:last-child { margin-bottom: 0; }

/* ── bootstrap-switch (the Yes/No toggles on the create & edit forms) ─────
   `assets/bootstrap-switch/…/bootstrap-switch.css` paints the "on" half
   verified-GREEN (#58b058) and the resting half a Bootstrap-3 blue. A switch
   being on is a state, not a verdict — brand tint and neutral, matching the
   chips that report these very same fields on the list and detail pages.

   Matched at `.has-switch span.switch-*` (0,2,1), the shape the vendored file
   uses, so this wins on order rather than on an !important. layout.js appends
   vault.css after the page's own <link>s. Verified by measuring: the "on" half
   read rgb(88,176,88) before. */

/* D-11: --fb-on-brand, same repointing as .btn-primary (3.49:1 in dark). */
.has-switch span.switch-primary,
.has-switch span.switch-left,
.has-switch span.switch-success {
  background: var(--fb-brand);
  color: var(--fb-on-brand);
}

.has-switch span.switch-right,
.has-switch span.switch-default,
.has-switch span.switch-danger {
  background: var(--fb-neutral-wash);
  color: var(--fb-muted);
}

.has-switch label {
  background: var(--fb-surface);
  border-color: var(--fb-line);
}

/* A group of fields separated from the one above it. Was a `border-top: 2px
   solid #eee` inline — or, for the destructive group on user-edit, a 2px
   denied-RED rule, which said "danger" in the colour reserved for a denied
   transaction while the button inside already said it in words. */
.fb-form-section {
  margin-top: 30px;
  padding-top: 20px;
  border-top: 1px solid var(--fb-line);
}

/* The muted line under a spinner while a detail page loads. */
.fb-loading-note { margin-top: 15px; font-size: 13px; color: var(--fb-muted); }

/* The one number a transaction-detail page is about.
   E-2 audit find: it declared tabular-nums and no family, so the single most
   important amount in the portal rendered in the UI face — the exact call
   site §Type names first ("EVERY number that matters: amounts …"). */
.fb-amount-hero {
  font-family: var(--fb-font-mono);
  font-size: 22px;
  font-weight: 600;
  color: var(--fb-ink);
  font-variant-numeric: tabular-nums;
}

/* The padlock beside a field whose value comes from the Federal ACH Directory
   and cannot be edited here. It is an annotation, not a warning. */
.fb-lock-hint {
  margin-left: 5px;
  font-size: 12px;
  color: var(--fb-muted);
}

/* Detail rows (bank-detail, user-detail, customer-detail …). */
.detail-label {
  font: 600 11px/1.2 var(--fb-font-ui);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--fb-muted);
}

.detail-row { border-bottom: 1px solid var(--fb-row-line); }

/* Modals moved out to css/vault-modal.css in D-4 — the shell that used to sit
   here is now the first section of that file, alongside the rest of the
   review-modal anatomy. Kept as a pointer rather than deleted silently: the
   next person looking for a modal rule will look here first. */

/* ============================================================================
   E-6 — the call sites the token layer never actually reached
   ============================================================================
   Composed-page QA (REMEDIATION_PLAN.md §1c E-6) measured every rendered page
   in both themes with getComputedStyle, D-11's method. `check-contrast.mjs`
   reported every token pairing passing throughout — and it was right about the
   pairings and blind to the paint, because on these six selectors a
   Bootstrap/FlatLab literal was OUTRANKING the Split rule that was already
   written for it.

   Two distinct mechanisms, both invisible to a token-pair checker:

     1. `!important` in css/bootstrap.min.css. `.text-muted{color:#6c757d
        !important}` and `.text-danger{color:#dc3545!important}` beat
        vault.css's own `.text-muted { color: var(--fb-muted) }` (line ~910)
        on the cascade's importance step, which specificity cannot reach. Those
        two Split rules have been dead since D-5. E-1b set the precedent for
        the answer — one `!important` cancel, stated where it happens (see
        bootstrap-reset.css:138 and the focus block above).

     2. Load order. css/bootstrap-reset.css is a static <link>; vault.css is
        injected by js/layout.js and therefore lands LATER, so equal-
        specificity rules here win — which is why `a` and `.card-title` below
        need no importance, only a rule that exists.

   Measured, on the running pages (worst case per selector):

     | selector                | theme | was     | now    |
     |-------------------------|-------|---------|--------|
     | .text-muted             | dark  | 3.48:1  | 6.38:1 |
     | .text-danger            | dark  | 3.61:1  | 6.19:1 |
     | .text-danger            | light | 3.98:1  | 5.75:1 |
     | h4.card-title           | dark  | 1.31:1  | 13.40:1|
     | .alert a / a            | light | 3.62:1  | 6.14:1 |
     | .alert a / a            | dark  | 3.69:1  | 7.38:1 |
     | code                    | light | 3.36:1  | 6.14:1 |
     | code                    | dark  | 3.97:1  | 7.38:1 |
     | .toast-close-button     | light | 1.14:1  | 5.75:1 |

   Nothing here is a new colour. Every value is a token this system already
   proves; what changes is that the token now arrives at the paint. */

/* 1. The two Bootstrap utility classes. `!important` only because Bootstrap
      used it first — this restores the rule vault.css already declared. */
.text-muted { color: var(--fb-muted) !important; }
.text-danger { color: var(--fb-denied) !important; }
a.text-danger:hover,
a.text-danger:focus { color: var(--fb-denied) !important; }

/* 2. The bare anchor. css/bootstrap-reset.css:2 paints every link `#667fa0`
      and its hover `#2A3542` — a FlatLab pair belonging to no token, frozen
      light, and the reason an "Add a user" link inside a washed alert read at
      3.62:1. Q-13's ruling applies: a link is TEXT, so it takes
      --fb-brand-ink, not --fb-brand. Anything with its own colour (.btn, the
      chrome's nav, .fb-chip, the lockups' inline SVG, which paints with
      `fill`) is more specific and is untouched. */
a { color: var(--fb-brand-ink); }
a:hover,
a:focus { color: var(--fb-brand-hover); }

/* 3. FlatLab's card heading (bootstrap-reset.css:24) is `#2A3542` — a frozen
      dark literal that renders at 1.31:1 on the dark surface, i.e. four
      section headings per detail page that Night Watch simply did not show.
      It also still asks for 'Open Sans', which E-2 retired; the display face
      is the token. */
.card-title,
h4.card-title {
  color: var(--fb-ink);
  font-family: var(--fb-font-display);
}

/* 4. Inline <code>. Bootstrap paints it `#e83e8c`, a pink belonging to no
      token and failing in both themes on integration.html's washed API
      descriptions. It is a value being quoted, not a verdict, so it takes the
      brand ink and the mono face §Type gives to things that are read
      literally. */
code {
  color: var(--fb-brand-ink);
  font-family: var(--fb-font-mono);
}

/* 5. toastr's dismiss glyph is `#ffffff` with a white text-shadow, which on
      the tokenised toast grounds above is a white × on a pale wash — 1.14:1,
      an invisible control on an error toast. `currentColor` takes each toast's
      own already-proven ink, which is the same repair E-5 made to the app's
      PROTECTED dot. Matched at (1,2,0) to beat toastr's own id-scoped rules
      the way the block above does. */
#toast-container > div > .toast-close-button {
  color: currentColor;
  text-shadow: none;
  opacity: .7;
}

#toast-container > div > .toast-close-button:hover,
#toast-container > div > .toast-close-button:focus {
  color: currentColor;
  opacity: 1;
}

/* ── UI-9: the "records per page" select clipped its value ────────────────
   Reported from stage: the DataTables length menu showed a cut-off glyph
   instead of "50". Measured on a static harness of the real banks.html
   stylesheet stack (Chromium, 13.5px Public Sans) rather than guessed:

     | source                                    | wins | declares          |
     |-------------------------------------------|------|-------------------|
     | style.css `.adv-table .dataTables_length   |(0,2,1)| width:65px        |
     |  select` (also the un-scoped twin, 2929)  |      | padding:5px 8px   |
     | vault.css `select.form-control[size="1"]  |(0,3,1)| padding-right:30px|
     |  :not([multiple])` (the DT dropdown rule) |      | height:38px       |

   The two rules are each defensible alone and fatal together: FlatLab's 65px
   is a BORDER-box width, and the 30px right gutter this sheet adds for the
   chevron is subtracted from inside it. Content box = 65 − 2 border − 8 − 30
   = **25px**. A native `appearance:auto` select reserves its own arrow inside
   that content box — measured here at ~20px — so the widest option ("100",
   21.92px) had 5px to render 22px of text. Chromium spills the overflow into
   the right padding and gets away with it; WebKit clips, which is the glyph
   Matt photographed. Nothing about it is theme-dependent, and no custom
   chevron background is involved (`background-image: none`, appearance auto).

   The fix is to stop declaring a width the content cannot be measured
   against. `width: auto` makes the engine size the box to its own widest
   option plus its own arrow — correct in every engine, and it survives a
   font fallback, a longer option list, or a user's larger default text,
   none of which a hard 65px does. The right gutter drops to the symmetric
   12px because with auto width the arrow is already inside the box; keeping
   30px there would leave a dead strip to the right of the chevron. The
   min-width is comfort, not fit: it keeps the control from shrinking to a
   cramped 68px and holds the same visual weight the toolbar had.

   Measured, this harness, both themes: 65px border-box / 25px content →
   80px / 54px content. Selector is (0,4,1) — one class more than the
   vault rule it amends and two more than FlatLab's — so it lands without
   touching a vendored sheet. Height stays 38px from the rule above, and
   the 44px phone floor in the E-6 block below still applies. */
.dataTables_length select,
.dataTables_length select.form-control:not([size]):not([multiple]),
.dataTables_length select.form-control[size="1"]:not([multiple]) {
  /* One place to tune the length menu's geometry. */
  --fb-length-select-min: 80px;   /* comfort floor; fit comes from width:auto */
  --fb-length-select-pad: 12px;   /* symmetric — the arrow is inside the box  */

  width: auto;
  min-width: var(--fb-length-select-min);
  padding-left: var(--fb-length-select-pad);
  padding-right: var(--fb-length-select-pad);
}

/* ── E-6: §Shape's 44px, at phone widths ──────────────────────────────────
   "Phone hit targets ≥ 44px" (DESIGN_SYSTEM.md §Shape). F-16 met it for the
   chrome it built — hamburger 44×44, drawer nav rows 48 — and E-6 measured
   the rest of the portal at a 375×812 device with getBoundingClientRect. The
   misses are not scattered; they are four numbers repeated across all 26
   pages, because the controls are sized once here and once by Bootstrap:

     | control                          | measured | pages |
     |----------------------------------|----------|-------|
     | .btn                             | 38 px    | all   |
     | .btn-sm                          | 32 px    | 11    |
     | .form-control / select           | 38 px    | all   |
     | topbar: search, avatar, toggle   | 36 px    | 22–26 |
     | #fb-theme-toggle WIDTH           | 25.1 px  | 26    |
     | .go-top                          | 40×40    | 20    |
     | .btn-copy                        | 26×26    | 2     |
     | toastr dismiss                   | 11.5×28  | 2     |

   The theme toggle is the sharp one: 36×36 is its declared square, but at
   375 px the topbar's flex row squeezes it to **25.1 px wide** — a control
   that is not merely under the floor but visibly narrower than it looks in
   every desktop screenshot ever taken of it.

   Scoped to the F-16 breakpoint (767 px — the same one vault-chrome.css and
   Layout._syncChromeMode() hand over at), so the desktop rhythm the ramp was
   tuned on is untouched. Nothing here changes a colour, a token or a
   component; it changes how tall a finger's worth of control is on a phone. */

@media (max-width: 767px) {
  .btn,
  .btn.btn-sm,
  .btn-sm,
  .form-control,
  .fb-input,
  select.form-control:not([size]):not([multiple]),
  select.fb-input:not([size]):not([multiple]),
  select.form-control[size="1"]:not([multiple]) {
    min-height: 44px;
    height: auto;
  }

  /* A button's label should still sit in the middle of the taller box. */
  .btn,
  .btn.btn-sm,
  .btn-sm { padding-top: 0; padding-bottom: 0; }

  /* The topbar trio. The toggle gets a min-WIDTH as well as a min-height,
     because the flex row is what was squeezing it. */
  .fb-theme-toggle,
  #fb-theme-toggle {
    min-width: 44px;
    min-height: 44px;
    flex: none;
  }

  .fb-topbar .dropdown-toggle { min-height: 44px; }

  .site-footer .go-top,
  .btn-copy {
    min-width: 44px;
    min-height: 44px;
  }

  /* toastr sizes its dismiss glyph from a font-size and nothing else. */
  #toast-container > div > .toast-close-button {
    display: inline-block;
    min-width: 44px;
    min-height: 44px;
    text-align: right;
  }

  /* The header lockup is a link to the dashboard, measured 157.5×30. */
  .header .logo {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* The two chrome-less auth pages (login, password-reset). Their controls are
     declared in their own <style> blocks, which win on order for `width` /
     `height` — but neither sets a MIN, so these clamp them from here and the
     pages need no edit. The show/hide eye is 30×30 and sits inside the field,
     so the field's right padding grows with it or the two overlap. */
  .password-toggle {
    min-width: 44px;
    min-height: 44px;
  }

  .password-wrapper .form-control { padding-right: 48px; }

  /* "Forgot your password?" (login) and "Back to sign in" (password-reset) —
     15px-tall standalone links, each alone on its row. Extended with the
     transparent ::after the app uses for the same job, so the visual weight
     (12.5px, muted) is unchanged and nothing can overlap them. */
  .forgot-link a,
  .back-link a {
    position: relative;
    display: inline-block;
  }

  .forgot-link a::after,
  .back-link a::after {
    content: '';
    position: absolute;
    inset: -14.5px -6px;
  }
}
