/* =============================================================================
   KANINE — Blazor design-system foundation (Step-0, UI-FIRST gate)
   -----------------------------------------------------------------------------
   THE PORT. This is the hand-written `kl-*` / `kf-*` realization of the prototype's
   design system (`ProposedUI/design-system/kanine.css` + `kanine.config.js`).
   The prototype uses Tailwind utilities for component LOOK; the Blazor build does
   NOT ship Tailwind — its pages carry semantic `kl-<page>-*` / `kf-*` class names
   that, until now, had NO backing CSS (screens rendered unstyled). This file is
   that backing: the --k-* brand token mirror, the light/dark/outdoor flip, and a
   base treatment for the shared component vocabulary, keyed to the SUFFIX of the
   page-prefixed families (kl-ce-card, kl-uof-field, kl-medit-input, …) so every
   screen gets coherent, on-brand structure for free. Per-page R11 then REFINES
   each screen with page-scoped CSS; it never reinvents the system.

   Loaded BEFORE app.css (which keeps the a11y target-size/reflow overrides that
   must win) so app.css's narrower fixes take precedence on cascade ties.

   Source of truth for hexes/tokens: brand/BRAND-AND-DESIGN.md + DESIGN-INTEGRATION
   -BRIEF §5.1 + ProposedUI/design-system/kanine.css. Do not introduce off-token
   colour here — the R11 token-conformance scan reds on it.

   SCOPING RULE (load-bearing): the broad suffix-family selectors are scoped under
   `.kl-shell-main` (the page content region). The app shell chrome (the ShellNav
   `.kl-shellnav-*` rail + top bar) lives OUTSIDE `<main>`, so a family like
   [class*="-header"] can never bleed onto the chrome. Named primitives (kf-*,
   kl-btn, kl-dialog) are global so top-layer <dialog> + cross-cutting buttons
   style everywhere.
   ========================================================================== */

/* ---- 0. Self-hosted web fonts (offline-first; no CDN) --------------------
   The four families the tokens below name are SELF-HOSTED from wwwroot/fonts
   because Kanine runs offline in the field — a Google Fonts CDN link would
   vanish the moment the device loses signal, and the icon font failing open
   renders ligature NAMES as literal words (the pg35 finding this fixes).
   Regenerate with tools/fonts/build-fonts.py. Material Symbols is subset to the
   68 icons in tools/fonts/icon-names.txt (3.95 MB full -> ~50 KB); the text
   families are Google's 'latin' slice. Licenses in wwwroot/fonts/LICENSE.md
   (Material Symbols Apache-2.0; the rest OFL-1.1). The text faces use
   font-display:optional + a <head> preload of each woff2 (App.razor): the faces
   are LOCAL files, so with the preload they are in the font cache at first
   paint and always render — but if a paint ever races the load, the browser
   keeps the fallback for that page view instead of RE-WRAPPING painted text
   (font-display:swap re-flowed content-sized bands site-wide — the record-view
   CLS 0.24 root cause, final-draft sweep 2026-07-03). The ICON face uses
   font-display:block so the load window shows a blank box, never a flash of
   the literal ligature word. */
@font-face {
  font-family:"Hanken Grotesk";
  font-style:normal; font-weight:400 800; font-display:optional;
  src:url("../fonts/hanken-grotesk-latin.woff2") format("woff2");
}
@font-face {
  font-family:"Inter";
  font-style:normal; font-weight:400 600; font-display:optional;
  src:url("../fonts/inter-latin.woff2") format("woff2");
}
@font-face {
  font-family:"JetBrains Mono";
  font-style:normal; font-weight:400 600; font-display:optional;
  src:url("../fonts/jetbrains-mono-latin.woff2") format("woff2");
}
@font-face {
  font-family:"Material Symbols Outlined";
  font-style:normal; font-weight:100 700; font-display:block;
  src:url("../fonts/material-symbols-outlined.woff2") format("woff2");
}

/* The icon primitive. Pages render icons as LIGATURE NAMES, e.g.
   <span class="material-symbols-outlined">verified</span>; the 'liga' feature
   maps the name to its glyph. Default axes mirror the prototype (opsz 24 /
   wght 400 / FILL 0 / GRAD 0); a page may override font-variation-settings
   (e.g. 'FILL' 1 for a filled state). */
.material-symbols-outlined {
  font-family:"Material Symbols Outlined";
  font-weight:normal; font-style:normal;
  font-size:24px; line-height:1; letter-spacing:normal; text-transform:none;
  display:inline-block; white-space:nowrap; word-wrap:normal; direction:ltr;
  /* pg40 CLS hardening: reserve EXACTLY the glyph's 1em advance + clip the pre-load ligature
     NAME. font-display:block (above) only makes the FOUT text INVISIBLE — it still occupies the
     full ligature-name width ("radio_button_unchecked" ≈ 400px) and then collapses to a ~1em glyph
     when the icon font applies, a reflow that produced a deterministic ~0.32 CLS on /suspects'
     icon-bearing filter chips. The LOADED-state width is unchanged (Material Symbols glyphs advance
     1em), so this is purely a loading-state guard that cannot move any already-painted icon — a
     strict, app-wide improvement. text-align:center keeps the glyph optically centred in the box. */
  inline-size:1em; overflow:hidden; text-align:center;
  -webkit-font-feature-settings:"liga"; font-feature-settings:"liga";
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  font-variation-settings:"FILL" 0,"wght" 400,"GRAD" 0,"opsz" 24;
  vertical-align:middle;
}

/* ---- 1. Token mirror (--k-* ; mirrors kanine.css §1) ---------------------- */
:root {
  --k-ink-900:#0A1A2B; --k-ink-800:#0E2236; --k-ink-700:#13304A; --k-ink-500:#2B5A8C;
  --k-teal-700:#0A7D77; --k-teal-500:#10B5A8; --k-teal-400:#2DD4C4;
  --k-paper:#F5F7FA; --k-gray-100:#E6EBF2; --k-gray-300:#C2CCD9;
  --k-gray-500:#7A8699; --k-gray-700:#3E4A5C; --k-gray-900:#161C26;
  --k-seal-brass:#C9A227;
  --k-success:#1E9E6A; --k-caution:#E08A00; --k-critical:#D7402B; --k-info:#2B7FFF;

  /* flipping semantic channels — RGB is the source of truth (swap with mode) */
  --k-bg-rgb: 245 247 250;        /* paper #F5F7FA */
  --k-surface-rgb: 255 255 255;   /* white card */
  --k-surface-2-rgb: 238 242 247; /* #EEF2F7 subtle fill */
  --k-surface-3-rgb: 230 235 242; /* #E6EBF2 */
  --k-text-rgb: 14 34 54;         /* ink-800 body text */
  --k-text-muted-rgb: 62 74 92;   /* gray-700 muted */
  --k-border-rgb: 194 204 217;    /* gray-300 */
  --k-outline-rgb: 122 134 153;   /* gray-500 */

  --k-bg:rgb(var(--k-bg-rgb));
  --k-surface:rgb(var(--k-surface-rgb));
  --k-surface-2:rgb(var(--k-surface-2-rgb));
  --k-surface-3:rgb(var(--k-surface-3-rgb));
  --k-text:rgb(var(--k-text-rgb));
  --k-text-muted:rgb(var(--k-text-muted-rgb));
  --k-border:rgb(var(--k-border-rgb));
  --k-outline:rgb(var(--k-outline-rgb));
  --k-accent:var(--k-teal-700);        /* teal as TEXT/foreground accent (AA on light) */
  --k-accent-bright:var(--k-teal-500);
  --k-on-accent:#FFFFFF;
  --k-header-bg:var(--k-ink-800);
  --k-header-text:#FFFFFF;
  --k-focus:var(--k-teal-400);

  --k-font-display:"Hanken Grotesk",system-ui,sans-serif;
  --k-font-ui:"Inter","IBM Plex Sans",system-ui,sans-serif;
  --k-font-mono:"JetBrains Mono","IBM Plex Mono",ui-monospace,monospace;

  --k-space-1:4px; --k-space-2:8px; --k-space-3:12px; --k-space-4:16px;
  --k-space-6:24px; --k-space-8:32px; --k-space-12:48px; --k-space-16:64px;
  --k-radius-sm:4px; --k-radius-md:8px; --k-radius-lg:16px; --k-radius-xl:28px; --k-radius-full:9999px;
  --k-elev-1:0 1px 2px rgb(0 0 0 / .08);
  --k-elev-2:0 2px 6px rgb(0 0 0 / .10);
  --k-elev-3:0 6px 16px rgb(0 0 0 / .12);
  --k-touch:48px;
  --k-content-max:1120px;

  /* §A10 phone-card title line (§8b-m). Both are DERIVED, not chosen: the line floor is the touch target minus
     the card's block padding, which is the tallest line the 48px pinned kebab can sit on while bleeding
     symmetrically into that padding instead of reaching the next line; the reserve is the pinned slot's own width
     plus one gutter. They are tokens so the pin inset, the line height and the horizontal reserve cannot drift
     apart in three separate rules — the guard test asserts they are still derived from --k-touch. */
  --k-tcard-line:calc(var(--k-touch) - var(--k-space-3));
  --k-tcard-reserve:calc(var(--k-touch) + var(--k-space-2));

  /* motion — restrained-premium register (R11). Calm, precise; signature flows override. */
  --k-ease:cubic-bezier(.2,.6,.2,1);
  --k-dur-1:.12s; --k-dur-2:.2s; --k-dur-3:.32s;
}

/* ---- 2. Modes (mirrors kanine.css §2) ------------------------------------- */
/* Dark: never pure black (halation). Set by the shell theme toggle (html.dark). */
html.dark {
  --k-bg-rgb: 15 22 32;
  --k-surface-rgb: 22 30 43;
  --k-surface-2-rgb: 30 40 56;
  --k-surface-3-rgb: 40 53 73;
  --k-text-rgb: 230 235 242;
  --k-text-muted-rgb: 159 176 194;
  /* R11 punch-list TOP-5: dark card borders were near-invisible (#28394C on #161E2B). Raise the edge so a card
     reads AS a card in the dark field POV (still on the ink ramp; bg/surface tokens untouched so the R11 canvas
     gate holds). */
  --k-border-rgb: 58 80 107;
  --k-outline-rgb: 122 140 162;
  --k-accent:var(--k-teal-400);
  --k-on-accent:var(--k-ink-900);
  --k-header-bg:#161E2B;
}
/* Outdoor / high-contrast: glove-grade, ≥7:1. Outdoor alone = light high-contrast. */
:root[data-outdoor="true"], html[data-outdoor="true"] {
  --k-bg-rgb: 255 255 255;
  --k-surface-rgb: 255 255 255;
  --k-surface-2-rgb: 237 242 247;
  --k-surface-3-rgb: 222 229 238;
  --k-text-rgb: 6 18 31;
  --k-text-muted-rgb: 35 53 74;
  --k-border-rgb: 43 90 140;
  --k-outline-rgb: 43 90 140;
  --k-accent:#066159;
}
/* Dark + outdoor = DARK high-contrast (night field). Specificity beats both singles. */
html.dark[data-outdoor="true"] {
  --k-bg-rgb: 6 11 18;
  --k-surface-rgb: 19 27 39;
  --k-surface-2-rgb: 30 41 56;
  --k-surface-3-rgb: 44 59 80;
  --k-text-rgb: 255 255 255;
  --k-text-muted-rgb: 201 214 229;
  --k-border-rgb: 86 116 148;
  --k-outline-rgb: 120 150 180;
  --k-accent: var(--k-teal-400);
  --k-on-accent: var(--k-ink-900);
  --k-header-bg: #0C141F;
}
html[data-outdoor="true"] body { font-size:18px; font-weight:500; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
}

/* ---- 3. Base reset + typography ------------------------------------------- */
html { -webkit-text-size-adjust:100%; }
body {
  margin:0;
  background:var(--k-bg);
  color:var(--k-text);
  font-family:var(--k-font-ui);
  font-size:16px;                /* 16px floor (glare/low-vision) */
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}
.k-mono { font-family:var(--k-font-mono); font-variant-ligatures:none; }
.k-display { font-family:var(--k-font-display); }

:where(a,button,input,select,textarea,[tabindex],[role="button"],[role="tab"],[role="switch"],[role="treeitem"]):focus-visible {
  outline:3px solid var(--k-focus);
  outline-offset:2px;
  border-radius:var(--k-radius-sm);
}
::selection { background:color-mix(in srgb, var(--k-teal-500) 30%, transparent); }

/* ---- 4. Page content region ----------------------------------------------- */
/* MainLayout's <main class="kl-shell-main"> is the content surface. Give the
   page a comfortable measure + rhythm; headings get the display face. */
.kl-shell-main {
  background:var(--k-bg);
  color:var(--k-text);
}
.kl-shell-main > * { box-sizing:border-box; }
.kl-shell-main h1 {
  font-family:var(--k-font-display);
  font-size:clamp(22px, 2.2vw, 28px);
  line-height:1.2; font-weight:800; letter-spacing:-0.01em;
  margin:0 0 var(--k-space-4);
  color:var(--k-text);
}
.kl-shell-main h2 { font-family:var(--k-font-display); font-size:18px; font-weight:700; margin:var(--k-space-2) 0; }
/* Buttons styled as links (a.kl-btn) must keep their button text colour, not the teal link colour (which, at its
   higher specificity, was washing the navy "New record" anchor to low-contrast teal-on-navy). Exclude any -btn class. */
.kl-shell-main a:not([class*="-btn"]) { color:var(--k-accent); text-underline-offset:2px; }
.kl-shell-main p { color:var(--k-text); }

/* =============================================================================
   5. THE COMPONENT FOUNDATION — suffix-keyed families, scoped to page content.
   Each rule targets the shared SUFFIX of the page-prefixed family classes, so
   kl-ce-card / kl-uof-card / kl-medit-card all inherit one card treatment, etc.
   `:where([class^="kl-"],[class*=" kl-"])[class$="-x"]` (ends-with) disambiguates -card from -card-title cleanly.
   ========================================================================== */
.kl-shell-main { --kf-field-gap: var(--k-space-2); }

/* ---- 5a. Cards / sections ------------------------------------------------- */
/* `.kl-card` — the GENERIC card, and the one member of this family that had no rule at all (found 2026-07-24).
   The suffix selector matches the whole class ATTRIBUTE, so `class="kl-card"` alone happened to end in "-card"
   and picked the chrome up by luck, while every MULTI-class use — `kl-card kl-gate-profile`, `kl-card kl-onb-fields`,
   `kl-card kl-hub-group`, `kl-card kl-sign-pad`, … — ended in some other suffix and got NOTHING. On the device
   that shipped the /signin remembered-profile tile as a raw UA <button> (grey outset border, square corners).
   Same escape hatch, same declarations, as the .kl-medit-card / .kl-ce-card / .kl-ee-card lines below. */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-card"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-section"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-panel"],
.kl-shell-main .kl-card,
.kl-shell-main .kl-medit-card,
.kl-shell-main .kl-ce-card,
.kl-shell-main .kl-ee-card {
  display:block;
  background:var(--k-surface);
  border:1px solid var(--k-border);
  border-radius:var(--k-radius-lg);
  box-shadow:var(--k-elev-1);
  padding:var(--k-space-6);
  margin-block-end:var(--k-space-4);
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-card-title"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-section-title"] {
  font-family:var(--k-font-display);
  font-size:16px; font-weight:700; line-height:1.3;
  margin:0 0 var(--k-space-3);
  color:var(--k-text);
}

/* ---- 5b. Form kit (kf-* explicit + page -field/-label/-input families) ---- */
.kf-field,
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-field"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-field-wide"] {
  display:flex; flex-direction:column; gap:var(--kf-field-gap);
  margin-block-end:var(--k-space-4); min-inline-size:0;
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-field-wide"] { grid-column:1 / -1; }

.kf-label,
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-label"] {
  font-size:13px; font-weight:600; line-height:1.3;
  color:var(--k-text-muted); letter-spacing:.01em;
}

.kf-input, .kf-select, .kf-textarea,
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-input"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-select"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-textarea"],
.kl-shell-main input[type="text"], .kl-shell-main input[type="date"],
.kl-shell-main input[type="datetime-local"],
.kl-shell-main input[type="time"], .kl-shell-main input[type="number"],
.kl-shell-main input[type="email"], .kl-shell-main input[type="search"],
/* password was MISSING from this list, and nothing anywhere else styled it — grep the whole tree for
   input[type="password"] and this is the only hit. So on every auth surface in the product the password box fell
   outside the design system while the field directly above it sat inside it: a different height (no --k-touch
   floor), a different font size, a different border colour, browser default padding. It is visible on /login,
   /account/first-login, /account/forgot-password, /account/reset-password, the invite accept, account security,
   the Apple/Google link pages and /k96/login-page.
   Found by an owner watching the K96 film (2026-07-30) — "the K96 password entry does not look the same as the
   other two entry boxes" — where the mismatch is starkest because that page also painted its own border colour.
   It is not a K96 defect; that page is just where it was noticed.
   Safe to add precisely BECAUSE nothing else styles password inputs: there is no component rule for this
   selector (0,2,1) to out-specify, which is the hazard §5c's kf-combobox reset exists to undo. */
.kl-shell-main input[type="password"],
.kl-shell-main input:not([type]),   /* a typeless <input> IS type=text but [type="text"] would miss it */
.kl-shell-main select, .kl-shell-main textarea {
  appearance:none;
  inline-size:100%; max-inline-size:100%; box-sizing:border-box;
  min-block-size:var(--k-touch);
  padding:0 var(--k-space-3);
  font:inherit; font-size:15px; color:var(--k-text);
  background:var(--k-surface);
  border:1px solid var(--k-outline);
  border-radius:var(--k-radius-sm);
  transition:border-color var(--k-dur-1) var(--k-ease), box-shadow var(--k-dur-1) var(--k-ease);
}
.kl-shell-main textarea, .kf-textarea, .kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-textarea"] {
  min-block-size:5rem; padding-block:var(--k-space-2); line-height:1.5; resize:vertical;
}
.kl-shell-main input:focus, .kl-shell-main select:focus, .kl-shell-main textarea:focus,
.kf-input:focus, .kf-select:focus, .kf-textarea:focus {
  outline:none; border-color:var(--k-accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--k-accent) 22%, transparent);
}
.kl-shell-main input:disabled, .kl-shell-main select:disabled, .kl-shell-main textarea:disabled {
  background:var(--k-surface-2); color:var(--k-text-muted); cursor:not-allowed;
}
/* Native <select> dropdown affordance: appearance:none (above) strips the UA arrow and nothing redrew one, so a
   select was indistinguishable from a text input (the record-entry "which of these is pickable?" finding). Draw an
   on-brand chevron (encoded inline SVG, ink-800 stroke — a data URI cannot read custom properties, so html.dark
   swaps a light-tint variant below; an SVG image is NOT a gradient, so the R11 canvas tooth is untouched) in the
   end-padding slot. The :not([multiple]):not([size]) does DOUBLE duty: it scopes to REAL closed selects (open list
   boxes take no closed-box arrow) AND lifts specificity to (0,3,1) so this outranks the form-kit family's
   `background` / `padding` SHORTHANDS on `.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-select"]` (0,2,0) — which otherwise silently
   reset the image away (shot-verified). The LookupComboBox's native mirror is display:none + draws its own
   .kf-combobox-chevron — never double-chevroned. */
.kl-shell-main select:not([multiple]):not([size]) {
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' stroke='%230E2236' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right var(--k-space-3) center;
  background-size:12px 8px;
  padding-inline-end:var(--k-space-8);
}
html.dark .kl-shell-main select:not([multiple]):not([size]) {
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' stroke='%23E6EBF2' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* Bool form field — a REAL brand-accented checkbox at its natural size. The generic FieldWidget gives a checkbox
   the kf-checkbox class (never kf-input, whose appearance:none + 100% inline-size + box border would strip the
   native tick and render the field as an empty full-width box — the record-entry "K9 alert reads as an empty box"
   finding). Keeps the native control (no appearance:none) so the tick + focus ring are real and accessible. */
.kf-checkbox {
  inline-size:1.15rem; block-size:1.15rem; min-block-size:auto;
  margin-block:var(--k-space-1); accent-color:var(--k-teal-500); cursor:pointer;
}
.kf-checkbox:disabled { cursor:not-allowed; opacity:.55; }

/* UI-GUAR-036 (R2-25): a native <input type="file"> renders ~21px tall — below the WCAG 2.5.5/2.5.8 floor (the live
   sweep flagged the GPX picker at 253×21). Give every file input in the app surface a ≥44 CSS-px touch height so its
   native "Choose file" button sits in a full-size tap target. Cursor + vertical centring keep it usable. */
.kl-shell-main input[type="file"] {
  min-block-size:var(--k-touch); display:inline-flex; align-items:center; cursor:pointer;
}

/* UI-GUAR-036 (R2-25): a breadcrumb is NAVIGATION (not inline prose), so its link is a ≥44 CSS-px touch target. The
   EventCreate breadcrumb link (.kl-ee-crumb) has no scoped sheet, so it was a bare ~20px link — give it a full-height
   tap area (authored globally so any breadcrumb-style link of this class qualifies).

   2026-07-28: the parenthetical above was the INTENT but not the CODE — the selector named .kl-ee-crumb alone, so
   every OTHER crumb link stayed ~20px, and each new one silently inherited the same miss (the vault's .kl-ev-crumb
   was one). Widened to the -crumb link FAMILY, matching the sibling selector in §5e that already styles crumbs this
   way. Anchors only: a touch target is something you TAP, and the family also contains non-interactive spans
   (-crumb-current, -crumb-sep) that must not become inline-flex boxes. */
.kl-shell-main a:where([class^="kl-"],[class*=" kl-"])[class$="-crumb"] {
  display:inline-flex; align-items:center; min-block-size:var(--k-touch);
}

/* UI-GUAR-036 (R2-25): the "More filters" ghost buttons (.kl-filters-more on Approvals — renamed 2026-07-02 from
   .kl-more-filters, whose "-filters" SUFFIX made the :where([class^="kl-"],[class*=" kl-"])[class$="-filters"] CONTAINER family style the BUTTON itself
   (align-items:flex-end → the bottom-hugged label of the owner's Round-2 complaint; live-probed); .kl-drafts-more
   on the Drafts panel) sit in a flex filter row whose cross-sizing shrank them below the foundation 48px floor
   (the live sweep measured 93×30). Authored GLOBALLY (scoped .razor.css would not reach the other component) —
   re-assert the full touch height and stop them from shrinking. */
.kl-shell-main .kl-filters-more,
.kl-shell-main .kl-drafts-more {
  min-block-size:var(--k-touch); flex:0 0 auto;
}

/* Segmented radio-group affordance (label box IS the tap target; the native radio is the accessible control,
   visually collapsed). Authored ONCE here in the GLOBAL sheet so EVERY page that uses it gets a ≥44 CSS-px touch
   target (UI-GUAR-036 / R2-25). It was previously defined ONLY in Onboarding's SCOPED .razor.css, so DogCreate's
   identical markup rendered as bare ~20px native radios (the live sweep flagged dogcreate-sex-* at NN×20). Onboarding
   keeps its own scoped copy (identical sizing) — no conflict. */
.kl-onb-radiogroup { display:flex; gap:var(--k-space-2); flex-wrap:wrap; }
.kl-onb-radio {
  flex:1 1 0; display:flex; align-items:center; justify-content:center;
  min-block-size:var(--k-touch); padding:0 var(--k-space-2);
  border:1px solid var(--k-border); border-radius:var(--k-radius-sm);
  background:var(--k-surface); color:var(--k-text); cursor:pointer; user-select:none;
  transition:background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}
.kl-onb-radio input[type="radio"] { position:absolute; opacity:0; inline-size:1px; block-size:1px; }
.kl-onb-radio.kl-is-checked { border-color:var(--k-teal-500); background:color-mix(in srgb,var(--k-teal-500) 12%,var(--k-surface)); color:var(--k-text); font-weight:600; }
.kl-onb-radio:focus-within { outline:3px solid var(--k-teal-400); outline-offset:2px; }

/* Vertical, grouped checkbox set (owner pref 2026-06-24). The generic FormSectionView coalesces a RUN of consecutive
   boolean(checkbox) placements into ONE <fieldset class="kf-checkgroup">. The fieldset SPANS the full section grid
   (grid-column:1/-1) so the group is one stacked column — not one tiny checkbox stranded per minmax(220px,1fr) cell
   with its label floating above (the "huge blank space" finding). Each member is still a generic FieldWidget .kf-field
   carrying its data-field-key (the structural test surface is unchanged); inside the group it lays out as a compact
   horizontal row — checkbox FIRST, then its label — mirroring ActivityFindsEntry's kf-afe-perf-metrics rows. */
.kf-checkgroup {
  grid-column:1 / -1;
  display:flex; flex-direction:column; gap:var(--k-space-1);
  margin:0; padding:0; border:0; min-inline-size:0;
}
/* A grouped checkbox field: a single inline row (box → label), not the default label-above-input column. The
   FieldWidget emits <label> then <input>; row-reverse + flex-end puts the box first while keeping the label the
   accessible <label for>. The row collapses to its content (no full-width box, no stacked margins). */
.kf-checkgroup .kf-field[data-widget="Checkbox"] {
  flex-flow:row-reverse wrap; justify-content:flex-end; align-items:center;
  gap:var(--k-space-1) var(--k-space-3); margin-block-end:0; padding-block:var(--k-space-1);
}
.kf-checkgroup .kf-field[data-widget="Checkbox"] .kf-label {
  margin:0; color:var(--k-text); font-weight:500; cursor:pointer; line-height:1.3;
}
.kf-checkgroup .kf-field[data-widget="Checkbox"] .kf-checkbox { flex:0 0 auto; }
/* The optional help string for a grouped checkbox drops to its own muted line under the row (full width). */
.kf-checkgroup .kf-field[data-widget="Checkbox"] .kf-help {
  flex-basis:100%; margin-inline-start:calc(1.15rem + var(--k-space-3));
  color:var(--k-text-muted); font-size:13px;
}

/* Native date / time / datetime-local pickers render their INTERNAL chrome (the value text, spinners, and the
   calendar-picker indicator) per the element's used color-scheme — not the element background. Without this they
   default to a light scheme, so on the dark canvas the widget reads as a jarring white-box-on-dark (R11 bar-4/6).
   Pin the scheme to the active brand mode so the native widget matches the themed box. (Light + outdoor are both
   light schemes; only dark — incl dark-outdoor, which keeps the .dark class — flips.)
   The BARE .kf-input/.kf-select/.kf-textarea classes are included (not only the .kl-shell-main-scoped element
   selectors) so the GENERIC FieldWidget controls (record-entry's startAt/endAt datetime-local + every other
   FormRenderer page) carry the scheme even if a future host renders the form outside .kl-shell-main — this was
   the record-entry "raw white date input on dark/outdoor" finding. Harmless on text inputs (color-scheme only
   touches native UA chrome: number spinners, the <select> popup, scrollbars), and correct for consistency. */
.kf-input, .kf-select, .kf-textarea, .kf-checkbox,
.kl-shell-main input[type="date"],
.kl-shell-main input[type="datetime-local"],
.kl-shell-main input[type="time"],
.kl-shell-main input[type="checkbox"] { color-scheme: light; }
html.dark .kf-input, html.dark .kf-select, html.dark .kf-textarea, html.dark .kf-checkbox,
html.dark .kl-shell-main input[type="date"],
html.dark .kl-shell-main input[type="datetime-local"],
html.dark .kl-shell-main input[type="time"],
html.dark .kl-shell-main input[type="checkbox"] { color-scheme: dark; }

/* ---- 5b-combo. Lookup COMBOBOX brand skin (structure is in form-renderer.css) ----
   The pick-or-write control that replaces the native <select> Dropdown. The visible BOX is the wrapper
   (.kf-combobox-control); the inner <input> is a borderless reset (it would otherwise be boxed by the generic
   input rule above — hence the specificity-winning override). Colours/surfaces compose from --k-* so the control
   auto-flips light/dark/outdoor with no per-mode rule needed; the chevron uses the muted text token. */
.kf-combobox-control {
  background:var(--k-surface);
  border:1px solid var(--k-outline);
  border-radius:var(--k-radius-sm);
  color:var(--k-text);
  transition:border-color var(--k-dur-1) var(--k-ease), box-shadow var(--k-dur-1) var(--k-ease);
}
.kf-combobox-control:focus-within {
  border-color:var(--k-accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--k-accent) 22%, transparent);
}
.kf-combobox--disabled .kf-combobox-control {
  background:var(--k-surface-2); color:var(--k-text-muted); cursor:not-allowed;
}
/* Reset the inner input: defeat the generic .kl-shell-main input[type="text"] box (0,2,1) so the wrapper is the box. */
.kl-shell-main .kf-combobox-control input.kf-combobox-input,
.kf-combobox-control input.kf-combobox-input {
  appearance:none; border:0; background:transparent; box-shadow:none;
  min-block-size:42px; padding:0; color:var(--k-text); font-size:15px;
}
.kl-shell-main .kf-combobox-control input.kf-combobox-input:focus,
.kf-combobox-control input.kf-combobox-input:focus {
  outline:none; border:0; box-shadow:none;
}
.kf-combobox-input::placeholder { color:var(--k-text-muted); }
.kf-combobox-chevron { color:var(--k-text-muted); }

/* selected-value chips (multi) — a quiet accent pill that flips with the tokens. */
.kf-chip {
  background:color-mix(in srgb, var(--k-accent) 14%, var(--k-surface));
  border:1px solid color-mix(in srgb, var(--k-accent) 30%, var(--k-border));
  color:var(--k-text);
}
.kf-chip-remove { color:var(--k-text-muted); }
.kf-chip-remove:hover { color:var(--k-text); }

/* the popover listbox + options. */
.kf-combobox-listbox {
  background:var(--k-surface);
  border:1px solid var(--k-border);
  box-shadow:var(--k-elev-3);
  color:var(--k-text);
}
.kf-combobox-option:hover,
.kf-combobox-option.is-active {
  background:color-mix(in srgb, var(--k-accent) 14%, var(--k-surface));
}
.kf-combobox-option.is-selected { font-weight:600; }
.kf-combobox-option-hint { color:var(--k-text-muted); }
.kf-combobox-empty { color:var(--k-text-muted); }

/* the blur add-confirm popup. */
.kf-combobox-addpopup {
  background:var(--k-surface);
  border:1px solid var(--k-border);
  box-shadow:var(--k-elev-3);
  color:var(--k-text);
}
.kf-combobox-addpopup-suggest-head { color:var(--k-text-muted); }
.kf-combobox-useexisting {
  background:var(--k-surface-2);
  color:var(--k-text);
  border:1px solid var(--k-border);
}
.kf-combobox-useexisting:hover { background:color-mix(in srgb, var(--k-accent) 14%, var(--k-surface)); }
.kf-combobox-useexisting-action { color:var(--k-accent); }
.kf-combobox-addpopup-advtoggle { color:var(--k-accent); }
.kf-combobox-addpopup-warn { color:var(--k-critical); }
.kf-combobox-proposed-note { color:var(--k-text-muted); }
.kf-combobox-addpopup-cancel {
  background:var(--k-surface); color:var(--k-text); border:1px solid var(--k-border); border-radius:var(--k-radius-sm);
  min-block-size:var(--k-touch); padding:0 var(--k-space-4); cursor:pointer; font:inherit; font-weight:600;
}
.kf-combobox-addpopup-confirm {
  background:var(--k-accent); color:var(--k-on-accent); border:1px solid var(--k-accent); border-radius:var(--k-radius-sm);
  min-block-size:var(--k-touch); padding:0 var(--k-space-4); cursor:pointer; font:inherit; font-weight:600;
}
.kf-combobox-addpopup-confirm:disabled { opacity:.55; cursor:not-allowed; }

/* read-only field value display (kl-*-readonly / -value) */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-readonly"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-value"] {
  display:inline-block; padding-block:var(--k-space-1);
  color:var(--k-text); font-size:15px;
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-readonly-note"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-note"] { font-size:13px; color:var(--k-text-muted); }

/* The muted-text helper. 98 usages across 39 files had NO rule anywhere in the shared surface — the five
   definitions that existed were all in Kanine.Web page-scoped .razor.css, so `kl-muted` was a dead class on BOTH
   heads: author-labelled muted text rendered at full body colour on /tenant-settings, /admin/settings-hub,
   /users-roles and /onboarding/first-k9, and in nine Kanine.Maui components. Found 2026-07-24 by the MAUI
   undefined-class guard (MauiUndefinedCssClassGuardTests) on its first run.
   The `.kl-shell-main` prefix is REQUIRED, not decoration: `.kl-shell-main p` above sets color:var(--k-text) at
   (0,1,1), so a bare `.kl-muted` at (0,1,0) LOSES on every <p> — which is most usages — and would have been
   completely inert. This matches the -note idiom directly above: same shape, same (0,2,0) specificity, same token.
   Colour only — the -note treatment also sets font-size, and applying that here would be a second, unmeasured
   shift. Both heads render <main class="kl-shell-main"> (web AuthLayout/MainLayout, MAUI MainLayout + GateLayout),
   so this reaches the field head too. */
.kl-shell-main .kl-muted { color:var(--k-text-muted); }

/* field rows that should grid two-up on wider screens */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-grid"] {
  display:grid; gap:var(--k-space-4);
  grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));
  margin-block-end:var(--k-space-4);
}

/* ---- 5c. Buttons ----------------------------------------------------------
   LAYOUT + touch-target on every button shape so each meets the 48px WCAG-2.5.8
   target: stem-at-start (kf-btn-attach, kf-btn-create, kl-btn-confirm) via
   [class*="kf-btn"] / [class*="kl-btn"], AND stem-at-end (kl-more-btn,
   kl-ee-add-btn, kl-dl-export-btn) via :where([class^="kl-"],[class*=" kl-"])[class$="-btn"]. APPEARANCE is set at LOW
   specificity (:where(), 0 weight) so the colour variants below — plain single-
   class selectors — always win by source order, no specificity war.

   UI-GUAR-012 (button hygiene): the BROAD substring/suffix selectors are ELEMENT-GATED to a
   real interactive element via a zero-weight `:where(button,a,input,label,summary,
   [role="button"])` prefix. A button is therefore styled ONLY when it is an actual
   button/anchor/file-label — never a wrapper <div>/<section> or an icon <span> that
   merely ends in a magic suffix (e.g. `ki-ms-add`, the `kl-delete` container, a
   `kl-*-confirm` panel). This kills the broad-substring/suffix over-capture that
   painted two concentric borders (a `.kl-delete` div around a `.kl-action-delete`
   button) and gave glyph spans button chrome. The gate uses `:where()` (0,0,0) so
   EVERY rule keeps its original specificity exactly — the gate only narrows WHICH
   elements match, never the colour-variant cascade order below.
   The opt-in `.kl-btn`/`.kf-btn` classes stay UN-scoped (global, as before) so a
   button rendered OUTSIDE `.kl-shell-main` (dev-login, the error page) keeps its
   layout; those are an explicit opt-in vocabulary placed only on real buttons, never
   a broad capture, so they need no element gate. */
.kl-btn, .kf-btn,
.kl-shell-main :where(button, a, input, label, summary, [role="button"]):where(
  [class*="kf-btn"], [class*="kl-btn"],
  :where([class^="kl-"],[class*=" kl-"])[class$="-save"], :where([class^="kl-"],[class*=" kl-"])[class$="-cancel"], :where([class^="kl-"],[class*=" kl-"])[class$="-add"], :where([class^="kl-"],[class*=" kl-"])[class$="-delete"],
  :where([class^="kl-"],[class*=" kl-"])[class$="-confirm"], :where([class^="kl-"],[class*=" kl-"])[class$="-btn"], :where([class^="kl-"],[class*=" kl-"])[class$="-use-template"]) {
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  min-block-size:var(--k-touch); min-inline-size:var(--k-touch);
  padding:0 var(--k-space-4);
  border-radius:var(--k-radius-md);
  font:inherit; font-size:15px; font-weight:600; line-height:1;
  cursor:pointer; text-decoration:none;
  transition:background var(--k-dur-1) var(--k-ease), color var(--k-dur-1) var(--k-ease),
             border-color var(--k-dur-1) var(--k-ease), transform var(--k-dur-1) var(--k-ease);
}
/* neutral DEFAULT appearance — :where keeps specificity at 0,1,0 so a themed
   variant (0,1,0, later in source) overrides it; an un-themed action button is
   still a clear, tappable, on-brand affordance rather than a UA-grey default.
   Element-gated by the leading :where(button,a,…) (0,0,0) so only real buttons get it. */
.kl-shell-main :where(button, a, input, label, summary, [role="button"]):where([class*="kf-btn"], [class*="kl-btn"], :where([class^="kl-"],[class*=" kl-"])[class$="-btn"], :where([class^="kl-"],[class*=" kl-"])[class$="-save"],
  :where([class^="kl-"],[class*=" kl-"])[class$="-cancel"], :where([class^="kl-"],[class*=" kl-"])[class$="-add"], :where([class^="kl-"],[class*=" kl-"])[class$="-delete"], :where([class^="kl-"],[class*=" kl-"])[class$="-confirm"], :where([class^="kl-"],[class*=" kl-"])[class$="-use-template"]) {
  background:var(--k-surface); color:var(--k-text); border:1px solid var(--k-border);
}
.kl-shell-main :where(button, a, input, label, summary, [role="button"]):where([class*="kf-btn"], [class*="kl-btn"], :where([class^="kl-"],[class*=" kl-"])[class$="-btn"]):hover { background:var(--k-surface-2); }
.kl-btn:active, .kf-btn:active,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-save"]:active,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-confirm"]:active { transform:translateY(1px); }

/* PRIMARY (navy-on-teal, never white text on teal) — 0,1,0 / 0,2,0, beats the
   :where default by source order. The scoped [class$=…] members are 0,2,0. */
.kl-btn--primary, .kf-btn--primary, .kl-btn-primary, .kl-btn-confirm,
.kf-btn-create, .kf-btn-create-from-track,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-save"],
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-confirm"],
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-use-template"],
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-add"] {
  background:var(--k-teal-500); color:var(--k-ink-900); border:1px solid transparent;
}
.kl-btn--primary:hover, .kf-btn--primary:hover, .kl-btn-confirm:hover,
.kf-btn-create:hover, .kf-btn-create-from-track:hover,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-save"]:hover,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-add"]:hover { background:var(--k-teal-400); }

/* GHOST / secondary (cancel) */
.kl-btn--ghost, .kf-btn--ghost, .kl-btn-cancel,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-cancel"] {
  background:transparent; color:var(--k-text); border:1px solid var(--k-border);
}
.kl-btn--ghost:hover, .kf-btn--ghost:hover, .kl-btn-cancel:hover,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-cancel"]:hover { background:var(--k-surface-2); }

/* SECONDARY (tonal) — a weighted-but-not-primary action that must sit ABOVE ghost in the hierarchy without
   competing with the single teal primary (e.g. record-entry's "Save (Draft)" beside the lone "Create record"
   primary). A quiet filled chip on surface-2 with a hairline; hover lifts to a faint accent wash. Brand-token
   only (no raw hex / gradient), so the R11 canvas/token teeth hold. Defined ABOVE the navy `.kl-btn:not([--])`
   default — and it carries `--`, so that default never matches it. */
.kl-btn--secondary, .kf-btn--secondary {
  background:var(--k-surface-2); color:var(--k-text); border:1px solid var(--k-outline);
}
.kl-btn--secondary:hover, .kf-btn--secondary:hover {
  background:color-mix(in srgb, var(--k-accent) 10%, var(--k-surface-2)); border-color:var(--k-accent);
}

/* plain kl-btn default = navy (dev-login personas, the shell sign-out). Scope to the UN-modified kl-btn (no `--`
   variant) so kl-btn--primary / --ghost / --danger / --seal — defined ABOVE in source — actually win instead of
   being overridden by this later, equal-specificity rule (the bug that flattened every button to navy + made the
   disabled ghost read as a grey slab). */
.kl-btn:not([class*="--"]) { background:var(--k-ink-700); color:#fff; border:1px solid transparent; }
.kl-btn:not([class*="--"]):hover { background:var(--k-ink-500); }

/* DANGER / delete */
.kl-btn--danger,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-delete"] {
  background:transparent; color:var(--k-critical);
  border:1px solid color-mix(in srgb, var(--k-critical) 40%, transparent);
}
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-delete"]:hover { background:color-mix(in srgb, var(--k-critical) 10%, transparent); }

/* SEAL (sign-to-seal signature flow) */
.kl-btn--seal, .kf-btn-seal, .kf-btn-seal-init {
  background:var(--k-ink-800); color:#fff; border:1px solid transparent;
}
/* …and its HOVER, which was the one filled variant that never declared one. Every other variant does
   (--primary → teal-400, --ghost/--cancel → surface-2, --secondary → accent wash, the un-modified .kl-btn →
   ink-500), so seal alone fell through to the generic `…[class*="kl-btn"]:hover { background:var(--k-surface-2) }`
   a few rules above — which repainted a NAVY button #EEF2F7 while `color:#fff` stayed, i.e. white text on
   near-white at about 1.1:1. The button did not highlight on hover; it vanished.

   FOUND BY FILMING (B5 owner review 2026-07-29: "the request scope override button becomes visible, it wasn't").
   The page had scrolled under a parked pointer, so /users-roles' full-width "Request scope override" landed under
   the cursor and blanked out for four seconds — and read on camera as a control popping into existence when the
   pointer finally moved off it. The harness half of that is fixed too (Story.ParkPointerAsync now releases the
   real hover whenever the drawn cursor is hidden), but the illegible hover was never a filming problem: it is
   what every user sees the moment they point at a seal button anywhere in the product.

   ink-700 keeps the fixed white foreground at >7:1 and reads as the same button, lit — which is what a hover is
   for. Equal specificity (0,2,0) with the generic rule, won on source order, exactly as --ghost's hover is. */
.kl-btn--seal:hover, .kf-btn-seal:hover, .kf-btn-seal-init:hover {
  background:var(--k-ink-700);
}

.kl-shell-main button:disabled,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-save"]:disabled,
.kl-shell-main :where(button, a, input, label, summary, [role="button"])[class$="-confirm"]:disabled { opacity:.55; cursor:not-allowed; }

/* Dark: a disabled PRIMARY at bare .55 opacity still reads as a saturated, enabled teal fill (the notifications
   "Mark all as read" finding) — wash the fill into the surface + mute the ink so the disabled state is legible in
   dark (E.20). Covers every primary member (class variants + the -save/-confirm/-add suffix magic) app-wide. */
html.dark .kl-btn--primary:disabled, html.dark .kf-btn--primary:disabled,
html.dark .kl-btn-primary:disabled, html.dark .kl-btn-confirm:disabled,
html.dark .kf-btn-create:disabled, html.dark .kf-btn-create-from-track:disabled,
html.dark .kl-shell-main :where(button, input, [role="button"])[class$="-save"]:disabled,
html.dark .kl-shell-main :where(button, input, [role="button"])[class$="-confirm"]:disabled,
html.dark .kl-shell-main :where(button, input, [role="button"])[class$="-add"]:disabled {
  background:color-mix(in srgb, var(--k-teal-500) 28%, var(--k-surface));
  color:var(--k-text-muted);
  opacity:1;
}

/* ---- 5d. Action bars (sticky top/bottom action surfaces) ------------------ */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-actionbar"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-bottombar"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-actions"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-actionbar"] {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--k-space-3);
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-actionbar"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-bottombar"] {
  justify-content:space-between;
  padding:var(--k-space-3) 0;
  margin-block-end:var(--k-space-4);
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-actionbar"] { border-block-end:1px solid var(--k-border); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-bottombar"] { border-block-start:1px solid var(--k-border); margin-block:var(--k-space-6) 0; justify-content:flex-end; }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-actions"] { justify-content:flex-end; }

/* ---- 5e. Identity / header / breadcrumb ----------------------------------- */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-identity"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-header"] {
  display:flex; flex-wrap:wrap; align-items:center; gap:var(--k-space-2) var(--k-space-3);
  min-inline-size:0;
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-subject"] { font-family:var(--k-font-display); font-size:18px; font-weight:700; }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-breadcrumb"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-crumb"] { font-size:13px; color:var(--k-text-muted); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-crumb-sep"] { color:var(--k-outline); padding-inline:var(--k-space-1); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-back"] {
  display:inline-flex; align-items:center; gap:6px;
  color:var(--k-accent); text-decoration:none; font-size:14px; font-weight:600;
  min-block-size:var(--k-touch);
}
/* ID-token display (record GUIDs etc.) — exclude form controls so an *-id filter
   input (e.g. kl-audit-flt-entity-id) keeps the normal input treatment. */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-id"]:not(input):not(select):not(textarea) { font-family:var(--k-font-mono); font-size:13px; color:var(--k-text-muted); }

/* ---- 5f. Status chips (shape + text + colour, never colour alone) --------- */
/* UI-GUAR-027 (R2-16, ruling §H.1 #16) — CHIP-LEAF SCOPING. A status TINT must only ever paint a small
   badge/chip/pill LEAF, never bleed onto a structural surface (row/cell/card/panel/section). The page emits
   `data-status`/`data-cert-status` ON THE ROW (`<tr data-status="active">`, `<tr data-cert-status="CURRENT">`)
   purely as a hook for that row's OWN page-scoped descendant selector (e.g. Lookups `.kl-lookups-row[data-status]
   .kl-lookups-status-pill`); the row is NOT meant to carry the fill. A bare `.kl-shell-main [data-status="active"]`
   selector therefore HIJACKS the row and paints the whole `<tr>` green (the owner's exact Round-2 report). The
   fix: every status SHAPE + TINT selector is gated to a chip leaf — `.kl-chip` OR a class suffixed `-chip`/`-pill`/
   `-badge`/`-status` — AND structural container tags (tr/td/th/table/thead/tbody/p/div/section/article/ul/ol/li)
   are excluded so an over-matching suffix (e.g. a `<td class="kl-lookups-cell-status">` / `<p class="kl-dog-status">`)
   can never become a tinted pill. No page depends on the bare row-level token for its chip colour (every pill
   colours itself via `kl-chip--*` or a page-scoped `.razor.css`), so this tightening is non-breaking. */
/* `.kl-badge` IS a chip, and until 2026-07-25 it was not one anywhere but inside one component.
   THE TRAP, recorded because it is the general lesson: `.kl-badge` was declared ONLY in
   `Lifecycle/RecordStatusBadge.razor.css`, a Blazor-SCOPED sheet — its rules are rewritten with that
   component's own `[b-xxxx]` attribute, so they reach that component's markup and NOTHING else. The
   suffix families below could not cover for it either: they gate on a `-chip`/`-status` leaf, and the
   `-badge` alternative appears only inside a `:where(...)` GATE on the data-status TINTS, which colours
   a pill without ever making one. So the four other users — `SessionRevocationAdmin` (the client name),
   `SignInPolicyAdmin` (5 of them, incl. the live/blocked IdP status) and `CustomFields` (PII /
   Analyzable) — all rendered their badge as PLAIN TEXT. Aliasing the class onto the ONE chip primitive
   fixes all of them in one place; RecordStatusBadge is unaffected, because its scoped rule is (0,2,0)
   and still wins. */
.kl-chip, .kl-badge,
.kl-shell-main :is(:where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]):not(td):not(th):not(tr):not(table):not(thead):not(tbody):not(p):not(div):not(section):not(article):not(ul):not(ol):not(li) {
  display:inline-flex; align-items:center; gap:6px;
  padding:2px 10px; border-radius:var(--k-radius-full);
  font-size:12px; font-weight:600; line-height:1.5; border:1px solid;
  color:var(--k-gray-700); background:var(--k-gray-100); border-color:var(--k-gray-300);
}
/* UI-GUAR-027 (R2-16) — NEUTRAL-base dark remap. The NEUTRAL chip base above uses the FIXED --k-gray-100 fill /
   --k-gray-700 text (these do NOT re-point per mode), so a plain `.kl-chip` with no `--*` variant (e.g. the
   agency-dashboard / reliability metric-id pill `kl-chip kl-agency-tile-id`) keeps a LIGHT gray-100 fill in the dark
   field — an unreadable bright pill (caught by the live badge-contrast tooth). Re-map the neutral base to a dark
   NEUTRAL surface fill + light text (zero-saturation, restrained) so an un-variant chip is legible in dark +
   dark-outdoor, mirroring the `kl-chip--*` remaps below. Variant chips override this via their own dark remap. */
html.dark .kl-chip, html.dark .kl-badge,
html.dark .kl-shell-main :is(:where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]):not(td):not(th):not(tr):not(table):not(thead):not(tbody):not(p):not(div):not(section):not(article):not(ul):not(ol):not(li) {
  color:var(--k-text); background:var(--k-surface-3); border-color:var(--k-border);
}
/* The chip-leaf gate the derived data-* tints below reuse: the SAME element must also be a chip/badge/pill leaf
   (never a row/cell/card). `:where(...)` contributes ZERO specificity, so the tint keeps the same weight it had
   before (a single [data-*] attribute), but it can no longer match a structural surface. */
.kl-shell-main [data-cert-status="CURRENT"]:where(.kl-chip, :where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-pill"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]),
.kl-shell-main [data-status="active"]:where(.kl-chip, :where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-pill"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]),
.kl-chip--success {
  color:#0b5c3c; background:color-mix(in srgb,var(--k-success) 12%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-success) 35%,var(--k-surface));
}
.kl-shell-main [data-cert-status="PENDING"]:where(.kl-chip, :where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-pill"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]), .kl-chip--caution {
  color:#7a4a00; background:color-mix(in srgb,var(--k-caution) 14%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-caution) 38%,var(--k-surface));
}
.kl-shell-main [data-cert-status="EXPIRED"]:where(.kl-chip, :where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-pill"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]), .kl-chip--critical {
  color:#7a1b12; background:color-mix(in srgb,var(--k-critical) 12%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-critical) 35%,var(--k-surface));
}
.kl-chip--info { color:#0b4f8a; background:color-mix(in srgb,var(--k-info) 10%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-info) 32%,var(--k-surface)); }
/* The two `kl-badge--*` variants SignInPolicyAdmin emits (`Live` / the blocked reason) were declared
   NOWHERE, so they were dead beside a base that was itself dead. Aliased onto the chip variants above
   rather than re-declaring their colours — one palette, one place. */
.kl-badge--success { color:#0b5c3c; background:color-mix(in srgb,var(--k-success) 12%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-success) 35%,var(--k-surface)); }
html.dark .kl-badge--success { color:#9ce6c1; background:color-mix(in srgb,var(--k-success) 30%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-success) 55%,var(--k-surface)); }
.kl-badge--muted { color:var(--k-text-muted); background:var(--k-surface-2); border-color:var(--k-border); }

/* Dark-mode pill contrast (R11 design-skeptic BLOCKER): the fixed dark text colours above are near-invisible on the
   dark surface. Re-map to LIGHT text on a more-saturated fill so STATUS — load-bearing, evidentiary info — stays
   clearly legible in dark + dark-outdoor. (Light/outdoor keep the dark-on-tint above; high contrast on white.)
   The SAME chip-leaf gate as the light tints — a `<tr>`/cell is never painted in dark either. */
html.dark .kl-shell-main [data-cert-status="CURRENT"]:where(.kl-chip, :where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-pill"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]),
html.dark .kl-shell-main [data-status="active"]:where(.kl-chip, :where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-pill"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]),
html.dark .kl-chip--success {
  color:#9ce6c1; background:color-mix(in srgb,var(--k-success) 30%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-success) 55%,var(--k-surface));
}
html.dark .kl-shell-main [data-cert-status="PENDING"]:where(.kl-chip, :where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-pill"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]), html.dark .kl-chip--caution {
  color:#f4c884; background:color-mix(in srgb,var(--k-caution) 32%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-caution) 55%,var(--k-surface));
}
html.dark .kl-shell-main [data-cert-status="EXPIRED"]:where(.kl-chip, :where([class^="kl-"],[class*=" kl-"])[class$="-chip"], :where([class^="kl-"],[class*=" kl-"])[class$="-pill"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"], :where([class^="kl-"],[class*=" kl-"])[class$="-status"]), html.dark .kl-chip--critical {
  color:#f6ab9d; background:color-mix(in srgb,var(--k-critical) 30%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-critical) 55%,var(--k-surface));
}
html.dark .kl-chip--info {
  color:#aacdff; background:color-mix(in srgb,var(--k-info) 30%,var(--k-surface)); border-color:color-mix(in srgb,var(--k-info) 55%,var(--k-surface));
}

/* ---- 5g. Banners / notices ------------------------------------------------ */
/* `.kl-signin-policy-ok` is named here explicitly (2026-07-25). Its SIBLING, `kl-signin-policy-error`, ends
   in `-error` and so picks up the banner treatment from the suffix family below; the success half ends in
   `-ok`, matches no family, and was declared nowhere — so on /admin/sessions and /admin/sign-in-policy a
   FAILURE rendered as a bordered banner and the matching SUCCESS rendered as a bare line of body text. Found
   by `app/tools/page-review/unstyled-class-scan.mjs`; the tint comes from the `-valid`/success rule below. */
.kl-shell-main .kl-signin-policy-ok,
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-notice"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-banner"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-conflict"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-failure"] {
  display:flex; align-items:flex-start; gap:var(--k-space-2);
  padding:var(--k-space-3) var(--k-space-4); border-radius:var(--k-radius-md);
  border:1px solid var(--k-border); background:var(--k-surface-2);
  font-size:14px; line-height:1.5; margin-block-end:var(--k-space-4);
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-conflict"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-failure"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-error"] {
  color:#7a1b12; background:color-mix(in srgb,var(--k-critical) 10%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-critical) 38%,var(--k-surface));
}
/* Dark remap (mirrors the R11 critical-chip pattern at §5f): --k-critical does NOT flip, so the fixed dark-red
   #7a1b12 text above is invisible once --k-surface darkens the tinted box. Re-point to LIGHT critical text on a
   more-saturated fill so error/conflict/failure banners stay legible in dark + dark-outdoor. */
html.dark .kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-conflict"],
html.dark .kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-failure"],
html.dark .kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-error"] {
  color:#f6ab9d; background:color-mix(in srgb,var(--k-critical) 30%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-critical) 55%,var(--k-surface));
}

/* ---- 5g-1. OUT-OF-FLOW REFUSAL SLOTS on the record command surface (owner ruling 2026-07-27) ---------------
   Found by the owner watching the sign-and-seal film: when a signing attempt is refused, the step-up refusal
   banner appeared INSIDE the lifecycle action bar in normal flow. That grew the bar's intrinsic width (the
   banner's max-content is a full sentence) and added a flex line, so the shared command row re-wrapped and
   Delete + "Edit record" dropped onto a second line — the controls moved under the user's cursor at the exact
   moment they were told the action had failed. The same shape bit the sibling soft-delete refusal.

   THE GUARANTEE, and why it is this and not a reserved height: `position:absolute` removes the slot from flex
   layout entirely — it is not a flex item, so it contributes NOTHING to the row's intrinsic sizes, line count or
   gaps. The action row's geometry is therefore identical with and without a message unconditionally, rather than
   "as long as the message fits the reservation" (which a min-height reservation can only promise until someone
   ships a longer string or a narrower viewport). The slot is rendered UNCONDITIONALLY by
   LifecycleActionBar.razor / RecordDeleteAction.razor so the row's DOM is byte-identical in both states too, and
   so the role="alert" live region exists BEFORE its text arrives.

   `.kl-record` is the DEFAULT containing block: a host that has not chosen a home for the message still anchors
   it to the shared surface rather than to the viewport. The two record-review pages re-anchor it deliberately
   (web: the page header row, per the ruling; device: directly under the command bar) in their own .razor.css.
   `pointer-events` is dropped on the (usually empty) slot and restored on the banner, so an invisible box can
   never eat a click in whatever region the host parks it over.

   Plain descendant selectors — the :where() families above contribute ZERO specificity, so `[class$="-failure"]`
   lands at (0,2,0); `.kl-shell-main .kl-actionbar-message > .kl-failure` is (0,3,0) and reliably wins the
   margin reset regardless of source order. */
.kl-shell-main .kl-record { position: relative; }
.kl-shell-main .kl-actionbar-message,
.kl-shell-main .kl-deleteaction-message {
  position: absolute;
  inset-inline: auto 0;
  inset-block-end: calc(100% + var(--k-space-2));
  z-index: 4;
  inline-size: max-content;
  max-inline-size: min(46rem, 100%);
  pointer-events: none;
}
.kl-shell-main .kl-actionbar-message > .kl-failure,
.kl-shell-main .kl-deleteaction-message > .kl-failure {
  margin-block-end: 0;
  pointer-events: auto;
}

/* THE EXPLICIT .kl-banner FAMILY (final-draft foundation heal). ~10 pages compose
   `class="kl-banner kl-banner--success|--caution|--error"` (save-success readbacks, the scheduled-reports
   email-degrade band, seat-cap notices…). The suffix families above NEVER matched them: :where([class^="kl-"],[class*=" kl-"])[class$="…"] tests the
   FULL class-attribute suffix, and on those elements the attribute ends in "--success"/"--caution" — so the
   banners rendered completely UNSTYLED (no box, no tint). Explicit classes fix the whole family at once.
   Recipe = the canonical status tint (§5f): fill ≈12% into the flipping surface, border ≈40%, dark-status text;
   dark remap = light status text over 30%/55% fills. The text IS the meaning (never color alone). */
.kl-banner {
  display:flex; align-items:flex-start; gap:var(--k-space-2);
  padding:var(--k-space-3) var(--k-space-4); border-radius:var(--k-radius-md);
  border:1px solid var(--k-border); background:var(--k-surface-2);
  font-size:14px; line-height:1.5; margin-block-end:var(--k-space-4);
}
.kl-banner--success {
  color:#0b5c3c; background:color-mix(in srgb,var(--k-success) 12%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-success) 40%,var(--k-surface));
}
.kl-banner--caution {
  color:#7a4a00; background:color-mix(in srgb,var(--k-caution) 14%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-caution) 40%,var(--k-surface));
}
.kl-banner--error, .kl-banner--critical {
  color:#7a1b12; background:color-mix(in srgb,var(--k-critical) 12%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-critical) 40%,var(--k-surface));
}
/* Canonical dark status palette (E.20): light status text, 30% fill / 55% border over the dark surface. */
html.dark .kl-banner--success {
  color:#9ce6c1; background:color-mix(in srgb,var(--k-success) 30%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-success) 55%,var(--k-surface));
}
html.dark .kl-banner--caution {
  color:#f4c884; background:color-mix(in srgb,var(--k-caution) 30%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-caution) 55%,var(--k-surface));
}
html.dark .kl-banner--error, html.dark .kl-banner--critical {
  color:#f6ab9d; background:color-mix(in srgb,var(--k-critical) 30%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-critical) 55%,var(--k-surface));
}
/* INFO member of the explicit family (the signup "check your email" band composes it; it previously
   fell back to the neutral base). Same recipe as .kl-chip--info at §5f. */
.kl-banner--info {
  color:#0b4f8a; background:color-mix(in srgb,var(--k-info) 10%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-info) 38%,var(--k-surface));
}
html.dark .kl-banner--info {
  color:#aacdff; background:color-mix(in srgb,var(--k-info) 30%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-info) 55%,var(--k-surface));
}

/* ---- 5h. State kit (loading / empty / error / not-found / denied) --------- */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-state"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-empty"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-loading"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-denied"] {
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:var(--k-space-3); text-align:center;
  padding:var(--k-space-12) var(--k-space-6);
  color:var(--k-text-muted);
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-state-icon"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-state-message"] { color:var(--k-text-muted); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-state-icon"] { font-size:32px; opacity:.6; }
.kl-notfound, .kl-error {
  display:block; padding:var(--k-space-4);
  border-radius:var(--k-radius-md);
  /* Mix the critical tint into the FLIPPING surface token, not a literal #fff — otherwise the box stays a glaring
     near-white card on the dark canvas (the account-page "could not be loaded" eyesore). */
  color:#7a1b12; background:color-mix(in srgb,var(--k-critical) 10%,var(--k-surface));
  border:1px solid color-mix(in srgb,var(--k-critical) 35%,var(--k-surface));
}
/* Dark remap so the dark-red text doesn't vanish on the now-darkened tint (same R11 critical palette as above). */
html.dark .kl-notfound, html.dark .kl-error {
  color:#f6ab9d; background:color-mix(in srgb,var(--k-critical) 30%,var(--k-surface));
  border-color:color-mix(in srgb,var(--k-critical) 55%,var(--k-surface));
}
.kl-valid { color:#0b5c3c; }
/* --k-success does not flip; the fixed dark-green is near-invisible on the dark dialog/card surface. */
html.dark .kl-valid { color:#9ce6c1; }

/* ---- 5i. Dialogs (the PANEL card inside the .kl-modal-overlay scrim) -------
   .kl-dialog is the modal PANEL, NOT the overlay: it is centered + scrimmed by
   its <KlOverlay> wrapper (.kl-modal-overlay above). It carries no positioning
   of its own — it is a plain card. The former `.kl-dialog::backdrop` rule is
   RETIRED (UI-GUAR-033): the app ships no native <dialog>/showModal, so a
   ::backdrop on the <div>/<form> these classes mark NEVER rendered — it gave a
   false sense of a scrim while leaving the modal scrimless. The real scrim is
   now the overlay element, always present, never a pseudo-element. */
.kl-dialog {
  border:0; padding:0; border-radius:var(--k-radius-lg);
  max-inline-size:30rem; inline-size:calc(100% - 2rem);
  background:var(--k-surface); color:var(--k-text); box-shadow:var(--k-elev-3);

  /* A tall dialog (the daily-log create form, with its ten fields, is taller than a phone) MUST scroll on touch.
     The overlay-scroll fix below relied on `align-items: safe center`, which does not reliably let a touch drag
     scroll the scrim in the Android WebView — so on a phone the dialog overflowed and could not be scrolled inside
     at all (reported 2026-07-24). The robust fix is to cap the DIALOG itself to the viewport and make IT the scroll
     container: this works on touch regardless of `safe` support. 100dvh (dynamic viewport height) accounts for the
     mobile browser/WebView chrome that 100vh ignores; the vh line is the fallback for engines without dvh.
     overscroll-behavior:contain stops the drag from chaining out to the scrim once the dialog hits its end. */
  max-block-size: calc(100vh - 2 * var(--k-space-4));
  max-block-size: calc(100dvh - 2 * var(--k-space-4));
  overflow-y: auto;
  overscroll-behavior: contain;
}
.kl-dialog > * { padding-inline:var(--k-space-6); }
.kl-dialog > :first-child { padding-block-start:var(--k-space-6); }
.kl-dialog-actions, .kl-reason-actions {
  display:flex; justify-content:flex-end; gap:var(--k-space-2);
  padding:var(--k-space-4) var(--k-space-6) var(--k-space-6);
}
.kl-reason { display:flex; flex-direction:column; gap:var(--k-space-2); padding:var(--k-space-4) var(--k-space-6); }

/* DEV-ONLY step-up autofill hint (SignatureStepUpDialog) — renders only when the development-gated IStepUpAutofill
   pre-fills the masked secret field, so a developer can see which persona phrase was filled. Never shown in
   production (no host supplies a PrefilledSecret there). A quiet dashed caution note, not a first-class control. */
.kl-stepup-devhint {
  display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--k-space-2);
  margin:0 var(--k-space-6) 0; padding:var(--k-space-2) var(--k-space-3);
  border:1px dashed color-mix(in srgb,var(--k-caution) 55%,var(--k-surface)); border-radius:var(--k-radius-md);
  background:color-mix(in srgb,var(--k-caution) 8%,var(--k-surface)); font-size:0.85rem;
}
.kl-stepup-devhint-label { color:var(--k-text-muted); }
.kl-stepup-devhint-value { font-weight:600; user-select:all; }

/* ---- 5j. Menus / overflow (top-layer-safe popovers) ----------------------- */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-overflow"] {
  display:inline-flex; align-items:center; justify-content:center;
  min-block-size:var(--k-touch); min-inline-size:var(--k-touch);
  border:1px solid transparent; border-radius:var(--k-radius-md);
  background:transparent; color:var(--k-text-muted); cursor:pointer; font-size:18px;
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-overflow"]:hover { background:var(--k-surface-2); color:var(--k-text); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-menu"] {
  position:absolute; z-index:50; min-inline-size:12rem;
  background:var(--k-surface); border:1px solid var(--k-border);
  border-radius:var(--k-radius-md); box-shadow:var(--k-elev-3);
  padding:var(--k-space-1); margin:0;
}
/* Owner UI review (item 2, swept project-wide): the reusable click-away backdrop for any floating [class$="-menu"]
   popover. A transparent full-viewport catcher sitting BELOW the menu's z-index:50, so a click anywhere outside the
   menu dismisses it while the menu itself stays interactive above the scrim. Render it as a SIBLING of the menu panel
   inside the @if(open) block, with an @onclick that clears the open-state. */
.kl-menu-scrim { position:fixed; inset:0; z-index:45; background:transparent; border:0; }
/* Owner UI review (item 2, swept): the click-away backdrop for the aria-modal SLIDE-OVER panels (.kl-slideover /
   .kl-cert-slideover, z-index:40 / 1000). This class was referenced by Dogs / Medical / Certifications / TrainingAids
   slide-overs but had NO rule, so click-away was non-functional in a real browser. It mirrors the .kl-dialog modal
   backdrop dim and sits BELOW the slide-over panel (z-index:39) so an outside click hits it (the sibling scrim's
   @onclick closes the panel) while the panel stays interactive above it. */
.kl-slideover-scrim { position:fixed; inset:0; z-index:39; background:rgb(10 26 43 / .55); backdrop-filter:blur(2px); border:0; } /* UI-GUAR-033: bumped .45→.55 so even edge slide-overs lay a ≥0.5 dim scrim. */

/* THE SHARED SLIDE-OVER PANEL FAMILY (final-draft foundation heal). Dogs + Medical mount `aside.kl-slideover`
   (+ -head/-title/-actions/-facts/-section/-foot) but the panel rules lived ONLY in TrainingAids' Blazor-SCOPED
   sheet — so opening quick-view on those pages dimmed the screen and dropped an UNPOSITIONED block at the end of
   the document flow. Promoted here (the scrim above was already shared); a page's scoped kl-slideover-* rules
   still win ties (the [b-hash] attribute out-specifies these plain classes), so TrainingAids is unchanged. */
.kl-slideover {
  position:fixed; inset-block:0; inset-inline-end:0; z-index:40;
  inline-size:min(27rem, 100vw);
  display:flex; flex-direction:column;
  background:var(--k-surface); border-inline-start:1px solid var(--k-border);
  box-shadow:var(--k-elev-3);
  overflow-y:auto; overscroll-behavior:contain;
}
.kl-slideover-head {
  display:flex; align-items:flex-start; justify-content:space-between; gap:var(--k-space-3);
  padding:var(--k-space-6); border-block-end:1px solid var(--k-border);
}
.kl-slideover-head h2, .kl-slideover-title {
  margin:0; font-family:var(--k-font-display); font-size:18px; font-weight:700; color:var(--k-text);
  overflow-wrap:anywhere;
}
.kl-slideover-actions {
  display:flex; align-items:center; gap:var(--k-space-2); flex-wrap:wrap;
  padding:var(--k-space-3) var(--k-space-6); border-block-end:1px solid var(--k-border);
}
dl.kl-slideover-facts, .kl-slideover-facts {
  display:grid; grid-template-columns:repeat(auto-fit, minmax(160px, 1fr));
  gap:var(--k-space-3) var(--k-space-4);
  margin:0; padding:var(--k-space-4) var(--k-space-6);
}
.kl-slideover-facts > div { min-inline-size:0; }
.kl-slideover-facts dt {
  font-size:12px; font-weight:600; letter-spacing:.04em; text-transform:uppercase;
  color:var(--k-text-muted);
}
.kl-slideover-facts dd { margin:0; font-weight:600; color:var(--k-text); overflow-wrap:anywhere; }
.kl-slideover-section {
  padding:var(--k-space-4) var(--k-space-6); border-block-start:1px solid var(--k-border);
}
.kl-slideover-foot {
  margin-block-start:auto;
  display:flex; align-items:center; justify-content:flex-end; gap:var(--k-space-2); flex-wrap:wrap;
  padding:var(--k-space-4) var(--k-space-6); border-block-start:1px solid var(--k-border);
}
/* Dark: a crisp left edge + heavier shadow so the panel reads AS a raised surface against the ink canvas. */
html.dark .kl-slideover {
  border-inline-start-color:color-mix(in srgb,var(--k-border) 72%,var(--k-text-muted));
  box-shadow:-8px 0 24px rgb(0 0 0 / .45);
}
/* Outdoor (glove-grade): heavier separators so the panel holds up in direct sun. */
html[data-outdoor="true"] .kl-slideover { border-inline-start-width:2px; }
html[data-outdoor="true"] .kl-slideover-head,
html[data-outdoor="true"] .kl-slideover-actions,
html[data-outdoor="true"] .kl-slideover-section,
html[data-outdoor="true"] .kl-slideover-foot { border-color:var(--k-outline); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-menu-item"] {
  display:flex; align-items:center; gap:var(--k-space-2);
  /* UI-GUAR-036 (R2-25): row/context menu items are ≥44 CSS-px touch targets (was 40). */
  inline-size:100%; min-block-size:var(--k-touch); padding:0 var(--k-space-3);
  background:transparent; border:0; border-radius:var(--k-radius-sm);
  font:inherit; font-size:14px; color:var(--k-text); text-align:start; cursor:pointer;
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-menu-item"]:hover { background:var(--k-surface-2); }
.kl-menu-pinned { opacity:1 !important; pointer-events:auto !important; }

/* ---- 5k. Tabs (active driven by [aria-selected]) -------------------------- */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-tablist"] {
  display:flex; gap:var(--k-space-6); align-items:center;
  overflow-x:auto; border-block-end:1px solid var(--k-border);
  margin-block-end:var(--k-space-4);
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-tab"] {
  appearance:none; background:none; border:0; cursor:pointer; white-space:nowrap;
  padding:var(--k-space-2) 0; min-block-size:var(--k-touch);
  border-block-end:2px solid transparent; color:var(--k-text-muted);
  font:inherit; font-size:14px; font-weight:600;
  transition:color var(--k-dur-1) var(--k-ease), border-color var(--k-dur-1) var(--k-ease);
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-tab"]:hover { color:var(--k-accent); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-tab"][aria-selected="true"] { color:var(--k-text); border-block-end-color:var(--k-teal-700); }
html.dark .kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-tab"][aria-selected="true"] { border-block-end-color:var(--k-teal-400); }
.kl-shell-main [role="tabpanel"][hidden] { display:none; }

/* ---- 5l. Lists / rows / facts --------------------------------------------- */
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-list"] { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--k-space-2); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-row"] {
  display:flex; align-items:center; gap:var(--k-space-3);
  padding:var(--k-space-3) var(--k-space-4);
  background:var(--k-surface); border:1px solid var(--k-border); border-radius:var(--k-radius-md);
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-facts"] {
  display:grid; gap:var(--k-space-2) var(--k-space-6);
  grid-template-columns:repeat(auto-fit, minmax(180px, 1fr));
}
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-filters"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-filterdrawer"] {
  display:flex; flex-wrap:wrap; gap:var(--k-space-3); align-items:flex-end;
  margin-block-end:var(--k-space-4);
}

/* ---- 5m. Toggles / switches / reveals ------------------------------------- */
.kl-shell-main [role="switch"],
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-toggle"] {
  display:inline-flex; align-items:center; gap:var(--k-space-2);
  min-block-size:var(--k-touch); padding:0 var(--k-space-3);
  background:var(--k-surface-2); border:1px solid var(--k-border); border-radius:var(--k-radius-full);
  font:inherit; font-size:14px; color:var(--k-text); cursor:pointer;
}
.kl-shell-main [role="switch"][aria-checked="true"] { background:color-mix(in srgb,var(--k-teal-500) 22%,var(--k-surface)); border-color:var(--k-teal-700); }
.kl-shell-main :where([class^="kl-"],[class*=" kl-"])[class$="-reveal"] { color:var(--k-accent); cursor:pointer; font-weight:600; }

/* ---- 5n. Table-structural guard -------------------------------------------
   The suffix families above (-row / -status / -list / -grid / -field / -header …)
   must NOT hijack native <table> layout on the table-based pages (approvals,
   conflicts, lookups, record-list, report-analytics): a :where([class^="kl-"],[class*=" kl-"])[class$="-row"] on a <tr>
   would set display:flex and collapse the columns. Re-assert native table display
   — the !important beats the 0,2,0 attribute-family rules — and clear the pill/card
   border-radius that would otherwise round table rows/cells. Per-table colour,
   width, borders, and spacing stay with each page's own kl-*-table CSS + R11. */
.kl-shell-main table { display:table !important; }
.kl-shell-main thead { display:table-header-group !important; }
.kl-shell-main tbody { display:table-row-group !important; }
.kl-shell-main tfoot { display:table-footer-group !important; }
.kl-shell-main tr { display:table-row !important; border-radius:0; }
.kl-shell-main th, .kl-shell-main td { display:table-cell !important; border-radius:0; }

/* ---- 6. Theme toggle control (shell chrome — global, outside main) -------- */
.kl-theme-toggle { display:inline-flex; gap:var(--k-space-1); align-items:center; }
.kl-theme-btn {
  display:inline-flex; align-items:center; justify-content:center;
  min-block-size:36px; min-inline-size:36px; padding:0 var(--k-space-2);
  background:rgb(255 255 255 / .08); color:#fff;
  border:1px solid rgb(255 255 255 / .18); border-radius:var(--k-radius-md);
  font:inherit; font-size:13px; cursor:pointer;
  transition:background var(--k-dur-1) var(--k-ease);
}
.kl-theme-btn:hover { background:rgb(255 255 255 / .16); }
.kl-theme-btn[aria-pressed="true"] { background:var(--k-teal-500); color:var(--k-ink-900); border-color:transparent; }

/* ---- 7. Application SHELL chrome (ShellNav — global, outside main) ----------
   The permission-gated left rail + top bar + mobile bottom nav + DEV persona
   switcher (UI-GUAR-006 / IAM-006). On-brand via the --k-* token mirror so it
   flips correctly in light / dark / outdoor (the ink ramp is the chrome canvas
   in every mode). Lives OUTSIDE .kl-shell-main so the broad suffix families
   never bleed onto it. Additive: it mounts ABOVE the existing legacy header. */
.kl-shellnav-rail {
  display:flex; flex-direction:column; gap:var(--k-space-1);
  background:var(--k-ink-800); color:#fff;
  inline-size:240px; padding:var(--k-space-2);
  border-inline-end:1px solid rgb(255 255 255 / .08);
  /* The rail is a fixed-height flex column at every width (full-height fixed column on desktop, a full-height drawer
     when open on narrow). It never scrolls as a whole — the brand stays pinned and the inner .kl-shellnav-list is the
     SOLE scroll region — so a long persona's items (incl. Expenses) are reachable instead of clipped below the fold. */
  overflow:hidden;
}
.kl-shellnav-brand {
  display:flex; align-items:center; gap:var(--k-space-2);
  padding:var(--k-space-2) var(--k-space-3); min-block-size:var(--k-touch);
  color:#fff; text-decoration:none; font-family:var(--k-font-display);
  font-weight:800; font-size:18px; letter-spacing:-.01em;
}
.kl-shellnav-mark {
  display:inline-flex; align-items:center; justify-content:center;
  inline-size:28px; block-size:28px; border-radius:var(--k-radius-md);
  background:var(--k-teal-500); color:var(--k-ink-900); font-weight:800;
}
/* Brand lockup image in the (always-navy) rail — the white-text 'dark' variant. */
.kl-shellnav-logo { display:block; block-size:54px; inline-size:auto; }
/* Onboarding wizard-rail brand mark — follows the theme bg, so light/dark swap. */
.kl-wizard-rail__logo { display:block; block-size:40px; inline-size:auto; margin-block-end:var(--k-space-2); }
.kl-wizard-rail__logo--dark { display:none; }
html.dark .kl-wizard-rail__logo--light { display:none; }
html.dark .kl-wizard-rail__logo--dark { display:block; }
/* -- Onboarding wizard STEP RAIL (shared by the onboarding routes) -- a restrained vertical stepper. The brand block + the
   logo swap are above; these rules give the rail a card surface + the steps a done / active / deferred state (the
   markup carries .kl-wizard-step with .kl-is-done / .kl-wizard-step--active / .kl-is-deferred). Authored but never
   styled before pg48 — the steps rendered as a browser-default bullet list. All on-token, so it flips light/dark/outdoor. */
.kl-wizard-rail {
  align-self:start;
  display:flex; flex-direction:column; gap:var(--k-space-4);
  padding:var(--k-space-4); min-inline-size:14rem;
  background:var(--k-surface); border:1px solid var(--k-border);
  border-radius:var(--k-radius-lg); box-shadow:0 1px 3px rgb(0 0 0 / .06);
}
.kl-wizard-rail__brand { display:flex; flex-direction:column; }
.kl-wizard-rail__title { font-family:var(--k-font-display); font-weight:800; font-size:16px; color:var(--k-text); }
.kl-wizard-rail__sub { font-size:12px; color:var(--k-text-muted); }
.kl-wizard-rail ul, .kl-wizard-rail ol { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px; }
.kl-wizard-step {
  display:flex; align-items:center; gap:var(--k-space-2);
  padding:var(--k-space-2) var(--k-space-3); min-block-size:var(--k-touch);
  border-radius:var(--k-radius-md); font-size:14px; font-weight:600;
  color:var(--k-text-muted); border-inline-start:2px solid transparent;
}
.kl-wizard-step::before {
  content:""; inline-size:9px; block-size:9px; border-radius:var(--k-radius-full); flex:0 0 auto;
  background:transparent; border:2px solid var(--k-outline);
}
.kl-wizard-step > a { color:inherit; text-decoration:none; font:inherit; }
.kl-wizard-step > a:hover { text-decoration:underline; }
/* A navigable step rendered as a <button> (WizardRail when OnStepSelected is bound) — reset to read like the > a label. */
.kl-wizard-step__btn {
  appearance:none; background:transparent; border:0; margin:0; padding:0;
  font:inherit; color:inherit; text-align:start; cursor:pointer;
  display:flex; align-items:center; gap:var(--k-space-2); inline-size:100%; min-block-size:var(--k-touch);
}
.kl-wizard-step__btn:hover { text-decoration:underline; }
.kl-wizard-step__btn:focus-visible { outline:2px solid var(--k-accent); outline-offset:2px; }
/* DONE — a filled accent dot; the label reads at full body ink. */
.kl-wizard-step.kl-is-done { color:var(--k-text); }
.kl-wizard-step.kl-is-done::before { background:var(--k-accent); border-color:var(--k-accent); }
/* ACTIVE (current step) — teal lead-rail + emphasis + a ringed filled dot. */
.kl-wizard-step.kl-wizard-step--active {
  color:var(--k-text); font-weight:800;
  background:color-mix(in srgb, var(--k-accent) 12%, transparent);
  border-inline-start-color:var(--k-accent);
}
.kl-wizard-step.kl-wizard-step--active::before {
  background:var(--k-accent); border-color:var(--k-accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--k-accent) 25%, transparent);
}
/* DEFERRED (unbuilt step) — dimmed + a dashed marker; clearly not-yet-reachable, never a dead link. */
.kl-wizard-step.kl-is-deferred { color:var(--k-text-muted); opacity:.6; }
.kl-wizard-step.kl-is-deferred::before { border-style:dashed; border-color:var(--k-outline); }

/* -- HORIZONTAL wizard TRACKER (Task-30) — the WizardRail horizontal variant: a compact top progress band for the
   narrow / field record-build surface. A connected progress line of numbered dots (a checkmark once done), the ACTIVE
   step named in a caption, completed dots tappable to jump back. On-token so it flips light/dark/outdoor. The
   record-build surface owns the show/hide (below): hidden on wide (the vertical .kl-wizard-rail is the desktop
   stepper), shown ≤640. Onboarding never renders the horizontal variant, so its rail is untouched. */
.kl-wizard-track {
  background:var(--k-surface); border:1px solid var(--k-border);
  border-radius:var(--k-radius-lg); box-shadow:0 1px 3px rgb(0 0 0 / .06);
  padding:var(--k-space-3) var(--k-space-2) var(--k-space-2);
}
.kl-wizard-track__list { list-style:none; margin:0; padding:0; display:flex; align-items:flex-start; }
.kl-wizard-track__step { position:relative; flex:1 1 0; min-inline-size:0; display:flex; justify-content:center; }
/* The connector line runs from the previous dot's centre to this one's (drawn on each step but the first). */
.kl-wizard-track__step::before {
  content:""; position:absolute;
  /* Centre the connector on the dot's vertical middle (the dot is centred in the touch-tall button,
     so its centre sits at touch/2 — not at the dot radius), and inset both ends so the line stops
     just short of each circle: clean segments BETWEEN the dots, never drawn behind them. */
  top:calc(var(--k-touch, 44px) / 2); transform:translateY(-50%);
  inset-inline-start:calc(-50% + 18px); inline-size:calc(100% - 36px); block-size:2px;
  background:var(--k-outline); z-index:0;
}
.kl-wizard-track__step:first-child::before { display:none; }
/* A done / active step fills its lead-in connector with accent = a progressing "how far along" line. */
.kl-wizard-track__step.kl-is-done::before,
.kl-wizard-track__step.kl-wizard-step--active::before { background:var(--k-accent); }
.kl-wizard-track__btn {
  appearance:none; background:transparent; border:0; margin:0; padding:0;
  font:inherit; cursor:pointer; z-index:1;
  display:inline-flex; align-items:center; justify-content:center;
  min-block-size:var(--k-touch); min-inline-size:var(--k-touch);
}
.kl-wizard-track__btn:focus-visible { outline:2px solid var(--k-accent); outline-offset:2px; border-radius:var(--k-radius-full); }
.kl-wizard-track__dot {
  inline-size:28px; block-size:28px; flex:0 0 auto; border-radius:var(--k-radius-full);
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--k-surface); border:2px solid var(--k-outline);
  font-size:13px; font-weight:700; line-height:1; color:var(--k-text-muted);
  transition:background var(--k-dur-1) var(--k-ease), border-color var(--k-dur-1) var(--k-ease), color var(--k-dur-1) var(--k-ease);
}
/* DONE — filled accent dot with a check. */
.kl-wizard-track__step.kl-is-done .kl-wizard-track__dot { background:var(--k-accent); border-color:var(--k-accent); color:var(--k-ink-900); }
/* ACTIVE — ringed accent dot, emphasised. */
.kl-wizard-track__step.kl-wizard-step--active .kl-wizard-track__dot {
  border-color:var(--k-accent); color:var(--k-text); font-weight:800;
  box-shadow:0 0 0 3px color-mix(in srgb, var(--k-accent) 25%, transparent);
}
/* UPCOMING — dimmed, plainly not-yet-reached. */
.kl-wizard-track__step:not(.kl-is-done):not(.kl-wizard-step--active) .kl-wizard-track__dot { opacity:.7; }
.kl-wizard-track__step.kl-is-deferred .kl-wizard-track__dot { border-style:dashed; opacity:.55; }
.kl-wizard-track__caption {
  margin:var(--k-space-2) 0 0; text-align:center;
  font-size:13px; font-weight:700; color:var(--k-text);
}
/* The nav-link region is the rail's SINGLE scroll container: flex:1 + min-block-size:0 lets it shrink inside the
   fixed-height column so a long signed-in persona (Supervisor has 15+ items) scrolls its items into reach instead of
   clipping below the fold. A VISIBLE on-rail-navy scrollbar is the affordance — without it the list silently cut off
   at "Vehicles" and Expenses/Medical/Certifications/etc. looked unreachable (the items rendered, but the overlay
   scrollbar gave no cue there was more). overscroll-behavior:contain keeps a rail flick from scrolling the page. */
.kl-shellnav-list {
  display:flex; flex-direction:column; gap:2px;
  flex:1 1 auto; min-block-size:0; overflow-y:auto; overscroll-behavior:contain;
  padding-block-end:var(--k-space-4);
  scrollbar-width:thin; scrollbar-color:rgb(255 255 255 / .28) transparent;
}
/* On a desktop browser, styling the webkit pseudo-elements promotes the list to a CLASSIC always-present scrollbar
   (it stops being a hover-only overlay), so a long persona always shows the groove + thumb = a persistent "there is
   more below" affordance. A faint track groove keeps the cue visible even when the thumb is at rest. */
.kl-shellnav-list::-webkit-scrollbar { inline-size:10px; }
.kl-shellnav-list::-webkit-scrollbar-track { background:rgb(255 255 255 / .06); border-radius:var(--k-radius-full); }
.kl-shellnav-list::-webkit-scrollbar-thumb {
  background:rgb(255 255 255 / .26); border-radius:var(--k-radius-full);
  border:2px solid transparent; background-clip:padding-box;
}
.kl-shellnav-list:hover::-webkit-scrollbar-thumb,
.kl-shellnav-list::-webkit-scrollbar-thumb:hover { background:rgb(255 255 255 / .44); background-clip:padding-box; }
.kl-shellnav-group {
  margin:var(--k-space-3) var(--k-space-3) var(--k-space-1);
  font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.06em;
  /* >= AA (4.5:1) for this 11px label on the --k-ink-800 rail; .45 measured ~4.2:1 and axe-failed color-contrast. */
  color:rgb(255 255 255 / .62);
}
.kl-shellnav-link {
  display:flex; align-items:center; gap:var(--k-space-2);
  padding:var(--k-space-2) var(--k-space-3); min-block-size:40px;
  color:rgb(255 255 255 / .82); text-decoration:none;
  border-radius:var(--k-radius-md); font-size:14px;
  transition:background var(--k-dur-1) var(--k-ease), color var(--k-dur-1) var(--k-ease);
}
.kl-shellnav-link:hover { background:rgb(255 255 255 / .08); color:#fff; }
.kl-shellnav-link[aria-current="page"] { background:var(--k-teal-700); color:#fff; }
/* Reporter reply round-trip — the "My feedback" attention badge (NEEDS_INFO + awaiting-confirmation count). */
.kl-shellnav-linklabel { flex:1 1 auto; }
.kl-shellnav-badge {
  flex:0 0 auto; min-inline-size:18px; block-size:18px; padding:0 6px;
  display:inline-flex; align-items:center; justify-content:center;
  border-radius:999px; background:#d64545; color:#fff;
  font-size:11px; font-weight:700; line-height:1;
}
.kl-shellnav-operatornote {
  margin:var(--k-space-3); padding:var(--k-space-3); border-radius:var(--k-radius-lg);
  background:rgb(255 255 255 / .06); color:rgb(255 255 255 / .82); font-size:13px;
}
.kl-shellnav-operatornote strong { display:block; margin-block-end:var(--k-space-1); color:#fff; }

/* Top bar (secondary chrome row). */
.kl-shellnav-topbar {
  display:flex; align-items:center; gap:var(--k-space-1);
  background:var(--k-header-bg); color:#fff;
  padding:var(--k-space-1) var(--k-space-3); min-block-size:48px;
  border-block-end:1px solid rgb(255 255 255 / .08);
}
.kl-shellnav-spacer { flex:1 1 auto; }
.kl-shellnav-iconbtn {
  display:inline-flex; align-items:center; justify-content:center;
  /* UI-GUAR-036 (R2-25): a ≥44² touch target on every phone width. These sit in a flex topbar, so a fixed
     inline/block-size was SHRUNK below 44 by flex when several buttons + the persona ellipsis competed for room
     (the live sweep measured 20–35×40). min-* + flex-shrink:0 makes the WCAG 2.5.5/2.5.8 floor non-negotiable. */
  inline-size:var(--k-touch); block-size:var(--k-touch);
  min-inline-size:var(--k-touch); min-block-size:var(--k-touch); flex:0 0 auto;
  border-radius:var(--k-radius-full);
  background:transparent; color:rgb(255 255 255 / .82);
  border:0; font:inherit; font-size:18px; cursor:pointer; text-decoration:none;
  transition:background var(--k-dur-1) var(--k-ease);
}
.kl-shellnav-iconbtn:hover { background:rgb(255 255 255 / .12); color:#fff; }
.kl-shellnav-iconbtn[aria-pressed="true"] { background:var(--k-teal-500); color:var(--k-ink-900); }
/* UI-GUAR-036 (R2-25): the account avatar is a ≥44 CSS-px touch target (was an explicit 36×36 that overrode the
   .kl-shellnav-iconbtn floor; the axe target-size pass flagged it on every route). */
.kl-shellnav-account { background:var(--k-ink-700); color:var(--k-teal-400); inline-size:var(--k-touch); block-size:var(--k-touch); font-size:13px; font-weight:700; }

/* The top bar's account cluster — the shell's SINGLE auth region (the redundant legacy header was retired). MainLayout
   fills this via ShellNav's AccountSlot: the signed-in persona + a working sign-out, or a dev-sign-in link. White ink
   on the dark bar (AA on the navy header canvas in every mode). */
.kl-shellnav-account-cluster { display:flex; align-items:center; gap:var(--k-space-2); min-inline-size:0; }
.kl-shellnav-account-cluster .kl-shellnav-signout-form { margin:0; }
.kl-shellnav-persona {
  font-size:13px; font-weight:600; color:#fff; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; max-inline-size:16ch;
}
.kl-shellnav-signout {
  font:inherit; font-size:13px; cursor:pointer; white-space:nowrap;
  color:#fff; background:rgb(255 255 255 / .12);
  border:1px solid rgb(255 255 255 / .28); border-radius:var(--k-radius-md);
  /* UI-GUAR-036 (R2-25): ≥44 CSS-px touch height (was 32; the live sweep flagged 77×32). */
  padding-block:var(--k-space-1); padding-inline:var(--k-space-3);
  min-block-size:var(--k-touch); display:inline-flex; align-items:center;
  transition:background var(--k-dur-1) var(--k-ease);
}
.kl-shellnav-signout:hover { background:rgb(255 255 255 / .22); }
.kl-shellnav-signin { color:#fff; font-size:13px; text-decoration:underline; white-space:nowrap; }

/* AUTH go-live — the top-bar account MENU (k-account). A native <details> disclosure (opens with no circuit on the
   static-SSR shell). The <summary> is the avatar trigger; the panel drops below, right-aligned to the bar edge. */
.kl-shellnav-accountmenu { position:relative; display:inline-flex; }
.kl-shellnav-accountmenu > summary { list-style:none; cursor:pointer; }
.kl-shellnav-accountmenu > summary::-webkit-details-marker { display:none; }
.kl-shellnav-accountmenu-panel {
  position:absolute; inset-block-start:calc(100% + var(--k-space-1)); inset-inline-end:0; z-index:50;
  min-inline-size:12rem; display:flex; flex-direction:column; padding-block:var(--k-space-1);
  background:var(--k-surface, #fff); color:var(--k-ink-900, #10233d);
  border:1px solid var(--k-border, rgb(0 0 0 / .12)); border-radius:var(--k-radius-md);
  box-shadow:0 8px 24px rgb(0 0 0 / .18);
}
/* box-sizing is LOAD-BEARING here (feedback 72cac066): the shell chrome renders outside <main>, so it inherits no
   box-sizing reset (the sheet's only ones are `.kl-shell-main > *` above and the form-control rule). Content-box +
   inline-size:100% + 12px padding-inline made each row 216px inside the 192px right-anchored panel, and the 24px
   surplus escaped past the viewport edge — measured at 1280x800, opening the menu took document scrollWidth
   1280 -> 1291 and gave the whole page a horizontal scrollbar. */
.kl-shellnav-accountmenu-item {
  display:flex; align-items:center; gap:var(--k-space-2); box-sizing:border-box;
  font:inherit; font-size:14px; text-align:start; text-decoration:none; white-space:nowrap; cursor:pointer;
  color:inherit; background:transparent; border:0; inline-size:100%;
  padding-block:var(--k-space-2); padding-inline:var(--k-space-3); min-block-size:var(--k-touch);
}
.kl-shellnav-accountmenu-item:hover,
.kl-shellnav-accountmenu-item:focus-visible { background:rgb(0 0 0 / .06); }
.kl-shellnav-accountmenu .kl-shellnav-signout-form { margin:0; }

/* ---- ACCOUNT HUB (owner-directed 2026-07-23) --------------------------------------------------------------
   The topbar account dropdown is the single account-level surface on both heads: identity header (face, full
   name, agency), the copyable user id, the head-supplied change-photo control, then every profile destination.
   It is also the ONLY account surface at phone widths, so it is width-capped and scrolls INSIDE itself rather
   than growing past the viewport (the same box-sizing lesson as the menu items above).
   KEEP IN SYNC with the other copy of this file (web serves Kanine.Web/wwwroot, MAUI serves _content/). */
.kl-account-hub { min-inline-size:17rem; max-inline-size:min(21rem, calc(100vw - var(--k-space-4)));
  max-block-size:min(34rem, calc(100vh - 5rem)); overflow-y:auto; overscroll-behavior:contain; }
.kl-account-hub-identity {
  display:flex; align-items:center; gap:var(--k-space-3); box-sizing:border-box;
  inline-size:100%; padding:var(--k-space-3);
}
/* The hub's own big face. Shares the circle treatment with the topbar avatar; an <img> fills it edge to edge. */
.kl-account-hub-face {
  flex:0 0 auto; inline-size:44px; block-size:44px; border-radius:50%; overflow:hidden;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--k-teal-500); color:var(--k-ink-900); font-weight:800; font-size:15px;
}
.kl-account-hub-face-img { inline-size:100%; block-size:100%; object-fit:cover; display:block; }
.kl-account-hub-who { display:flex; flex-direction:column; min-inline-size:0; }
/* Names and agencies are arbitrary user data: they wrap-or-ellipsis instead of widening the panel. */
.kl-account-hub-name { font-size:14px; font-weight:700; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.kl-account-hub-agency { font-size:12px; opacity:.75; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.kl-account-hub-uid {
  display:flex; align-items:center; gap:var(--k-space-2); box-sizing:border-box;
  inline-size:100%; padding:0 var(--k-space-3) var(--k-space-2);
}
.kl-account-hub-uid-label { font-size:11px; text-transform:uppercase; letter-spacing:.04em; opacity:.6; flex:0 0 auto; }
/* The id is a machine token — monospaced, and it TRUNCATES rather than forcing the panel wider. */
.kl-account-hub-uid-value {
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:11px;
  flex:1 1 auto; min-inline-size:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.kl-account-hub-copy {
  flex:0 0 auto; appearance:none; border:1px solid var(--k-border, rgb(0 0 0 / .12)); background:transparent;
  color:inherit; cursor:pointer; font:inherit; font-size:13px; line-height:1;
  border-radius:var(--k-radius-md); padding:6px 8px; min-inline-size:32px; min-block-size:32px;
}
.kl-account-hub-copy:hover, .kl-account-hub-copy:focus-visible { background:rgb(0 0 0 / .06); }
/* Copy feedback is a real state change the JS stamps — never a silent no-op button. */
.kl-account-hub-copy.is-copied { background:var(--k-teal-400); border-color:transparent; color:var(--k-ink-900); }
.kl-account-hub-photo { box-sizing:border-box; inline-size:100%; padding:0 var(--k-space-3) var(--k-space-2); }
.kl-account-hub-photoform { display:flex; align-items:center; gap:var(--k-space-2); margin:0; }
.kl-account-hub-photobtn {
  display:inline-flex; align-items:center; gap:var(--k-space-2); cursor:pointer;
  font-size:13px; font-weight:600; padding:8px 10px; min-block-size:var(--k-touch); box-sizing:border-box;
  border:1px solid var(--k-border, rgb(0 0 0 / .12)); border-radius:var(--k-radius-md);
}
.kl-account-hub-photobtn:hover { background:rgb(0 0 0 / .06); }
/* The submit button is the NO-JS fallback for the auto-submitting file input: hidden from pointer users (who never
   need it) but kept in the accessibility tree and revealed on keyboard focus, so it is never an unreachable control. */
.kl-account-hub-photofallback {
  appearance:none; background:transparent; border:0; font:inherit; font-size:12px; cursor:pointer;
  color:inherit; opacity:0; inline-size:1px; block-size:1px; padding:0; overflow:hidden;
}
.kl-account-hub-photofallback:focus-visible {
  opacity:1; inline-size:auto; block-size:auto; padding:6px 8px;
  border:1px solid var(--k-border, rgb(0 0 0 / .12)); border-radius:var(--k-radius-md);
}
.kl-account-hub-sep { block-size:1px; background:var(--k-border, rgb(0 0 0 / .12)); margin-block:var(--k-space-1); }
/* The my-feedback badge sits at the row's end rather than beside the label. */
.kl-account-hub .kl-shellnav-accountmenu-item .kl-shellnav-badge { margin-inline-start:auto; }
/* The topbar circle itself, once a real photo is set. */
.kl-shellnav-avatar-img {
  inline-size:100%; block-size:100%; border-radius:50%; object-fit:cover; display:block;
}


.kl-shellnav-topbar a:focus-visible,
.kl-shellnav-topbar summary:focus-visible,
.kl-shellnav-topbar button:focus-visible { outline:3px solid var(--k-teal-400); outline-offset:2px; border-radius:2px; }

/* Mobile bottom nav (thumb reach). Hidden on wide; the rail is the desktop nav.
   ONE height token shared by the bar and the content clearance so the two can never drift apart (the page
   must scroll clear of the bar). */
:root { --k-bottomnav-h: 56px; }

/* ---- P4: the STICKY-OFFSET contract for in-page sticky elements (2026-07-24) ------------------------------
   The shell top bar is sticky at z-index:70 wherever it is pinned. An in-page element that also sticks — the
   record-build wizard tracker, a settings section rail — must therefore start BELOW it, or once stuck it slides
   under the bar and its controls become unclickable while still being visible and enabled. That is a genuine
   unreachability defect, not a paint-order nit: the C4 ladder settles WHO PAINTS ON TOP, but nothing declared the
   OFFSET, so every new sticky element rediscovered the same bug (the wizard tracker's steps 1-2 were unclickable
   once stuck; /tenant-settings had the identical shape and was patched page-side).

   --k-topbar-h  the bar's own box: min-block-size 48 + 2x--k-space-1 padding + 1px border = 57px. A documented
                 constant, exactly like --k-bottomnav-h above (the bar can only grow if its content wraps, which
                 the >=44px icon row prevents).
   --k-sticky-top  what an in-page sticky element should use for `top`. ZERO by default, because the bar is NOT
                 sticky everywhere: at desktop widths with the rail pinned it scrolls away normally, and an offset
                 there would leave a 57px gap above the stuck element. It resolves to the bar height only where the
                 bar is actually pinned — the collapsed-rail state below, and the <=1280 band (declared in the web
                 head's app.css, right beside the rule that makes the bar sticky, so the two cannot drift). */
:root { --k-topbar-h: 57px; --k-sticky-top: 0px; }
html[data-shellrail="collapsed"] { --k-sticky-top: var(--k-topbar-h); }
.kl-shellnav-bottom { display:none; }
.kl-shellnav-bottomitem {
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
  flex:1 1 0; min-block-size:var(--k-touch);
  background:transparent; border:0; font:inherit; font-size:11px;
  color:rgb(255 255 255 / .65); text-decoration:none; cursor:pointer;
}
.kl-shellnav-bottomitem--primary { color:var(--k-teal-400); font-weight:700; }
/* SELECTED tab (owner-directed 2026-07-23): the tab for the page you are already on reads as current — full-strength
   teal label + glyph and a teal cap on the bar's top edge, so the state is carried by MORE than colour alone. The
   ::before is positioned against the item (relative) and hugs its top so the cap lines up with the bar border. */
.kl-shellnav-bottomitem { position:relative; transition:color 120ms ease; }
.kl-shellnav-bottomitem.is-active { color:var(--k-teal-400); font-weight:700; }
.kl-shellnav-bottomitem.is-active::before {
  content:""; position:absolute; inset-block-start:0; inset-inline:22%;
  block-size:3px; border-end-start-radius:2px; border-end-end-radius:2px;
  background:var(--k-teal-400);
}

/* ---- CAPTURE FAB + RADIAL MENU (owner-directed 2026-07-22) ------------------------------------------------
   The centre of the bar is a raised CIRCULAR button, larger than the flanking tabs, that rises roughly half out
   of the bar. The "half-moon cut" is the ring of bar-coloured box-shadow around the disc: it paints OVER the
   bar's top border so the disc reads as punched through the bar rather than floating on top of it — one shadow,
   no SVG mask, no second stacking context. .kl-shellnav-bottomspacer is the flex column it occupies so the two
   flanking tabs never slide underneath.
   Both the FAB and the radial are positioned against .kl-shellnav-bottom (position:fixed at <=1024px), so the
   whole cluster travels with the docked bar and inherits its safe-area padding. */
.kl-shellnav-bottomspacer { flex:1 1 0; min-inline-size:72px; pointer-events:none; }
.kl-shellnav-fab {
  position:absolute; inset-inline-start:50%; inset-block-start:8px;
  transform:translate(-50%, -50%);
  inline-size:64px; block-size:64px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  border:0; cursor:pointer; font:inherit; z-index:2;
  color:var(--k-ink-900);
  background:var(--k-teal-500);
  /* The bar-coloured ring IS the notch; the outer drop shadow lifts the disc off the page. */
  box-shadow:0 0 0 5px var(--k-ink-800), 0 6px 16px rgb(0 0 0 / .45);
  transition:transform 140ms ease, background-color 140ms ease;
}
.kl-shellnav-fab:hover { background:var(--k-teal-400); }
.kl-shellnav-fab:focus-visible { outline:3px solid #fff; outline-offset:3px; }
.kl-shellnav-fab-glyph { font-size:30px; line-height:1; transition:transform 160ms ease; }
/* Open state: the + becomes an x, so the FAB is its own close affordance (no second control to find). */
.kl-shellnav-fab[aria-expanded="true"] .kl-shellnav-fab-glyph { transform:rotate(45deg); }

/* The radial itself is a ZERO-SIZE origin pinned to the FAB's centre; each spoke is translated out to its own
   (--k-spoke-x, --k-spoke-y) point on the arc, supplied inline by the markup. Closed = not rendered for the
   pointer (visibility:hidden + zero scale), so it can never intercept taps on the bar beneath it. */
.kl-capture-radial {
  position:absolute; inset-inline-start:50%; inset-block-start:8px;
  inline-size:0; block-size:0; z-index:1;
  visibility:hidden; pointer-events:none;
}
.kl-capture-radial.is-open { visibility:visible; pointer-events:auto; }
.kl-capture-spoke {
  position:absolute; inset-inline-start:0; inset-block-start:0;
  display:flex; flex-direction:column; align-items:center; gap:4px;
  /* The 48px DOT is what sits ON the arc point, so the column is shifted -50% of its OWN width (centred on the
     point) and lifted by the dot's radius (the point lands on the dot's centre, not the column's top). The label
     stays on ONE line so the column's height is predictable — that height is what keeps the low outer spokes and
     their labels clear of the bar. */
  inline-size:max-content; max-inline-size:112px; white-space:nowrap;
  text-decoration:none; color:#fff;
  transform:translate(-50%, -22px) scale(.4); transform-origin:top center; opacity:0;
  transition:transform 180ms cubic-bezier(.2,.8,.3,1.2), opacity 140ms ease;
}
.kl-capture-radial.is-open .kl-capture-spoke {
  transform:translate(calc(var(--k-spoke-x) - 50%), calc(var(--k-spoke-y) - 22px)) scale(1);
  opacity:1;
}
.kl-capture-spoke-dot {
  display:flex; align-items:center; justify-content:center;
  inline-size:44px; block-size:44px; border-radius:50%;
  background:var(--k-ink-700); color:#fff; font-size:21px;
  border:1px solid rgb(255 255 255 / .16); box-shadow:0 4px 12px rgb(0 0 0 / .4);
}
.kl-capture-spoke--primary .kl-capture-spoke-dot {
  background:var(--k-teal-400); color:var(--k-ink-900); border-color:transparent;
}
.kl-capture-spoke-label {
  font-size:10px; font-weight:600; line-height:1.3; text-align:center; letter-spacing:.01em;
  padding:1px 6px; border-radius:999px;
  background:rgb(10 26 43 / .92); color:#fff;
}
.kl-capture-spoke:focus-visible .kl-capture-spoke-dot { outline:3px solid var(--k-teal-400); outline-offset:2px; }
/* The capture scrim dims the page so the spokes read against arbitrary content underneath. */
.kl-menu-scrim--capture { background:rgb(0 0 0 / .45); }
/* The floating FEEDBACK bubble lives at z-index 10000 (it must clear Blazor's reconnect overlay), so nothing in the
   shell can paint over it — it would sit ON TOP of the right-hand spokes. It stands down while the radial is open.
   HEAD-AGNOSTIC ON PURPOSE: keyed off the radial's own is-open state via :has(), NOT off a class the JS stamps.
   The two heads open this menu by different paths — the web shell is static SSR so kanine-shell-nav.js carries it,
   while on the Blazor-Hybrid (MAUI) head the shell renders POST-boot with no enhancedload, so the JS never wires and
   the C# ToggleCapture is what fires. A JS-stamped body class was therefore correct on web and dead on device (seen
   red on a Pixel 7a: the bubble sat on top of the Video spoke). The state itself is the trigger, so both paths work.
   :has() is already a dependency here — kanine-feedback.css uses it to hide this same bubble on the build surface. */
body:has(.kl-capture-radial.is-open) .kl-feedback-fab { opacity:0; pointer-events:none; }
/* Honour reduced-motion: the spokes still appear/disappear, they just do not fly or scale. */
@media (prefers-reduced-motion: reduce) {
  .kl-capture-spoke,
  .kl-capture-radial.is-open .kl-capture-spoke {
    transition:opacity 100ms ease;
    transform:translate(calc(var(--k-spoke-x) - 50%), calc(var(--k-spoke-y) - 22px));
  }
  .kl-shellnav-fab, .kl-shellnav-fab-glyph { transition:none; }
}
/* A sheet row that must be a <button> (the sign-out POST) reads exactly like the <a> rows. */
.kl-shellnav-link--btn {
  appearance:none; background:transparent; border:0; margin:0;
  inline-size:100%; text-align:start; cursor:pointer;
}

/* ---- SHELL BACK ARROW: hidden ONLY when pressing it would do nothing (owner ruling 2026-07-24) --------------
   "The back arrow can be hidden if there is no history and it would not go anywhere but back to where we are. But
   if I just came from record listing and I'm home now, I could press the back arrow and go back."

   BEHAVIOURAL, NEVER ROUTE-BASED. The rule is an AND of two independently-sourced signals, and the arrow hides only
   when BOTH actively assert a no-op:
     html[data-shellhistory="none"]  — kanine-shell-history.js: this TAB has no in-app history behind it
     .kl-shellnav-back[data-back-noop="route"] — ShellBackNoOp (C#): this route's TASK_ROOTS parent IS this route

   So "/" reached from /records keeps a working arrow (history says otherwise), and a cold deep link to /records/new
   keeps one too (its parent /records genuinely goes somewhere). Only the intersection — a cold arrival on a page
   whose parent is itself — is a dead control, and that is what disappears.

   FAIL VISIBLE BY CONSTRUCTION: this is the ONLY rule that hides the arrow, and it needs both attributes present.
   A missing, errored or never-ran signal (storage denied, JS blocked, an unknown route) omits one of them and the
   arrow stays on screen. A present-but-occasionally-useless arrow beats a missing one on a page that needs it.

   display:none rather than visibility/opacity so the dead control also leaves the accessibility tree and the tab
   order — a control that cannot do anything should not be reachable by keyboard or screen reader either. The
   element itself stays in the DOM, so data-testid="shell#topbar.k-back" is never removed from the markup.

   NO LAYOUT SHIFT: .kl-shellnav-spacer (flex:1 1 auto) sits between the left controls and the right cluster and
   absorbs the freed width, so the right-hand cluster stays right-aligned and the left controls precede the arrow —
   nothing moves. The stamp is applied pre-paint, so the arrow is absent from the FIRST frame rather than painted
   and then removed. */
html[data-shellhistory="none"] .kl-shellnav-back[data-back-noop="route"] { display:none; }

/* ---- SHELL RAIL SHOW/HIDE (owner ruling 2026-07-24, extended same day) --------------------------------------
   The topbar control that shows or hides the left rail, on any screen size at or above 1025px, remembered per
   device. ONE preference with THREE values — `pinned`, `collapsed`, or ABSENT (= whatever this width does by
   default) — carried by one localStorage key + one cookie flag + one <html data-shellrail> attribute. It is
   deliberately an ABSOLUTE state and not an "invert the width default" flag: an inverting flag would mean that
   collapsing at 1440 and then narrowing to 1280 brought the rail BACK, which is nonsense. Absolute means the
   answer to "is the sidebar showing?" only ever changes when the user presses the button (the <=1024 phone band
   is the one exception — see below).

     >=1281  default PINNED    · press → collapsed
     1025-1280 default HIDDEN  · press → pinned (240px rail + the content beside it, bottom nav stands down)
     <=1024  always the drawer; the toggle is HIDDEN so it can never be a no-op control (the same UI-GUAR-015
             §H.1 #4 rule that hides the hamburger where the rail already shows). A stored preference is kept,
             not cleared, so it takes effect again as soon as the window is wide enough to honour it.

   Two glyphs, one shown per EFFECTIVE state, so the icon reports the state without a label swap: a chevron
   pushing the rail away when it is showing, a hamburger offering it back when it is hidden. `.kl-shellnav-iconbtn`
   gives it the >=44px WCAG touch floor. */
.kl-shellnav-railtoggle { display:none; }
/* Default (no stored preference): >=1281 the rail shows, so the button offers "hide". */
.kl-shellnav-railtoggle .kl-railtoggle-collapsed { display:none; }
/* Default in the 1025-1280 band: the rail is hidden, so the button offers "show". */
@media (max-width: 1280px) {
  html:not([data-shellrail="pinned"]) .kl-shellnav-railtoggle .kl-railtoggle-pinned { display:none; }
  html:not([data-shellrail="pinned"]) .kl-shellnav-railtoggle .kl-railtoggle-collapsed { display:inline; }
  html:not([data-shellrail="pinned"]) .kl-shellnav-railtoggle { background:var(--k-teal-500); color:var(--k-ink-900); }
}
/* An EXPLICIT preference overrides the width default in both directions. Declared after the media block because
   these carry the same specificity — source order is what settles the tie. */
html[data-shellrail="collapsed"] .kl-shellnav-railtoggle .kl-railtoggle-pinned { display:none; }
html[data-shellrail="collapsed"] .kl-shellnav-railtoggle .kl-railtoggle-collapsed { display:inline; }
html[data-shellrail="pinned"] .kl-shellnav-railtoggle .kl-railtoggle-pinned { display:inline; }
html[data-shellrail="pinned"] .kl-shellnav-railtoggle .kl-railtoggle-collapsed { display:none; }
/* The state cue is CSS-driven off <html>, NOT off the aria-pressed the JS stamps: on the Blazor-Hybrid head the
   shell renders POST-boot, long after the applicator's DOMContentLoaded sync, so an aria-pressed-only cue would be
   stale there. aria-pressed still carries the state for assistive tech (and the shared iconbtn rule paints it). */
html[data-shellrail="collapsed"] .kl-shellnav-railtoggle { background:var(--k-teal-500); color:var(--k-ink-900); }
html[data-shellrail="pinned"] .kl-shellnav-railtoggle { background:transparent; color:rgb(255 255 255 / .82); }

/* Desktop (>=1281px): the rail is a FIXED full-height left column; the rest of the
   shell — the top bar, the legacy header and the page content — flows to the RIGHT of
   it via a matching inline-start offset on .kl-shell. THIS is the rule that was
   missing: without it the rail rendered as a 240px block stacked ABOVE the header and
   content (a left bar with blank space to its right) instead of beside them. Below
   1281px the rail collapses to a drawer (the max-width:1280px block) and the offset is
   dropped so the content reclaims the full width. The parent's box-sizing:border-box +
   max-inline-size:100% keep the 240px offset INSIDE the viewport, so the §6.2
   zero-horizontal-overflow guarantee still holds at 1281 / 1440 / 1920.

   BREAKPOINT RAISED 1025 -> 1281 (owner ruling 2026-07-24): a landscape tablet is ~1280 CSS px wide, and there a
   240px fixed rail left the content visibly cramped. The owner's call was that the tablet should HIDE the rail and
   give the content the width, so 1280-and-below now gets the drawer + bottom nav. Three sibling rules move with it
   or the band breaks: the drawer/bottom-nav block below, the <main> bottom-bar clearance in MainLayout.razor.css,
   and the feedback FAB lift in kanine-feedback.css. The CRAMPED-TOPBAR hides (search / notifications / persona /
   account cluster) deliberately did NOT move — they were measured and justified at 320px, and a 1280 landscape
   tablet has ample room for all four (see the max-width:1024px block below).

   Every rule here is gated on html:not([data-shellrail="collapsed"]) so the user's own collapse preference wins at
   desktop widths; when collapsed, the rail block below takes over at every width. */
@media (min-width: 1281px) {
  html:not([data-shellrail="collapsed"]) .kl-shellnav-rail {
    position:fixed; inset-block:0; inset-inline-start:0;
    inline-size:240px; z-index:50;
    /* overflow:hidden is inherited from the base rule — the rail never scrolls as a whole; the brand stays pinned and
       the inner .kl-shellnav-list is the SOLE scroll region, so every persona's items (incl. Expenses) are reachable. */
  }
  html:not([data-shellrail="collapsed"]) .kl-shell { padding-inline-start:240px; }
  /* UI-GUAR-015 (owner ruling §H.1 #4): the hamburger is HIDDEN where the rail already shows (the fixed desktop
     column above), so it is never a no-op toggle. When the user has COLLAPSED the rail the hamburger stays hidden
     too — the rail toggle is the one control that governs the rail at these widths, so there is never a second,
     competing nav opener. */
  .kl-shellnav-menu { display:none; }
}

/* The show/hide toggle renders from 1025 up — at 1024 and below the rail is unconditionally a drawer, so the
   control would be a no-op there. Owner UX (2026-06-29): the FIRST top-bar control sat flush against the rail's
   right edge and its round hover highlight read as "cut off" by the left nav; that first control is now the rail
   toggle (the hamburger is hidden wherever the rail shows, and Back follows the toggle), so the nudge moved with
   it and the arrow no longer carries its own. */
@media (min-width: 1025px) {
  .kl-shellnav-railtoggle { display:inline-flex; margin-inline-start:var(--k-space-2); }
}

/* ---- EXPLICITLY PINNED IN THE 1025-1280 BAND (owner ruling 2026-07-24) --------------------------------------
   The band's DEFAULT is the drawer + bottom nav (a landscape tablet reclaiming the rail's 240px). A user on a
   1280-wide LAPTOP wants the opposite, so pressing the toggle here restores the fixed rail column — the same
   layout >=1281 gets. The bottom nav and its <main> clearance stand down with it: the rail IS the navigation
   when it is pinned, so keeping a bottom bar would duplicate every destination and spend 56px of vertical space
   on it (principle B1). Scoped to >=1025 so a phone can never land in this state. */
@media (min-width: 1025px) and (max-width: 1280px) {
  html[data-shellrail="pinned"] .kl-shellnav-rail {
    position:fixed; inset-block:0; inset-inline-start:0;
    inline-size:240px; z-index:50; display:flex; box-shadow:none;
  }
  html[data-shellrail="pinned"] .kl-shell { padding-inline-start:240px; }
  /* The rail is the nav again ⇒ the hamburger would be a no-op, and the bottom bar a duplicate. */
  html[data-shellrail="pinned"] .kl-shellnav-menu { display:none; }
  html[data-shellrail="pinned"] .kl-shellnav-bottom { display:none; }
  /* The feedback FAB's bottom-bar lift (kanine-feedback.css:38-45) stands down with the bar it was clearing.
     Physical `bottom` on purpose — it is the exact property that rule sets, and this selector out-specifies it. */
  html[data-shellrail="pinned"] .kl-feedback-fab { bottom:20px; }
}

/* Narrow widths: the rail collapses to a drawer (k-menu toggles is-open); the bottom nav appears. */
@media (max-width: 1280px) {
  .kl-shellnav-rail { position:fixed; inset-block:0; inset-inline-start:0; z-index:60; display:none; box-shadow:var(--k-elev-3); }
  .kl-shellnav-rail.is-open { display:flex; }
  /* DOCKED for real (owner mobile-nav pass 2026-07-03): the old position:sticky was a NO-OP — ShellNav renders
     near the TOP of the document flow, and sticky bottom:0 never moves an element below its flow position, so the
     "bottom" bar rendered as a second row under the topbar. position:fixed docks it to the viewport bottom (§7
     thumb reach); z-index 55 sits under the drawer/sheet (60) and the modal overlay (80). env(safe-area-inset-bottom)
     keeps the row of targets above an iOS home indicator. */
  .kl-shellnav-bottom {
    position:fixed; inset-inline:0; inset-block-end:0; z-index:55;
    display:flex; align-items:stretch;
    min-block-size:var(--k-bottomnav-h);
    padding-block-end:env(safe-area-inset-bottom, 0px);
    background:var(--k-ink-800); border-block-start:1px solid rgb(255 255 255 / .08);
  }
  /* The fixed bar is out of flow, so the routed content must clear it: the bar-height clearance on the shell's
     <main> lives in MainLayout.razor.css (its scoped padding-block rule OWNS main's padding and would out-order an
     equal-specificity override here) — see the max-width:1280px block there. */
}

/* CRAMPED TOPBAR — a SEPARATE, NARROWER query on purpose (owner ruling 2026-07-24 kept these at <=1024 when the
   drawer band moved up to <=1280). Everything below is a PHONE-width space concession measured at 320px; a 1280
   landscape tablet has ample topbar room, so folding these into the drawer query would silently strip four
   affordances (search, notifications, the persona label and the whole account cluster) off every tablet. */
@media (max-width: 1024px) {
  /* UI-GUAR-036 (R2-25): the topbar holds hamburger + back + the theme toggles + account at phone widths. With every
     icon button now a non-shrinkable ≥44² target (the WCAG floor), six of them + the persona + sign-out overflowed
     320px (the live sweep measured documentElement scrollWidth 413 > 320). The two NAV icon buttons (search,
     notifications) are fully reachable on mobile via the rail drawer (hamburger) AND the bottom-nav, so they are
     hidden from the cramped topbar here — no function is lost. The theme/outdoor toggles are KEPT (they live ONLY on
     the topbar, so they must stay reachable on mobile), and the long persona label collapses so the row fits with
     full-size touch targets and zero horizontal overflow. */
  .kl-shellnav-iconbtn[href="/records/search"],
  .kl-shellnav-iconbtn[href="/notifications"] {
    display:none;
  }
  .kl-shellnav-persona { display:none; }
  /* The sign-out + persona cluster CROWDS the account avatar at phone widths — the topbar ran out of room and the
     cluster overlapped the 48² avatar (axe: only ~7.5px of unobscured clickable space). Hide the in-topbar cluster
     on mobile; account / sign-out / sign-in all live in the topbar account HUB dropdown at these widths (the
     avatar the user taps), so the avatar gets its full, unobscured ≥44 touch target back. */
  .kl-shellnav-account-cluster { display:none; }
}

/* ---- COLLAPSED RAIL, ANY WIDTH (owner ruling 2026-07-24) ---------------------------------------------------
   The user's per-device preference, persisted by kanine-theme.js and stamped on <html> BEFORE first paint (and
   re-emitted by the server during prerender from the same kanine_theme cookie), so there is no flash of the wrong
   shell and no dependence on a circuit. HEAD-AGNOSTIC: the trigger is an ATTRIBUTE ON <html>, never a class a
   JS wiring pass stamps on the shell — the web shell is static SSR while the Blazor-Hybrid head renders it
   POST-boot with no enhancedload, so a shell-stamped class is correct on web and dead on device (kanine-design-
   system.css:1494 records the Pixel 7a proof of exactly that failure).

   Specificity note: `html[data-shellrail="collapsed"] .kl-shellnav-rail` is (0,2,1), so it beats the plain
   `.kl-shellnav-rail` rules in the media blocks above regardless of source order; the .is-open companion is
   (0,3,1) and still wins over it. The rail is given the full DRAWER treatment rather than a bare display:none so
   a drawer opened at <=1280 stays correct if the window is then widened past the desktop breakpoint. */
html[data-shellrail="collapsed"] .kl-shellnav-rail {
  position:fixed; inset-block:0; inset-inline-start:0; z-index:60; display:none; box-shadow:var(--k-elev-3);
}
html[data-shellrail="collapsed"] .kl-shellnav-rail.is-open { display:flex; inset-block-start:56px; }
/* Keep the topbar (and with it the rail toggle) ABOVE an open drawer, so the control that governs the rail is
   never covered by the rail. The web head carries the same pairing for the <=1280 band in app.css; declaring it
   here as well is what makes the collapsed state correct on the native head too. Topbar box =
   min-block-size:48px + 2x--k-space-1 padding + 1px border ≈ 56px, matching the inset above. */
html[data-shellrail="collapsed"] .kl-shellnav-topbar { position:sticky; inset-block-start:0; z-index:70; }

/* =============================================================================
   8. R11 CROSS-CUTTING PRIMITIVES (opt-in classes; additive — a page must carry
   the class, so the ~35 already-wired-but-unpolished screens are NOT disturbed).
   These are the highest-leverage punch-list fixes (R11-AESTHETIC-PUNCHLIST.md
   TOP §2–§4): a real data-table treatment, a segmented/chip selected-state, and
   a content-measure wrapper that kills the "grey void" on sparse pages. Built
   here ONCE so every page-major sign-off EXTENDS them instead of reinventing.
   No gradients, no body-bg change → the R11 token/canvas gate holds.
   ========================================================================== */

/* ---- 8a. Content measure (kill the empty-canvas void; punch-list TOP-4) -----
   A page wraps its content in <div class="kl-page"> to get a centered, readable
   measure instead of stretching edge-to-edge as a sparse grey field. -wide opts
   a single page out to the full content max (dense tables/dashboards). */
.kl-shell-main .kl-page {
  inline-size: 100%;
  max-inline-size: 64rem;          /* ~1024px readable measure for forms/lists */
  margin-inline: auto;
  display: flex; flex-direction: column; gap: var(--k-space-4);
}
/* Phone-width rhythm (owner ruling 2026-07-24, D11): tighten the inter-card gap from 16px to 12px on
   narrow viewports — vertical space is scarce on a phone (principle B1). Shared here, for ALL screens, so
   the tightening cannot drift the way it did when it lived as a per-page patch on /records. The MAUI field
   head is always in this range and inherits it. */
@media (max-width: 640px) {
  .kl-shell-main .kl-page { gap: var(--k-space-3); }
}
.kl-shell-main .kl-page--wide { max-inline-size: var(--k-content-max); } /* 1120px for dense screens */
.kl-shell-main .kl-page--narrow { max-inline-size: 40rem; }             /* centered single-card (login, one-draft) */
/* Operator mission-control boards (feedback kanban, leads pipeline) are full-bleed dashboards, NOT reading
   surfaces: opt out of the measure cap so their columns fill the whole shell width instead of scrolling inside a
   1120px box on a wide monitor. Declared AFTER --wide so it wins when both classes are present. */
.kl-shell-main .kl-page--board { max-inline-size: none; }
/* A vertically-centered hero surface for genuinely sparse pages (login persona card): presence, not abandonment. */
.kl-shell-main .kl-hero {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--k-space-4); min-block-size: 60vh; text-align: center;
}

/* ---- 8b. Data table (punch-list TOP-3 — the strongest "unstyled" evidence) --
   <div class="kl-tablewrap"><table class="kl-data-table"> … </table></div>
   Elevated card wrapper (rounded, clips the header, scrolls a wide table INSIDE
   itself so a narrow viewport never gets a page-level horizontal scrollbar), a
   styled sticky header with sort carets, row dividers + hover. The §5n guard
   already re-asserts native <table> display; this only adds skin, never layout. */
.kl-shell-main .kl-tablewrap {
  background: var(--k-surface);
  border: 1px solid var(--k-border);
  border-radius: var(--k-radius-lg);
  box-shadow: var(--k-elev-1);
  overflow-x: auto;                /* wide table scrolls inside the card (auto = reachable, clip-audit clean) */
  overflow-y: hidden;
  max-inline-size: 100%;
}
.kl-shell-main .kl-data-table {
  inline-size: 100%;
  border-collapse: collapse;
  font-size: 14px;
  background: var(--k-surface);
}
.kl-shell-main .kl-data-table caption {
  text-align: start;
  padding: var(--k-space-3) var(--k-space-4) 0;
  font-size: 13px; color: var(--k-text-muted);
}
.kl-shell-main .kl-data-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--k-surface-2);
  color: var(--k-text-muted);
  font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  text-align: start; white-space: nowrap;
  padding: var(--k-space-3) var(--k-space-4);
  border-block-end: 1px solid var(--k-border);
}
.kl-shell-main .kl-data-table tbody td {
  padding: var(--k-space-3) var(--k-space-4);
  border-block-end: 1px solid color-mix(in srgb, var(--k-border) 55%, transparent);
  color: var(--k-text); vertical-align: middle;
}
.kl-shell-main .kl-data-table tbody tr:last-child td { border-block-end: 0; }
.kl-shell-main .kl-data-table tbody tr { transition: background var(--k-dur-1) var(--k-ease); }
.kl-shell-main .kl-data-table tbody tr:hover { background: var(--k-surface-2); }
.kl-shell-main .kl-data-table tbody tr[role="button"], .kl-shell-main .kl-data-table tbody tr.kl-row-nav { cursor: pointer; }
/* Header sort button: looks like the header label, carries an aria-sort-driven caret (no layout shift on flip). */
.kl-shell-main .kl-data-table th .th-sort {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--k-text-muted); min-block-size: 0; min-inline-size: 0;
}
.kl-shell-main .kl-data-table th .th-sort:hover { color: var(--k-accent); }
.kl-shell-main .kl-data-table th .th-sort::after { content: "↕"; opacity: .35; font-size: 11px; }
.kl-shell-main .kl-data-table th[aria-sort="ascending"] .th-sort { color: var(--k-text); }
.kl-shell-main .kl-data-table th[aria-sort="ascending"] .th-sort::after { content: "▲"; opacity: 1; color: var(--k-accent); }
.kl-shell-main .kl-data-table th[aria-sort="descending"] .th-sort { color: var(--k-text); }
.kl-shell-main .kl-data-table th[aria-sort="descending"] .th-sort::after { content: "▼"; opacity: 1; color: var(--k-accent); }

/* ---- 8b-r. Two-line row identity (the cross-cutting cell vocabulary the pages already emit): a
   <span class="kl-row-title"> lead over a quiet <span class="kl-row-sub"> secondary line (e-mail, key, id).
   Both are plain inline spans, so with no display they weld into ONE string on ONE line — feedback 3ed53a15
   read "Smoke Probesmoke-admin@kaninesoftware.com" on the users-roles directory (measured: title right edge
   === sub left edge, same y). Block-level splits the stack; the sub-line stays muted + smaller so the name
   still leads the row. Lives HERE, not in a page's scoped sheet, because the pages that emit it are shared
   across heads via the Kanine.Presentation.Pages RCL. ---- */
.kl-shell-main .kl-row-title { display: block; font-weight: 600; color: var(--k-text); }
.kl-shell-main .kl-row-sub {
  display: block; margin-block-start: 2px;
  font-size: 12px; color: var(--k-text-muted);
}

/* ---- 8b-m. MOBILE CARD COLLAPSE for data tables (2026-07-16 mobile sweep) ----
   At phone widths a wide .kl-data-table inside .kl-tablewrap "works" only via an undiscoverable horizontal
   scroll — on the live sweep a wide registry showed only its first 3 columns and HID the rest entirely,
   and fixed-width tables (training aids) overlapped their own cells. OPT-IN class `kl-table-cards` on the
   <table> reflows each row into a stacked card at ≤640px: header row hidden, each <tr> becomes a bordered
   card, each <td> a labeled field (label from its data-label attribute). Desktop is untouched (media-scoped),
   markup unchanged except the opt-in class + data-label/kl-tcard-* annotations — same components, same teeth.
   Helpers: .kl-tcard-title (full-width lead cell), .kl-tcard-full (full-width field),
   .kl-tcard-hide (desk-only column dropped from the card), .kl-tcard-actions (top-right corner slot). */
@media (max-width: 640px) {
  .kl-shell-main .kl-tablewrap:has(> .kl-table-cards) {
    background: transparent; border: 0; border-radius: 0; box-shadow: none;
    overflow: visible;
  }
  /* display overrides carry !important to beat the §5n table-structural guard (also !important, earlier). */
  .kl-shell-main .kl-table-cards { display: block !important; inline-size: 100%; }
  .kl-shell-main .kl-table-cards thead { display: none !important; }
  .kl-shell-main .kl-table-cards tbody { display: flex !important; flex-direction: column; gap: var(--k-space-3); }
  .kl-shell-main .kl-table-cards tbody tr {
    position: relative;
    display: flex !important; flex-wrap: wrap; align-items: baseline;
    column-gap: var(--k-space-5); row-gap: var(--k-space-2);
    background: var(--k-surface); border: 1px solid var(--k-border);
    border-radius: var(--k-radius-lg); box-shadow: var(--k-elev-1);
    padding: var(--k-space-3) var(--k-space-4);
  }
  .kl-shell-main .kl-table-cards tbody tr:hover { background: var(--k-surface); }
  .kl-shell-main .kl-table-cards tbody td {
    display: block !important; padding: 0; border: 0; min-inline-size: 0;
    overflow-wrap: anywhere;
  }
  .kl-shell-main .kl-table-cards td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
    color: var(--k-text-muted);
  }
  /* ---- THE CARD'S TITLE LINE (§A10, owner ruling 2026-07-25) ------------------------------------------------
     ONE line carries the card's identity: [ title ..................... status badge ] [ ⋮ ]. A label alone on a
     row, a badge alone on a row and a kebab alone on a row are three instances of the same waste (§A9/§B1/§B2),
     and the owner reported all three on the same day. The lead cell is `.kl-tcard-title`; the row's status chip
     opts onto the line with `.kl-tcard-badge`; the overflow slot `.kl-tcard-actions` is PINNED to the corner.

     VERTICAL CENTRING is the point, not a detail. The three have different intrinsic heights — a 24px text line,
     a 24px padded pill and a 48px touch target — so any of `baseline`/`stretch`/`flex-start`, and any stray
     margin/padding/border on one of them, splits them onto three different centres (measured 25 / 32 / 35.5 on
     /records before this block existed). The line therefore declares a SINGLE height floor
     (`--k-tcard-line`) and everything on it is centred against that floor:
       · the title cell is `display:flex; align-items:center`, so its text — even a bare text node, which becomes
         an anonymous flex item — is centred rather than sitting on the cell's top edge;
       · the badge cell is centred with `align-self`;
       · the pinned slot is inset by exactly HALF the difference between the touch target and the line floor,
         which puts its centre on the line's centre and lets its A6 48px hit area bleed symmetrically into the
         card's own block padding. Nothing shrinks; only the empty space around the glyph leaves the flow. */
  .kl-shell-main .kl-table-cards .kl-tcard-title {
    order: -6;
    flex-basis: 100%;
    display: flex !important; align-items: center;
    min-block-size: var(--k-tcard-line);
    font-size: 16px; font-weight: 700;
    /* Space kept clear for the pinned corner slot. The slot is out of flow, so nothing else knows it is there —
       this margin is what stops a long value sliding under it (the 2026-07-24 overlap, closed by reserving the
       line rather than by giving the slot a whole row). It is one token-derived value, not a per-page constant:
       the slot is `--k-touch` wide at `--k-space-2` from the card edge, and the card's own inline padding is
       `--k-space-4`, so `--k-touch + --k-space-2` over-reserves by exactly one `--k-space-2` gutter. */
    margin-inline-end: var(--k-tcard-reserve);
    /* BELT AND BRACES, and it earns its keep. The margin above reserves the corner while the title is the last
       in-flow item on the line; when a badge rides the line the reserve moves to the badge — and if that badge is
       too wide to fit (the longest status labels at 320-393px) the badge wraps to a line of its own and the title
       is suddenly last again, with no reserve, running straight under the pinned slot (measured: the title cell
       reaching 40px into the slot on /records and /medical, 113px on /certifications). A max on the cell holds the
       clearance in EVERY one of those states without costing anything in the normal one. */
    max-inline-size: calc(100% - var(--k-tcard-reserve));
  }
  /* An element child ellipsizes rather than pushing the badge off the line — see the "what gives" note below. */
  .kl-shell-main .kl-table-cards .kl-tcard-title > * {
    min-inline-size: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* WITH a badge on the line the title stops being the full-width lead cell and becomes the elastic one.
     `flex-basis: 0`, NOT `auto`, and that is the whole trick: a wrapping flex container picks its LINES from base
     sizes BEFORE it shrinks anything, so a title with `basis:auto` bases at its full text width and pushes the
     badge onto a second row — making the card TALLER on exactly the long-title case that motivated the change.
     A zero basis asks for nothing and then GROWS into whatever the badge leaves.
     WHAT GIVES WHEN SPACE IS TIGHT: the TITLE ellipsizes, the badge stays whole. A truncated status
     ("Needs Appro…") is not merely unhelpful in a records product, it is misreadable; a truncated title is
     recoverable from context and from opening the row. The reserve moves to the badge, which now ends the line.
     The selector reaches the title THROUGH THE ROW rather than as `~ .kl-tcard-badge` from the title, because the
     badge is not always the later cell: /expenses renders Credit/Debit and /lookups renders its status pill BEFORE
     the title in DOM order (the table's desktop column order), and a sibling combinator silently misses those two —
     leaving the title full-width and the badge stranded on a line of its own, which is the defect verbatim. */
  .kl-shell-main .kl-table-cards tbody tr:has(> .kl-tcard-badge) > .kl-tcard-title {
    flex: 1 1 0;
    flex-basis: 0;
    min-inline-size: 4rem;
    margin-inline-end: 0;
  }
  .kl-shell-main .kl-table-cards .kl-tcard-badge {
    order: -5;
    flex: 0 0 auto;
    align-self: center;
    margin-inline-end: var(--k-tcard-reserve);
  }
  /* The eyebrow every data cell gets would read as a caption stacked above the chip — on the title line the chip
     IS the label. (The desktop table still has its column header; nothing is lost.) */
  .kl-shell-main .kl-table-cards .kl-tcard-badge[data-label]::before { content: none; }
  /* THE LINE BREAK, and why it has to be a generated box. Flexbox chooses line breaks by summing outer
     hypothetical sizes in order, so there is NO declaration you can put on the title, the badge or the slot that
     ends the line after them — anything big enough to force the break also pushes the badge itself onto the next
     line. The container's own ::after IS a flex item, so a zero-height 100%-basis box ordered between the badge
     and the body cells ends the line deterministically, at every width and for every badge label. Without it a
     short badge simply leaves room and the first body field joins the title line (measured: "Breed / German
     Shepherd" fits beside "Active" at 393px). The negative start margin pays back the extra row-gap the empty
     line would otherwise cost, so the break is free. */
  .kl-shell-main .kl-table-cards tbody tr:has(> .kl-tcard-badge)::after {
    content: "";
    order: -3;
    flex: 0 0 100%;
    block-size: 0;
    margin-block-start: calc(-1 * var(--k-space-2));
  }
  .kl-shell-main .kl-table-cards .kl-tcard-full { flex-basis: 100%; }
  /* A field the owner wants on a line of its own, at the BOTTOM of the card, wherever it sits in the table's
     column order (the case number on /records: "i just wish it was its own row at the bottom"). */
  .kl-shell-main .kl-table-cards .kl-tcard-foot { order: 9; flex-basis: 100%; }
  /* …and ABSENT, not blank, when it has no value: an empty labeled cell still paints its eyebrow, its box and a
     row-gap, which is the same wasted line by another name. This is an OPT-IN class the page sets from its own
     null check, deliberately NOT a generic "empty cell" rule — the action slots on records/dogs/vehicles/medical/
     training-aids all report empty textContent (a kebab's glyph is not text), so a generic rule would delete
     every row menu on every registry. */
  .kl-shell-main .kl-table-cards .kl-tcard-empty { display: none !important; }
  .kl-shell-main .kl-table-cards .kl-tcard-hide { display: none !important; }
  /* CARD ACTIONS — PINNED to the card's top-inline-end corner (owner ruling 2026-07-25).
     This REPLACES the full-width footer band approved on 2026-07-24. The owner's clarification is that the band
     "pertains to when showing in the list" — the desktop TABLE rendering — and not to the phone CARD, where a
     lone kebab on a row of its own cost a measured 57px per card on dogs / medical / training-aids /
     certifications / people and 49px on vehicles / records, for one control.

     THE 2026-07-24 OBJECTION IS ANSWERED, NOT IGNORED. The corner was abandoned because `position:absolute` takes
     the slot OUT OF FLOW, so no sibling knew the space was occupied and a long value ran underneath it. The fix
     is the reserve on the title line above (`--k-tcard-reserve`) plus the line floor (`--k-tcard-line`), which
     bound the slot in BOTH axes: horizontally nothing on the title line may enter the reserve, and vertically the
     slot cannot reach the next line because the floor is `--k-touch` minus the padding it bleeds into. The
     objection that a reserve "is a magic number that silently becomes wrong" is met by deriving it from the same
     tokens that size and inset the slot, and by the guard test that fails if they drift apart.

     A SLOT HOLDING MORE THAN ONE CONTROL KEEPS THE BAND — `.kl-tcard-actions--band` below. Lookups' proposed rows
     carry four actions (Approve / Merge / Fix code / Reject); those do not fit a corner at any width worth
     reserving on a 320px card, and that half of the 2026-07-24 reasoning still holds. The rule is therefore
     "ONE overflow control pins; a SET of inline actions bands", which is the same judgement §W4/B3 already makes
     about command bars.

     `display` CARRIES !important, and it has to. The `tbody td` rule above sets `display:block !important` (itself
     needed to beat the §5n table-structural guard), and importance is settled BEFORE specificity in the cascade —
     so no selector, however specific, can win against it. */
  .kl-shell-main .kl-table-cards .kl-tcard-actions {
    order: -4;
    position: absolute;
    inset-block-start: calc((var(--k-touch) - var(--k-tcard-line)) / 2);
    inset-inline-end: var(--k-space-2);
    inline-size: auto;
    display: flex !important; align-items: center; justify-content: flex-end; gap: var(--k-space-2);
    min-block-size: var(--k-touch);
    min-inline-size: var(--k-touch);
    margin: 0;
    padding: 0;
    border: 0;
  }
  /* A6 ON THE CARD. Two registries compact their row kebab to 30px for DESK density (`kl-data-table--compact` on
     vehicles, the dense-registry compaction on training aids). That is a desktop choice with a mouse behind it;
     on the phone card the same control is a thumb target, and 30px is below the WCAG 2.5.8 floor this repo holds.
     The slot already reserves `--k-touch`, so restoring the button to fill it costs no layout at all. Media-scoped
     — desktop density is untouched. */
  .kl-shell-main .kl-table-cards .kl-tcard-actions .kl-rowmenu-kebab {
    min-block-size: var(--k-touch);
    min-inline-size: var(--k-touch);
  }
  /* The multi-action opt-out: the pre-2026-07-25 footer band, for slots that hold a SET of controls. */
  .kl-shell-main .kl-table-cards .kl-tcard-actions--band {
    position: static;
    flex-basis: 100%;
    flex-wrap: wrap;
    justify-content: flex-start;
    inline-size: auto;
    margin-block-start: var(--k-space-1);
    padding-block-start: var(--k-space-2);
    border-block-start: 1px solid color-mix(in srgb, var(--k-border) 55%, transparent);
  }
  /* …BUT THE BAND IS NOT ALL-OR-NOTHING (owner ruling 2026-07-25, /approvals). A slot can legitimately hold both
     kinds of control at once, and they belong in different places:

       · PRIMARY DECISIONS keep the band. Approve / Reject on the approval queue are supervisor decisions, not
         overflow; burying either behind an extra tap would be felt on every row of a queue-clearing session.
       · The OVERFLOW control still corners, exactly as it does on a single-control slot. A kebab is the same
         affordance whatever it happens to share a cell with, and §A10's whole point is that it never owns a row.

     Expressing it on the CONTROL rather than on the cell is what makes it general: the row menu is always the same
     component, so this rule needs no per-page markup, no second opt-out class and no split <td> (which would
     change the DESKTOP table's column count for a phone-only concern). A slot with no row menu — Lookups' four
     value actions, the pending-invitation link + Revoke — matches nothing here and is untouched, which is the
     honest way for a rule to "not apply" rather than being excluded by name.

     The corner it lands in is already reserved: the title line's `--k-tcard-reserve` is unconditional, so nothing
     slides under this kebab any more than under a pinned slot's. */
  .kl-shell-main .kl-table-cards .kl-tcard-actions--band > .kl-rowmenu-wrap {
    position: absolute;
    inset-block-start: calc((var(--k-touch) - var(--k-tcard-line)) / 2);
    inset-inline-end: var(--k-space-2);
    min-block-size: var(--k-touch);
    min-inline-size: var(--k-touch);
    justify-content: flex-end;
  }
  /* The label pseudo-element that every data cell gets would read as a stray caption above the buttons. */
  .kl-shell-main .kl-table-cards .kl-tcard-actions[data-label]::before { content: none; }
}

/* ---- 8b-o. THE SAME SLOT, ON A REAL TABLE (>640px) — owner review 2026-07-31 ----------------------------------
   §8b-m pins `.kl-tcard-actions` to a CARD's corner, and that block is `max-width: 640px`. Above it the registry is
   a real <table> again and the class styled nothing at all, so a trailing actions cell was an ordinary <td>: its
   controls sat at the START of the column, which reads as "three pixels to the right of the last word of the last
   column" rather than as a slot at the row's edge. Reported on /training-aids; it was true of every registry.

   THE RULE ALREADY EXISTED — PRIVATELY, ONCE. `Approvals.razor.css` carried `.kl-queue .kl-col-action
   { text-align: end; white-space: nowrap; }` under its own class name, and its ≤640px block then had to undo it.
   That is verbatim the shape §A10 was written about: /records held the card title line privately and eight sibling
   registries could not inherit it. So this is a PROMOTION, not a new idea — same declarations, keyed on the slot
   class the whole product already marks, so every registry inherits it and Approvals' copy could be deleted.

   Keyed on `td.kl-tcard-actions` rather than on a page or a second opt-out class, and media-scoped to the exact
   complement of §8b-m so the two can never both apply: the card form positions the slot absolutely and owns its
   own alignment, and a stray `text-align` reaching it would fight the corner it has already been pinned to.

   `white-space: nowrap` is the other half and is not cosmetic: a slot holding a control PLUS a kebab wraps the
   kebab onto a second line the moment the column is narrow, which is the banded-footer look §A10 removed. */
@media (min-width: 641px) {
  .kl-shell-main table.kl-table-cards td.kl-tcard-actions {
    text-align: end;
    white-space: nowrap;
  }
}

/* ---- 8b-n. THE SAME TITLE LINE, OFF-TABLE (§A10 made universal, owner ruling 2026-07-25) ----
   The owner ruled ONE card anatomy for the whole product — title, then its badge, then the overflow, on line 1,
   with NO exemption for notification or triage surfaces. The argument he ruled against was that on a reminders /
   feedback board the badge is the scanning key and earns line 1; the argument he ruled FOR is teachability and
   guardability: one rule has no boundary to argue about and nothing to drift.

   THIS IS THE SAME RULE, NOT A SECOND COPY. It reuses the identical class vocabulary — `.kl-tcard-title`,
   `.kl-tcard-badge`, `.kl-tcard-actions` — so a reader learns three names once and the guard asks one question of
   every card in the product. What differs is only what the SURFACE supplies: §8b-m reflows a <table> whose cells
   are siblings and therefore needs a generated line break and a corner reserve; a purpose-built card already has a
   header element, so the line is just that element wearing `.kl-cardline`.

   THREE DELIBERATE DIFFERENCES from the table form, each measured rather than assumed:
     1. NOT media-scoped. A registry card only exists below 640px; a reminder card is a card at every width, so its
        anatomy is not a phone reflow and pretending otherwise would leave the inverted layout live on desktop.
     2. THE TITLE WRAPS — no ellipsis. A table card's title is a short type or call-name in a fixed-height row; an
        off-table title is a complaint line or a subject sentence (`/feedback/mine` line-clamps its snippet to two
        lines by design). Truncating those to one line would delete content the card exists to show.
     3. NO generated line break. §8b-m needs one because a table's body cells are the title's SIBLINGS and would
        otherwise join its line. A card's header element already bounds the line — and here the next item on it is
        usually something that SHOULD share the row (a relative timestamp, a category label), so forcing a break
        would spend the row §A10 just reclaimed. */
.kl-shell-main .kl-cardline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--k-space-2) var(--k-space-3);
}

/* The line only takes the touch-target floor when it actually carries a control — a badge and a title do not need
   48px of room, and forcing it on every card would hand back the height this ruling reclaimed. */
.kl-shell-main .kl-cardline:has(> .kl-tcard-actions) {
  min-block-size: var(--k-tcard-line);
}

.kl-shell-main .kl-cardline > .kl-tcard-title {
  order: -6;
  flex: 1 1 0;
  flex-basis: 0;
  /* `min-inline-size: 0` is not cosmetic: a flex item's automatic minimum is its CONTENT size, so without this a
     long subject refuses to shrink, pushes the badge off the line, and re-creates the exact defect. */
  min-inline-size: 0;
  margin: 0;
}

.kl-shell-main .kl-cardline > .kl-tcard-badge {
  order: -5;
  flex: 0 0 auto;
  align-self: center;
}

.kl-shell-main .kl-cardline > .kl-tcard-actions {
  order: -4;
  flex: 0 0 auto;
  /* Trailing edge without a magic reserve: the title grows, so the overflow is already last on the line. In flow,
     not pinned — an off-table card has no `position: relative` row to pin against and needs none, and staying in
     flow means no sibling can ever run underneath it. */
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: var(--k-space-2);
  min-block-size: var(--k-touch);
  min-inline-size: var(--k-touch);
  justify-content: flex-end;
}

/* ---- 8c. Segmented control (exclusive scope; punch-list registry/scope) -----
   <div class="kl-segmented" role="group"> <button aria-pressed> … </button> …
   One pill-group; the pressed segment is teal-filled (navy ink on teal, on-brand). */
.kl-shell-main .kl-segmented {
  display: inline-flex; flex-wrap: wrap; align-items: stretch;
  border: 1px solid var(--k-border); border-radius: var(--k-radius-md);
  overflow: hidden; background: var(--k-surface);
}
.kl-shell-main .kl-segmented > button {
  appearance: none; background: transparent; color: var(--k-text);
  border: 0; border-inline-end: 1px solid var(--k-border);
  min-block-size: var(--k-touch); padding: 0 var(--k-space-4);
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: background var(--k-dur-1) var(--k-ease), color var(--k-dur-1) var(--k-ease);
}
.kl-shell-main .kl-segmented > button:last-child { border-inline-end: 0; }
.kl-shell-main .kl-segmented > button:hover { background: var(--k-surface-2); }
.kl-shell-main .kl-segmented > button[aria-pressed="true"] { background: var(--k-teal-500); color: var(--k-ink-900); }

/* ---- 8d. Filter chip (selected/active state; punch-list drafts-4 / registry) -
   A toggle chip with a real selected state — <button class="kl-filterchip"
   aria-pressed="true|false">. Deliberate size (>UA default), teal-fill when on. */
.kl-shell-main .kl-filterchip {
  display: inline-flex; align-items: center; gap: 6px;
  /* UI-GUAR-036 (R2-25): a ≥44 CSS-px touch target (was 40; the live sweep flagged filter chips at NN×40). */
  min-block-size: var(--k-touch); padding: 0 var(--k-space-4);
  border: 1px solid var(--k-border); border-radius: var(--k-radius-full);
  background: var(--k-surface); color: var(--k-text);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap;
  transition: background var(--k-dur-1) var(--k-ease), border-color var(--k-dur-1) var(--k-ease), color var(--k-dur-1) var(--k-ease);
}
.kl-shell-main .kl-filterchip:hover { background: var(--k-surface-2); }
.kl-shell-main .kl-filterchip[aria-pressed="true"] {
  background: var(--k-teal-500); color: var(--k-ink-900); border-color: transparent;
}

/* ---- 8e. Section header row (left-aligned title + count/actions; registry-7) - */
.kl-shell-main .kl-section-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--k-space-2) var(--k-space-3);
  margin-block: var(--k-space-2);
}
.kl-shell-main .kl-section-head h1, .kl-shell-main .kl-section-head h2,
.kl-shell-main .kl-section-head .kl-section-title { margin: 0; }
.kl-shell-main .kl-count {
  font-size: 13px; color: var(--k-text-muted); font-variant-numeric: tabular-nums;
}
/* Severity-tinted counts (Home rework/approval/unread) — the markup shipped kl-count--critical/--caution/--info
   with no rules behind them (TEST-EFFICACY A1 dead-variant finding); counts carry severity per owner doctrine. */
.kl-shell-main .kl-count--critical { color:var(--k-critical); font-weight:600; }
.kl-shell-main .kl-count--caution { color:var(--k-caution); font-weight:600; }
.kl-shell-main .kl-count--info { color:var(--k-info); font-weight:600; }

/* Numeric table cells (Expenses/Medical amount columns) — the markup shipped kl-cell--num with no rule
   (TEST-EFFICACY A1 dead-variant finding); dense right-aligned tabular numerals per the owner's ledger taste. */
.kl-shell-main th.kl-cell--num, .kl-shell-main td.kl-cell--num { text-align:end; font-variant-numeric:tabular-nums; }
/* Primary/secondary actions pushed to the trailing edge of a section header (e.g. registry "New record"). */
.kl-shell-main .kl-head-actions { display: flex; flex-wrap: wrap; gap: var(--k-space-3); margin-inline-start: auto; }

/* =============================================================================
   §W4 — COMMAND BAR: one row of buttons, always (owner ruling 2026-07-24)
   "on all page types we should only allow 1 row of buttons — only keeping out the most important buttons, and
    hiding any that would overflow into a second row into a 'more actions' button that shows them in a dropdown."

   The behaviour is CommandBar.razor + kanine-commandbar.js; this is only its presentation. Two states, switched by
   the single `kl-is-collapsed` class the measurer applies:

     FITS      — .kl-cmdbar-cluster is display:contents, so its actions ARE the bar's own flex children and render
                 byte-identically to the un-adopted markup, and the More trigger is display:none (out of the a11y
                 tree and the tab order, exactly when it would be a no-op).
     DOES NOT  — the cluster becomes an absolutely-positioned dropdown anchored to the bar's inline end, and the
                 trigger appears.

   The flex-wrap:wrap on .kl-head-actions above is deliberately LEFT ALONE. It is what makes the un-collapsed
   measurement honest (the measurer asks "do these land on more than one row?"), and it is the safe resting state
   for any bar that has not adopted the component or whose JS never ran.
   ========================================================================== */
.kl-shell-main .kl-cmdbar { position: relative; }

/* Dissolved: the cluster contributes no box, so its children are the bar's children. */
.kl-shell-main .kl-cmdbar-cluster { display: contents; }

/* The trigger only exists when the bar could not fit. */
.kl-shell-main .kl-cmdbar-more { display: none; }
.kl-shell-main .kl-cmdbar.kl-is-collapsed .kl-cmdbar-more { display: inline-flex; }

/* Collapsed: the cluster is a real box again — a dropdown panel, closed by default. */
.kl-shell-main .kl-cmdbar.kl-is-collapsed .kl-cmdbar-cluster { display: none; }
.kl-shell-main .kl-cmdbar.kl-is-collapsed .kl-cmdbar-cluster.is-open {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--k-space-2);
  position: absolute;
  inset-block-start: calc(100% + var(--k-space-1));
  inset-inline-end: 0;
  z-index: 60;                       /* the shared menu rung — above page content, below the modal overlay (80) */
  min-inline-size: 12rem;
  /* Arbitrary labels and a narrow phone: the panel caps to the viewport and scrolls INSIDE itself rather than
     pushing the page wide. Same lesson as the shell account hub (box-sizing is load-bearing here too). */
  box-sizing: border-box;
  max-inline-size: min(20rem, calc(100vw - var(--k-space-4)));
  max-block-size: min(24rem, calc(100vh - 6rem));
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--k-space-2);
  background: var(--k-surface, #fff);
  border: 1px solid var(--k-border, rgb(0 0 0 / .12));
  border-radius: var(--k-radius-md);
  box-shadow: 0 8px 24px rgb(0 0 0 / .18);
}

/* Inside the dropdown the actions are full-width rows, so a long label wraps within the panel instead of widening
   it. Touch targets are NEVER reduced to buy the row — collapsing IS how the row is bought (principle A6). */
.kl-shell-main .kl-cmdbar.kl-is-collapsed .kl-cmdbar-cluster.is-open > * {
  inline-size: 100%;
  justify-content: flex-start;
  min-block-size: var(--k-touch);
}

/* The trigger keeps the WCAG floor like every other icon+label button in a bar. */
.kl-shell-main .kl-cmdbar-more { min-block-size: var(--k-touch); align-items: center; gap: var(--k-space-2); }

/* Below the field breakpoint the trigger sheds its text label and reads as the standard overflow glyph, so the
   collapsed bar costs one 44px target instead of a wide pill on a 320px phone. */
@media (max-width: 640px) {
  .kl-shell-main .kl-cmdbar-more-label { display: none; }
  .kl-shell-main .kl-cmdbar-more { min-inline-size: var(--k-touch); justify-content: center; }
}

/* Explicit filter-panel card (a multi-class element can't lean on the [class$="-card"] whole-attribute suffix). */
/* W1 (2026-07-23): `margin-block-end: var(--k-space-4)` was REMOVED here. §8a's `.kl-page` is a flex column with
   its own `gap: var(--k-space-4)`, and a flex gap NEVER collapses with a margin — so every listing screen paid 32px
   where 16px was intended. The gap now owns the rhythm; the block-context restoration lives in §8e-w1 below. */
.kl-shell-main .kl-filterpanel {
  background: var(--k-surface);
  border: 1px solid var(--k-border);
  border-radius: var(--k-radius-lg);
  box-shadow: var(--k-elev-1);
  padding: var(--k-space-6);
}

/* ---- 8e-w1. Rhythm restoration for NON-flex containers (W1, 2026-07-23) ------
   The gap-owns-the-rhythm rule above only holds where the parent actually provides one. Probed live across all
   twelve reachable listing screens: 15 filterpanel/searchrow elements, 14 of them direct children of `.kl-page`
   (flex, 16px gap). The single exception is /users-roles, where `.kl-filterpanel` sits inside `.kl-list-pane`,
   a plain `display:block` section with no gap — there the margin was load-bearing, so it is restored explicitly.
   Keyed on the CONTAINER, not the page, so any future block-context reuse inherits the same spacing. */
.kl-shell-main .kl-list-pane > .kl-filterpanel,
.kl-shell-main .kl-list-pane > .kl-searchrow,
.kl-shell-main .kl-detail-pane > .kl-filterpanel,
.kl-shell-main .kl-detail-pane > .kl-searchrow {
  margin-block-end: var(--k-space-4);
}

/* Same doubled-gap mechanism, different selector family: §5l's `[class$="-filters"]` / `[class$="-filterdrawer"]`
   container rule also carries `margin-block-end: var(--k-space-4)` (e.g. `.kl-quick-filters` on /records). That
   family is used in many nested contexts where the margin IS load-bearing, so it is neutralised only where the
   flex gap provably already applies — as a DIRECT child of `.kl-page`. */
.kl-shell-main .kl-page > :where([class^="kl-"],[class*=" kl-"])[class$="-filters"],
.kl-shell-main .kl-page > :where([class^="kl-"],[class*=" kl-"])[class$="-filterdrawer"] {
  margin-block-end: 0;
}

/* ---- 8f. Collapsible filter card (tame the dense filter wall; registry-2) ---- */
.kl-shell-main details.kl-collapse { border: 0; }
.kl-shell-main details.kl-collapse > summary {
  display: inline-flex; align-items: center; gap: var(--k-space-2);
  min-block-size: var(--k-touch); padding: 0 var(--k-space-3);
  cursor: pointer; list-style: none; color: var(--k-accent); font-weight: 600; font-size: 14px;
  border-radius: var(--k-radius-md); inline-size: fit-content;
}
.kl-shell-main details.kl-collapse > summary::-webkit-details-marker { display: none; }
.kl-shell-main details.kl-collapse > summary::before { content: "▸"; transition: transform var(--k-dur-1) var(--k-ease); }
.kl-shell-main details.kl-collapse[open] > summary::before { transform: rotate(90deg); }

/* ---- 8g. Small layout helpers (search row + sr-only accessible name) -------- */
/* W1 (2026-07-23): `margin-block-end` REMOVED — see the note on `.kl-filterpanel` (§8e). `.kl-page`'s flex gap owns
   the rhythm; §8e-w1 restores it for the non-flex `.kl-list-pane` / `.kl-detail-pane` contexts. */
.kl-shell-main .kl-searchrow {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--k-space-3);
}
.kl-shell-main .kl-searchbox { flex: 1 1 16rem; min-inline-size: 0; inline-size: auto; }

/* ---- 8g-w2. Invisible readiness markers (W2, 2026-07-23) --------------------
   69 `<div data-testid="…-committed">` hooks across 66 files exist purely so E2E can observe that a surface
   settled. Most render EMPTY, yet as direct flex children of `.kl-page` each one still counts as a flex item and
   therefore consumes a gap on BOTH sides — 32px of layout around nothing.
   `position: absolute` takes them out of flow while KEEPING their box, the DOM node and every `data-*` attribute
   (unlike `display: contents`, which deletes the box outright and can break anything waiting on the element).
   `:empty` is load-bearing, not decoration: the naming convention alone does NOT identify these markers — the same
   suffix is carried by three REAL content containers (`manifest-receipt-committed` wraps the whole receipt,
   `packet-builder-committed` the whole builder, `mn-demographics-committed` a visible status line). `:empty`
   excludes those automatically, and self-heals: the moment a marker gains a child it drops out of this rule and
   returns to normal flow — which is exactly the failure mode that made `display: contents` unsafe here. */
[data-testid$="-committed"]:empty { position: absolute; }

/* Accessible-name-only (visually hidden, still in the a11y tree) — e.g. a <caption> behind a visible section head. */
.kl-visually-hidden {
  position: absolute !important; inline-size: 1px; block-size: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0);
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---- 8g-a8. DESK-ONLY controls — the phone-width export rule (§A8) ----------
   Owner ruling 2026-07-25, on the daily log: "on daily log, on mobile screen size web and maui. the filters
   button has free space to the right but the export log button displays on the next row instead (WAIT, export
   buttons dont belong on the phone size displays)". A LIST/LEDGER export is a desk task. `RecordList` already
   carried exactly that reasoning as a HEAD rule ("A desk task — hidden where the export client is not provided
   (the field head), so the registry stays usable there without the packet pipeline"); this promotes it to a
   WIDTH rule, which is what "web and maui" means — a narrow browser window on a desktop obeys it identically.

   `display: none` is load-bearing, NOT a stylistic pick. `visibility: hidden` / `opacity: 0` / an off-screen
   clip would leave the control in the TAB ORDER and in the ACCESSIBILITY TREE: a keyboard or screen-reader
   user would still reach an "Export log" button the sighted phone user cannot see — a worse defect than the
   wrapped row this closes. `display: none` drops the subtree from layout, from the a11y tree and from
   sequential focus navigation together. The shell's own hamburger and the command bar's More trigger are
   hidden the same way, for the same stated reason (§W4).

   `!important` is likewise deliberate. Consumers set their own `display` — the Expenses export cluster is a
   Blazor-SCOPED `display: flex` group (specificity 0,2,0) and lives inside a `display: contents` command-bar
   cluster. A one-way "must be ABSENT" safety rule must not lose a specificity race to a page's own layout
   declaration; §8j's `.kl-tcard-hide` already uses `!important` for the same class of guarantee.

   NOT scoped to `.kl-shell-main`: the rule must hold wherever a governed control renders, and a specificity
   bump would defeat the point of the flag. It is opt-in by class, so it can never capture anything by accident.

   Consumers must be governed markup, not decoration: this is a CAPABILITY REMOVAL, so it belongs only on
   list/ledger exports (per-item exports a handler may genuinely need on scene are deliberately NOT flagged).
   Enforced by app/tests/Kanine.Web.Tests/Req_DeskOnlyExportTests.cs. */
@media (max-width: 640px) {
  .kl-desk-only { display: none !important; }
}

/* Skip-to-content (WCAG 2.4.1 Bypass Blocks). Off-screen until focused, NOT display:none — a hidden-by-display
   link is not focusable, so it could never be reached by the keyboard user it exists for. z-index clears the
   sticky topbar (z-index:70) so it is not painted behind the chrome it is helping the user skip past. */
.kl-skip-link {
  position: fixed; inset-block-start: 0; inset-inline-start: 0; z-index: 200;
  transform: translateY(-120%);
  padding: var(--k-space-3, 12px) var(--k-space-4, 16px);
  background: var(--k-teal-500); color: var(--k-ink-900);
  font-weight: 600; text-decoration: none;
  border-end-end-radius: var(--k-radius-md, 8px);
}
.kl-skip-link:focus, .kl-skip-link:focus-visible {
  transform: translateY(0);
  outline: 2px solid var(--k-ink-900); outline-offset: -4px;
}
@media (prefers-reduced-motion: no-preference) { .kl-skip-link { transition: transform 120ms ease-out; } }

/* ---- 8h. Drafts panel (REC-056 page-major sign-off) -----------------------
   Consumes the cross-cutting primitives (kl-section-head, kl-count, kl-head-actions, kl-filterchip,
   :where([class^="kl-"],[class*=" kl-"])[class$="-row"], kl-btn). Adds only the row's column rhythm + the trailing-action push (light/outdoor proven).
   NOTE: this page's DARK-mode neutral-button/chip contrast rides the FOUNDATION dark-button issue tracked in
   ProposedUI/docs/UI-OPEN-QUESTIONS.md (affects every page incl. the signed-off records-registry) — not fixed here. */
.kl-shell-main .kl-drafts-filters { align-items: center; }
.kl-shell-main .kl-draft-row { flex-wrap: wrap; gap: var(--k-space-2) var(--k-space-4); }
.kl-shell-main .kl-draft-dog { font-weight: 700; color: var(--k-text); }
.kl-shell-main .kl-draft-start,
.kl-shell-main .kl-draft-age { color: var(--k-text-muted); font-size: 13px; font-variant-numeric: tabular-nums; }
.kl-shell-main .kl-draft-actions {
  margin-inline-start: auto; display: inline-flex; align-items: center; gap: var(--k-space-2);
}
.kl-shell-main .kl-drafts-paging {
  display: flex; align-items: center; justify-content: center; gap: var(--k-space-3);
  margin-block-start: var(--k-space-4);
}

/* =============================================================================
   8i. Shared involvement popup — GLOBAL (un-scoped) modal + tabs + count chips.
   -----------------------------------------------------------------------------
   PROMOTED from the record-build surface's page-scoped .kl-rb-modal so the new
   cross-file Involvement components (Components/Involvement/*) share ONE overlay /
   modal / tab-strip / chip — a Blazor-scoped page rule would not reach a sibling
   component's markup. Every value is an on-brand --k-* token (mode-adaptive: light
   / dark / outdoor); focus-visible rings throughout.
   ========================================================================== */
/* ==========================================================================
   THE one enforced centered-overlay primitive (UI-GUAR-033 / owner ruling §H.1
   #22). Every modal/dialog/sheet renders as a TRUE overlay through this class
   (the <KlOverlay> Blazor wrapper applies it + owns ESC/outside-click): a
   single full-viewport position:fixed inset:0 scrim, DIM at a FIXED navy
   alpha .55 (≥0.5 and MODE-STABLE — NOT a --k-text/color-mix that flips light
   in dark), centered, stacked ABOVE the shell. The shell rail/top-bar/bottom
   nav top out at z-index 60 (kl-shellnav-rail desktop 50 / mobile 60, kl-
   shellnav rail drawer 60); z-index:80 here clears all of them so a modal is
   never painted under the chrome. There is exactly ONE scrim element (the
   overlay IS the scrim — the panel is its child). Retired the dead
   .kl-dialog::backdrop (the app ships NO native <dialog>/showModal, so a
   ::backdrop on a <div> never renders — it was a scrimless no-op).
   ========================================================================== */
.kl-modal-overlay {
  position: fixed; inset: 0; z-index: 80;
  display: flex; justify-content: center;
  padding: var(--k-space-4);
  background: rgb(10 26 43 / .55); backdrop-filter: blur(2px);

  /* A dialog TALLER than the viewport was previously unreachable at both ends. The overlay is fixed with no
     overflow, so an over-tall flex child simply overflowed the scrim with no scrollbar — and because
     `align-items: center` centres the overflow, BOTH the header and the ACTIONS FOOTER were clipped off-screen
     with no way to reach them. A user could open a tall dialog and be unable to press Save.
     Caught by Playwright on the daily-log create dialog ("Element is outside of the viewport", after
     scroll-into-view and even with a forced click) once this branch added four fields to it. It is a SHARED
     defect, not a daily-log one: any dialog that outgrows the viewport had it.
     Fix: let the scrim scroll, and centre with `safe` so overflow spills only downward (reachable) instead of
     symmetrically (unreachable). `safe` degrades to normal centring on engines that lack it, which is the
     pre-existing behaviour — never worse. */
  overflow-y: auto;
  align-items: safe center;
}
/* Edge drawer / side-sheet exception (still a real scrim, just NOT centered):
   <KlOverlay Edge="..."> stamps data-edge so the auditor admits the off-center
   placement while STILL requiring the full-viewport ≥0.5 scrim. The panel pins
   to the named edge and fills the cross-axis; the scrim dim is unchanged. */
.kl-modal-overlay[data-edge] { padding: 0; }
.kl-modal-overlay[data-edge="end"] { justify-content: flex-end; align-items: stretch; }
.kl-modal-overlay[data-edge="start"] { justify-content: flex-start; align-items: stretch; }
.kl-modal-overlay[data-edge="bottom"] { align-items: flex-end; justify-content: stretch; }
.kl-modal-overlay[data-edge="top"] { align-items: flex-start; justify-content: stretch; }
/* The <KlOverlay> panel slot is layout-transparent (display:contents): it carries the
   @onclick:stopPropagation seam (so an inside-click never closes) WITHOUT inserting a box
   that would defeat the overlay's centering / edge placement — the child panel becomes the
   direct flex item. */
.kl-overlay-slot { display: contents; }
.kl-modal {
  inline-size: min(40rem, 100%); max-block-size: min(85vh, 48rem); overflow: auto;
  display: flex; flex-direction: column; gap: var(--k-space-4);
  background: var(--k-surface); color: var(--k-text);
  border: 1px solid var(--k-border); border-radius: var(--k-radius-lg);
  box-shadow: var(--k-elev-3); padding: var(--k-space-6);
}
.kl-modal-head { display: flex; align-items: center; justify-content: space-between; gap: var(--k-space-3); }
.kl-modal-title { margin: 0; font-family: var(--k-font-display); font-size: 18px; font-weight: 800; color: var(--k-text); }

/* The tab strip — UNDERLINE tabs (owner ruling 2026-06-29): no pills, no fill. The selected tab is a navy-bold label
   with a 2px TEAL underline; inactive tabs are muted text. Teal stays a THIN accent (the brand doctrine bans a large
   teal fill — it glows/vibrates in dark mode), so this is dark/outdoor-safe where the old teal-filled pill was not. */
.kl-modal-tabs {
  display: flex; align-items: stretch; gap: var(--k-space-4); flex-wrap: wrap;
  border-block-end: 1px solid var(--k-border);
}
.kl-modal-tab {
  appearance: none; background: transparent; color: var(--k-text-muted);
  border: 0; border-block-end: 2px solid transparent; border-radius: 0;
  margin-block-end: -1px; /* overlap the strip's 1px rail so the active underline sits on it */
  min-block-size: var(--k-touch); padding: 0 var(--k-space-1);
  font: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: color var(--k-dur-1) var(--k-ease), border-color var(--k-dur-1) var(--k-ease);
}
.kl-modal-tab:hover { color: var(--k-text); }
.kl-modal-tab[aria-selected="true"] { color: var(--k-text); font-weight: 700; border-block-end-color: var(--k-accent); }
.kl-modal-tab:focus-visible { outline: 2px solid var(--k-accent); outline-offset: 2px; border-radius: var(--k-radius-sm); }
.kl-modal-body { display: flex; flex-direction: column; gap: var(--k-space-4); }

/* The clickable count chip — a .kl-chip rendered as a button (resets the native button chrome). It pins its OWN
   semantic-token colours (NOT the base .kl-chip neutral gray palette, which does not flip in dark) so the count stays
   legible across light / dark / outdoor; the later single-class rule wins over .kl-chip. */
.kl-involve-chips { display: inline-flex; align-items: center; gap: var(--k-space-2); flex-wrap: wrap; }
.kl-involve-chip {
  appearance: none; cursor: pointer; font: inherit;
  color: var(--k-text); background: var(--k-surface-2); border-color: var(--k-border);
}
.kl-involve-chip:hover { border-color: var(--k-accent); background: var(--k-surface); }
.kl-involve-chip:focus-visible { outline: 2px solid var(--k-accent); outline-offset: 2px; }

/* The "+ Add entity" bar (the PARENT renders it next to the chips) + the popup's file / UoF blocks. */
.kl-involve-bar { display: flex; align-items: center; gap: var(--k-space-3); flex-wrap: wrap; }
.kl-involve-modal { inline-size: min(46rem, 100%); }
.kl-involve-files { display: flex; flex-direction: column; gap: var(--k-space-3); }
.kl-involve-file { position: relative; align-self: flex-start; overflow: hidden; cursor: pointer; }
.kl-involve-file-input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.kl-involve-files-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--k-space-2); }
.kl-involve-files-item {
  display: flex; align-items: center; gap: var(--k-space-3);
  padding: var(--k-space-3) var(--k-space-4); border: 1px solid var(--k-border);
  border-radius: var(--k-radius-md); background: var(--k-surface-2);
}
.kl-involve-files-name { font-weight: 600; color: var(--k-text); }
.kl-involve-files-remove { margin-inline-start: auto; }

/* GEO-022 / ARCH-036 — resumable video upload + inline playback in the Files panel. */
.kl-involve-files-item { flex-direction: column; align-items: stretch; }
.kl-involve-files-row { display: flex; align-items: center; gap: var(--k-space-3); }
.kl-involve-files-video {
  width: 100%; max-height: 280px; margin-top: var(--k-space-2);
  border-radius: var(--k-radius-md); background: #000;
}
.kl-involve-file-actions { display: flex; flex-wrap: wrap; gap: var(--k-space-2); align-items: center; }
.kl-video-upload { position: relative; display: inline-flex; flex-direction: column; gap: var(--k-space-1); }
.kl-video-upload-progress { display: flex; align-items: center; gap: var(--k-space-2); min-width: 14rem; }
.kl-video-upload-track {
  flex: 1; height: 8px; border-radius: 999px; overflow: hidden;
  background: var(--k-surface-3, var(--k-border));
}
.kl-video-upload-bar { height: 100%; background: var(--k-accent); transition: width .15s ease; }
.kl-video-upload-phase { font-size: .85rem; color: var(--k-text-muted); white-space: nowrap; }
.kl-video-upload-failure { margin-top: var(--k-space-2); }
/* The two empty states. NOTE (2026-07-25): the four `kl-involve-uof-*` selectors here named
   `-stub` / `-title` / `-note` / `-icon`, and the markup has emitted `-empty` / `-empty-title` /
   `-empty-note` / `-empty-icon` since the panel was built — so every one of them was a DEAD rule
   and the Use-of-Force empty state was picking up nothing but section 5h's generic state kit.
   Repointed to the classes the component actually renders; the files variant is unchanged. */
.kl-involve-files-empty,
.kl-involve-uof-empty {
  display: flex; flex-direction: column; align-items: center; gap: var(--k-space-1);
  text-align: center; padding: var(--k-space-6) var(--k-space-4);
  border: 1px dashed var(--k-border); border-radius: var(--k-radius-md); color: var(--k-text-muted);
}
.kl-involve-files-empty-title,
.kl-involve-uof-empty-title { margin: 0; font-weight: 700; color: var(--k-text); }
.kl-involve-files-empty-note,
.kl-involve-uof-empty-note { margin: 0; font-size: 13px; }
.kl-involve-files-empty-icon,
.kl-involve-uof-empty-icon { font-size: 28px; color: var(--k-text-muted); }

/* ---- 6.99 Detail-screen header icons (UI-GUAR-019 / R2-08) ----------------- */
/* Every detail/view screen leads its identity + each section/card header with a large
   .ki icon (owner ruling section H.1 #8). This shared utility gives the leading icon a
   consistent trailing gap + optical alignment WITHOUT each detail page re-declaring it.
   The icon scales with the heading font-size (the .ki base is font-relative), so it is
   "large" on the 16-24px section/identity headings. Accent-tinted so it reads as a
   hierarchy marker, not body text; flips per-mode via the --k-accent token. */
.kl-detail-h-icon {
  margin-inline-end: var(--k-space-2);
  color: var(--k-accent);
  font-size: 0.92em;
  vertical-align: -0.05em;
  flex-shrink: 0;
}

/* =============================================================================
   6.100 PRE-AUTH / ACCOUNT CARD FAMILY (kl-login-*) — AUTH Phase 7 visual heal.
   The sign-in card vocabulary was authored only in Login.razor.css (Blazor-SCOPED
   to Login.razor), so every OTHER page composing it — /signup, /invite,
   /account/two-factor-challenge, /account/first-login, /account/link-google|apple,
   /logout, and the /account/security forms — rendered those classes UNSTYLED
   (no navy brand band, no card width, default h1). Promoted here — the same heal
   as the explicit .kl-banner family (§5g) — so ONE authoring styles every auth
   surface; Login.razor.css is retired. Scoped under .kl-shell-main at the same
   specificity as the §5a/5b suffix families and AFTER them in source, so the
   card-shell overrides (padding:0 etc.) win by order. Token-driven throughout —
   light/dark/outdoor flip with the --k-* foundation; flat fills only (R11).
   ========================================================================== */
.kl-shell-main .kl-login {
  display:flex; flex-direction:column; gap:var(--k-space-6);
  inline-size:100%; max-inline-size:28rem; margin-inline:auto;
}

/* The card is a chromeless shell — the brand band and content own the padding
   (defeats the §5a :where([class^="kl-"],[class*=" kl-"])[class$="-card"] padding + trailing margin). */
.kl-shell-main .kl-login-card {
  inline-size:100%; padding:0; margin-block-end:0; overflow:hidden;
  background:var(--k-surface);
  border:1px solid var(--k-outline);
  border-radius:var(--k-radius-lg);
  box-shadow:0 4px 24px rgba(10, 26, 43, 0.08);
}

/* Navy brand band. --k-ink-800 is a FIXED brand token (never re-points per mode),
   so the band + its white/teal type hold their contrast in light, dark, and outdoor. */
.kl-shell-main .kl-login-brand {
  background:var(--k-ink-800); color:#fff;
  padding:var(--k-space-8) var(--k-space-6);
  display:flex; flex-direction:column; align-items:center; text-align:center;
  gap:var(--k-space-1);
  border-block-end:1px solid var(--k-ink-900);
}
.kl-shell-main .kl-login-mark { inline-size:48px; block-size:48px; margin-block-end:var(--k-space-2); }
.kl-shell-main .kl-login-title {
  margin:0; font-family:var(--k-font-display);
  font-size:1.6rem; font-weight:700; letter-spacing:-0.01em; color:#fff;
}
.kl-shell-main .kl-login-subtitle {
  margin:0; font-size:0.72rem; text-transform:uppercase; letter-spacing:0.18em;
  color:var(--k-teal-400); opacity:.9;
}

.kl-shell-main .kl-login-content { padding:var(--k-space-6); }
@media (min-width: 480px) { .kl-shell-main .kl-login-content { padding:var(--k-space-8); } }

.kl-shell-main .kl-login-view { display:flex; flex-direction:column; gap:var(--k-space-4); }
.kl-shell-main .kl-login-view--center { text-align:center; align-items:center; }

.kl-shell-main .kl-login-h2 {
  margin:0; font-family:var(--k-font-display); font-size:18px; font-weight:700; line-height:1.3;
  color:var(--k-text);
}
.kl-shell-main .kl-login-h3 { margin:0; font-size:15px; font-weight:600; color:var(--k-text); }
.kl-shell-main .kl-login-p { margin:0; font-size:14px; line-height:1.55; color:var(--k-text-muted); }
/* Prose links inside a login paragraph read accent — but a BUTTON that happens to be an <a>
   must keep its own button text colour. Same `:not([class*="-btn"])` guard as the app-wide
   `.kl-shell-main a` rule (§4, above): this rule is a narrower restatement of it that was
   authored without the guard, so at 0,2,1 it out-specified `.kl-btn--primary` (0,1,0) and
   painted var(--k-accent) TEXT on a var(--k-accent) BACKGROUND — 1:1 contrast, an invisible
   label (measured on /signup/cloud/complete's "Sign in" CTA, the post-payment landing).
   `-btn` (not `--`) is the discriminator because it also covers the un-modified `.kl-btn`
   and the kf-btn / *-btn families that §8 paints as buttons. */
.kl-shell-main .kl-login-p a:not([class*="-btn"]) { color:var(--k-accent); font-weight:600; }

/* Form rhythm: the form's own gap paces the fields (defeat the §5b family's
   per-field trailing margin so spacing is one system, not two stacked ones). */
.kl-shell-main .kl-login-form,
.kl-shell-main .kl-login-realm-form { display:flex; flex-direction:column; gap:var(--k-space-4); }
.kl-shell-main .kl-login-field { margin-block-end:0; gap:var(--k-space-1); }
.kl-shell-main .kl-login-label { color:var(--k-text); font-size:13px; font-weight:600; }

/* The collapsible recovery-code fallback (two-factor): the <summary> IS the label —
   make it read and hit like a control. */
.kl-shell-main summary.kl-login-label { cursor:pointer; display:flex; align-items:center; gap:var(--k-space-2); min-block-size:var(--k-touch); }
.kl-shell-main details[open] > summary.kl-login-label { margin-block-end:var(--k-space-1); }

/* ...and it needs a VISIBLE disclosure affordance, which the rule above accidentally removed: `display:flex` on a
   <summary> suppresses the browser's own marker, so this rendered as a plain bold sentence with nothing to suggest
   it opens. `cursor:pointer` is not discoverable — it only appears once the pointer is already on the words, and
   never at all on a touch device. That matters more here than anywhere else on the screen: this line is the ONLY
   self-service way past the second factor for someone whose authenticator is gone, and everything else in the card
   (a label, a field, a button) looks like a control while the one escape looked like a caption.
   Found while filming catalogue B4 (2026-07-30) — the beat needed a positive control to prove its click had done
   anything, which is the same difficulty a real user has. Same rotating caret §8f already uses on the collapsible
   filter card, so the product has ONE disclosure idiom rather than two. */
.kl-shell-main summary.kl-login-label { list-style:none; }
.kl-shell-main summary.kl-login-label::-webkit-details-marker { display:none; }
.kl-shell-main summary.kl-login-label::before {
  content:"\25B8"; color:var(--k-accent); transition:transform var(--k-dur-1) var(--k-ease);
}
.kl-shell-main details[open] > summary.kl-login-label::before { transform:rotate(90deg); }

/* Screen-reader-only label (pre-auth pages have no .kl-visually-hidden dependency). */
.kl-login-srlabel {
  position:absolute; inline-size:1px; block-size:1px; overflow:hidden;
  clip:rect(0 0 0 0); white-space:nowrap;
}

/* Divider + the layered agency affordances (home-realm lookup, K96 route, signup). */
.kl-shell-main .kl-login-divider {
  display:flex; align-items:center; gap:var(--k-space-4);
  color:var(--k-text-muted); font-size:0.72rem; text-transform:uppercase; letter-spacing:0.08em;
}
.kl-shell-main .kl-login-divider::before,
.kl-shell-main .kl-login-divider::after { content:""; flex:1; border-block-start:1px solid var(--k-outline); }

.kl-shell-main .kl-login-agency { display:flex; flex-direction:column; gap:var(--k-space-4); margin-block-start:var(--k-space-2); }

.kl-shell-main .kl-login-link {
  align-self:center; display:inline-flex; align-items:center; min-block-size:var(--k-touch);
  color:var(--k-accent); font-size:14px; font-weight:600; text-decoration:none;
}
.kl-shell-main .kl-login-link:hover { text-decoration:underline; }
.kl-shell-main .kl-login-link:focus-visible { outline:2px solid var(--k-accent); outline-offset:2px; border-radius:var(--k-radius-sm); }

/* login.html#button.action[2] — the password field and its visibility toggle share one relative box, so the
   toggle sits INSIDE the input's trailing padding rather than beside it (the prototype treatment). Restored
   2026-08-02 with the control; the rules came off the pre-AUTH-P3 Login.razor.css unchanged. */
.kl-shell-main .kl-login-pw { position:relative; display:flex; align-items:center; }
.kl-shell-main .kl-login-pw .kl-login-input { padding-inline-end:3rem; }
.kl-shell-main .kl-login-pw-toggle {
  position:absolute; inset-inline-end:var(--k-space-2);
  display:inline-flex; align-items:center; justify-content:center;
  min-inline-size:var(--k-touch); min-block-size:var(--k-touch);
  background:none; border:0; color:var(--k-text-muted); cursor:pointer;
}
.kl-shell-main .kl-login-pw-toggle:hover { color:var(--k-text); }
.kl-shell-main .kl-login-pw-toggle:focus-visible { outline:2px solid var(--k-accent); outline-offset:2px; border-radius:var(--k-radius-sm); }

/* login.html#button.action[6] + [9] — the lockout banner's action row (Acknowledge / Contact administrator).
   It sits INSIDE the banner, under the message, so it wraps on a narrow card rather than crushing the labels. */
.kl-shell-main .kl-login-actions {
  display:flex; flex-wrap:wrap; gap:var(--k-space-2);
  margin-block-start:var(--k-space-3);
}

/* Inside the card the view's own gap paces banners — the app-wide trailing margin would double it. */
.kl-shell-main .kl-login-view .kl-banner { margin-block-end:0; }

/* The home-realm context banner (AUTH §4) — a quiet teal-edged status band. */
.kl-shell-main .kl-login-realm {
  background:var(--k-surface-2); border-inline-start:4px solid var(--k-teal-500); color:var(--k-text);
}

.kl-shell-main .kl-login-foot {
  background:var(--k-surface-2); padding:var(--k-space-3) var(--k-space-4);
  border-block-start:1px solid var(--k-outline);
  text-align:center; font-size:12px; color:var(--k-text-muted);
}

/* Inside the auth card a note is a boxed teal-edged aside (denser than the
   app-wide muted :where([class^="kl-"],[class*=" kl-"])[class$="-note"] treatment). */
.kl-shell-main .kl-login .kl-readonly-note {
  margin:0; padding:var(--k-space-2) var(--k-space-3);
  background:var(--k-surface-2); border-inline-start:3px solid var(--k-teal-500);
  border-radius:var(--k-radius-sm); font-size:13px; color:var(--k-text-muted);
}

/* Social sign-in buttons (Login + Security): the official mark + label as one
   centered row; the inline SVG sizes itself (18px), so just align and space it. */
.kl-btn--provider { display:inline-flex; align-items:center; justify-content:center; gap:0.55rem; }
.kl-provider-logo { flex:0 0 auto; display:block; }

/* Account→Security: the auth-form vocabulary reused INSIDE settings cards — cap the
   line length so password fields don't stretch the full content column, and lay the
   provider link buttons side-by-side above their shared note. */
.kl-shell-main .kl-card .kl-login-form { max-inline-size:28rem; }
.kl-shell-main .kl-security-linkrow {
  display:flex; flex-wrap:wrap; gap:var(--k-space-3);
  margin-block-start:var(--k-space-4); margin-block-end:var(--k-space-2);
}

/* The one-time recovery-code reveal: a dashed evidence panel, codes on a mono grid. */
.kl-shell-main .kl-recovery-codes {
  margin:0; padding:var(--k-space-4); list-style:none;
  display:grid; grid-template-columns:repeat(auto-fit, minmax(9rem, 1fr)); gap:var(--k-space-2);
  background:var(--k-surface-2); border:1px dashed var(--k-border); border-radius:var(--k-radius-md);
  font-size:15px; letter-spacing:.04em;
}

/* ---- 7. Print (printouts always light) ------------------------------------ */
@media print {
  .kl-shellnav-rail, .kl-shellnav-topbar, .kl-shellnav-bottom, .kl-no-print { display:none !important; }
  .kl-shell { padding-inline-start:0 !important; }
  .kl-shell-main { margin:0 !important; padding:0 !important; }
  body { background:#fff; color:#000; }
}

/* ── FREE-plan chip (owner-directed 2026-07-18): the always-visible free-tier marker in the shell rail + topbar. ── */
.kl-shellnav-planchip{display:flex;align-items:center;gap:6px;margin:4px 14px 8px;padding:6px 10px;font-size:11px;
  font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--k-ink-900);background:var(--k-teal-400);
  border-radius:6px;text-decoration:none}
.kl-shellnav-planchip:hover{background:var(--k-teal-500)}
.kl-shellnav-planchip .ki{font-size:14px}
.kl-shellnav-planchip--top{margin:0 8px 0 0;padding:4px 8px}

/* =============================================================================
   8j. Location + weather disclosure — GLOBAL (un-scoped) accordion chrome.
   -----------------------------------------------------------------------------
   PROMOTED from RecordEnvelopeFieldsSection's page-scoped .kl-rb-envelope
   .kl-rb-locwx* rules (the .kl-rb-modal precedent above) when the accordion was
   extracted into the shared RecordLocationWeatherAccordion component so the
   quick-start scratch pad could mount the SAME disclosure the record-entry
   wizard uses. A Blazor-scoped rule reaches only the markup its own component
   renders, so the chrome had to leave the envelope sheet or be duplicated —
   this is the ONE copy, in the ONE shared CSS surface both heads load.

   The envelope's Defaults drawer and Ref. # accordion deliberately reuse this
   same chrome (they always did, by class); they keep their own scoped
   .kl-rb-defaults-* additions.

   The collapsed header PREVIEWS both values, so a working GPS autofill needs no
   open and a failed one is one tap from the manual controls. Every value is an
   on-brand --k-* token; the header clears the touch floor.
   ========================================================================== */
.kl-rb-locwx {
  padding: 0;
  border: 1px solid var(--k-border); border-radius: var(--k-radius-md);
  background: var(--k-surface); overflow: hidden;
}

/* HEADER — the always-visible at-a-glance display AND the tap target (≥44px). ONE line: tight vertical
   padding, the 44px floor comes from min-block-size, not stacked content, so it reads like a single field row. */
.kl-rb-locwx-head {
  appearance: none; background: none; border: 0; margin: 0; cursor: pointer;
  inline-size: 100%; min-block-size: var(--k-touch); /* the a11y floor, not a padded two-liner */
  display: flex; align-items: center; gap: var(--k-space-2);
  padding-block: var(--k-space-1); padding-inline: var(--k-space-4);
  font: inherit; color: var(--k-text); text-align: start;
}
.kl-rb-locwx-head:focus-visible {
  outline: 2px solid var(--k-accent); outline-offset: -2px; border-radius: var(--k-radius-md);
}

/* COLLAPSED — the single at-a-glance line: pin · address (ellipsised) · weather (pinned to the end). */
.kl-rb-locwx-line {
  flex: 1 1 auto; min-inline-size: 0;
  display: flex; align-items: baseline; gap: var(--k-space-2);
}
.kl-rb-locwx-loc {
  flex: 1 1 auto; min-inline-size: 0;
  display: inline-flex; align-items: baseline; gap: 4px;
}
.kl-rb-locwx-ico {
  flex: 0 0 auto; font-size: 13px; color: var(--k-text-muted); align-self: center;
}
.kl-rb-locwx-wx {
  flex: 0 0 auto; display: inline-flex; align-items: baseline; white-space: nowrap;
}
/* A hairline separator before the weather so it reads as its own segment on the shared line. */
.kl-rb-locwx-wx::before {
  content: "·"; color: var(--k-text-muted); margin-inline-end: var(--k-space-2);
}
/* OPEN — a slim uppercase title; the body renders the real controls, so the header sheds the previews. */
.kl-rb-locwx-title {
  flex: 1 1 auto;
  font-size: 12px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--k-text-muted); text-transform: uppercase;
}
/* The value shrink-fits and ellipsises so a long address never wraps the header taller. */
.kl-rb-locwx-val {
  min-inline-size: 0;
  font-size: 14px; color: var(--k-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The address is the flexible half of the line — it shrinks/ellipsises; the weather stays whole. */
.kl-rb-locwx-loc .kl-rb-locwx-val { flex: 1 1 auto; }
.kl-rb-locwx-muted { color: var(--k-text-muted); font-style: italic; }

.kl-rb-locwx-chevron {
  flex: 0 0 auto; font-size: 16px; color: var(--k-text-muted);
  transition: color var(--k-dur-1) var(--k-ease);
}
.kl-rb-locwx-head:hover .kl-rb-locwx-chevron,
.kl-rb-locwx-head[aria-expanded="true"] .kl-rb-locwx-chevron { color: var(--k-accent); }

/* BODY — the full controls, revealed on expand. A short height/opacity reveal keeps it native, no jank.
   [hidden] is what collapses it (the region always exists so aria-controls resolves); the explicit
   display:none is required because the flex declaration below would otherwise beat the UA hidden rule. */
.kl-rb-locwx-body {
  display: flex; flex-direction: column; gap: var(--k-space-4);
  padding: var(--k-space-4);
  border-block-start: 1px solid var(--k-border);
  animation: kl-rb-locwx-reveal var(--k-dur-2, 180ms) var(--k-ease) both;
}
.kl-rb-locwx-body[hidden] { display: none; }
@keyframes kl-rb-locwx-reveal {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .kl-rb-locwx-body { animation: none; }
}

/* ============================================================================
   AN ICON IS NEVER A CONTAINER (owner-reported 2026-07-22).
   ----------------------------------------------------------------------------
   The structural families above are matched by the SUFFIX of the class ATTRIBUTE
   — :where([class^="kl-"],[class*=" kl-"])[class$="-card"], :where([class^="kl-"],[class*=" kl-"])[class$="-btn"], :where([class^="kl-"],[class*=" kl-"])[class$="-badge"] and 60 more. That is a
   deliberate, useful convention for containers, but it cannot tell a container
   from an ICON whose semantic name happens to end the same way. Seventeen do:

       .ki-ms-credit-card  ends "-card"     .ki-save          ends "-save"
       .ki-ms-badge        ends "-badge"    .ki-delete        ends "-delete"
       .ki-error           ends "-error"    .ki-overflow-menu ends "-menu"      ...

   Whenever such an icon is written class-last — <span class="ki ki-ms-credit-card">
   — the attribute ends with "-card" and the icon was painted AS a card: surface
   background, 1px border, 16px radius, 24px padding, display:block. That is the
   "big white square where the icon should be" reported on the billing button: the
   glyph was rendering correctly all along, inside a white card of its own.

   Reset ONLY the container properties. Never font-size, colour or line-height —
   icons legitimately set those, and several deliberate treatments (the home
   quick-action chips, the plan chip) depend on them. Declared AFTER THE LAST
   STRUCTURAL SUFFIX RULE so source order settles the tie against every
   equal-specificity structural rule above; scoped component rules (which carry a
   b-* attribute, and so out-specify this) still win, exactly as they should.

   THAT — not "last in the file" — is the invariant, and the distinction is
   load-bearing (corrected 2026-07-25). Sections added below this point are fine
   PROVIDED they declare no `[class$="…"]` structural family; §9's query card and
   the card-list section that follow it declare none, and the two `.ki` rules they
   do carry (`.kl-qcard-toggle .ki`) set font-size / line-height / colour, which
   this block deliberately never touches and which out-specify it anyway.
   Req_Icons_NotAContainerTests enforces exactly this ordering, reading RULES and
   not the prose above — so append new sections freely, but if one ever needs a
   suffix family, move this block below it.
   ============================================================================ */
.kl-shell-main .ki {
  display: inline-block;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
  margin-block-end: 0;
}

/* ============================================================================
   §9. QUERY CARD (`.kl-qcard`) — the ONE list-surface query pattern
   ----------------------------------------------------------------------------
   Owner ruling 2026-07-25: every list/registry screen presents its query chrome
   as ONE card with ONE line —

       [ primary filter ]  [ search box ]  [ ONE "Filters" disclosure button ]

   — and everything else (the surplus filters, a search-scope picker, the
   show-deleted read toggle) lives behind that single button, inside the SAME
   card. Two cards and two disclosure controls for one region was the defect: it
   spent a whole second card of vertical space on chrome that is idle for the
   overwhelming majority of visits, and it made the phone and the desk two
   different things to learn.

   PROVENANCE. This pattern shipped first on the records registry (/records) —
   phone-only on 2026-07-24, promoted to every width on 2026-07-25 — where it
   took the query chrome from two cards (~227px) down to a single header line.
   Its CSS lived page-scoped in RecordList.razor.css. Sweeping the other seven
   registries onto it would have meant EIGHT copies of one pattern, which is
   precisely the divergence this repo has already been bitten by (RecordList and
   Dogs once carried two differently-scoped copies of the same rhythm fix — see
   Kanine.Architecture.Tests/SharedListingRhythmGuardTests). So it lives here,
   once, in the RCL that BOTH heads load. Only genuinely per-page deltas (which
   control is the primary, and how wide it is) stay page-scoped.

   OPT-IN BY WRAPPER. Every rule below is anchored to `.kl-qcard`, so a
   `.kl-searchrow` / `.kl-searchbar` / `.kl-filterpanel` anywhere else in the app
   is completely untouched.

   ANATOMY (RecordList.razor and Vehicles.razor are the reference hosts):

     <div class="kl-qcard kl-qcard--hassearch">          <- wrapper
       <section class="kl-searchrow [kl-qcard-open]">    <- HEADER line
         ...primary filter... ...search box...
         <button class="kl-qcard-toggle">Filters</button><- the ONE control
       </section>
       <section class="kl-filterpanel [kl-qcard-open]">  <- BODY (collapsed)
         <FilterAccordion HostOwnsDisclosure Expanded=... >
       </section>
     </div>

   DOM ORDER is header-then-body on purpose: a disclosure whose revealed content
   precedes its own button in the tab order is a WCAG 2.4.3 defect. Because the
   markup order already IS the visual order, no `order` reversal is needed at any
   width.

   NOTE ON THE SHARED-RHYTHM GUARD: nothing here sets `margin-block-end` on
   `.kl-filterpanel` / `.kl-searchrow`. The wrapper is the flex child of
   `.kl-page`, so the page gap still owns the rhythm (§8e-w1).
   ============================================================================ */

/* The wrapper butt-joins its two sections into a single card — `gap: 0` is the
   whole trick; `.kl-page`'s own gap still separates the card from its siblings. */
.kl-shell-main .kl-qcard {
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* Collapsed: the body is not merely empty, it is GONE — no box, no border, no
   page gap. That vanished card IS the reclaim. `display:none` is correct for a
   collapsed disclosure: the button's aria-expanded="false" already tells
   assistive tech the content is not exposed (and the FilterAccordion surplus
   inside it carries [hidden] for the same reason). */
.kl-shell-main .kl-qcard > .kl-filterpanel { display: none; }

.kl-shell-main .kl-qcard > .kl-filterpanel.kl-qcard-open {
  display: block;
  /* Butt-joined under the header: no top border (it would double the header's)
     and no top radius, so the two boxes read as ONE card. */
  border-block-start: 0;
  border-start-start-radius: 0;
  border-start-end-radius: 0;
}

/* ---- The HEADER line. The baseline leaves `.kl-searchrow` a bare flex row; inside
   a query card it becomes the card itself. `flex-wrap: wrap` is load-bearing rather
   than cosmetic — it is what lets a revealed scope picker (or a wrapped control on a
   phone) drop onto its own line INSIDE the card. `align-items: flex-end` overrides
   the shared `center` so a .kf-field primary (whose label sits ABOVE its control)
   shares a bottom edge with the search box and the button instead of floating
   centred against them. ---- */
.kl-shell-main .kl-qcard > .kl-searchrow,
.kl-shell-main .kl-qcard > .kl-searchbar {
  position: relative;             /* positioning context for any popover */
  overflow: visible;              /* a scope popover must escape the card */
  background-color: var(--k-surface);
  border: 1px solid var(--k-border);
  border-radius: var(--k-radius-lg);
  padding: var(--k-space-2);
  box-shadow: var(--k-elev-1);
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--k-space-3);

  /* ---- THE EYEBROW: the brand hairline across the TOP edge of the MERGED card (owner report
     2026-07-25). It is painted as the card's OWN top background LAYER, not as an absolutely
     positioned `::before` bar.

     WHY NOT A ::before BAR. That is what shipped, and it overhung the rounded corners: a 2px-tall
     box cannot carry a 16px corner radius, because CSS scales every radius down by the same factor
     until it fits the box's shortest side (f = 2/16), so `border-start-*-radius: var(--k-radius-lg)`
     rendered as a ~2px nub and the bar ran the card's FULL width. At the top edge the card's painted
     area is inset by up to the corner radius, so the bar's ends stuck out past the card's outline —
     measured at 9.43px of overhang per corner, at every width.

     WHY NOT `overflow: hidden` ON THE CARD. It would clip the bar to the curve perfectly and it is
     the obvious fix — but this card deliberately hosts escaping UI. The scope picker's
     `.kl-searchscope-menu` is `position: absolute` INSIDE this row, so clipping truncates it; proven
     by screenshotting the popover with `overflow:hidden` forced on and diffing the pixels (they
     differ). `overflow: visible` above stays load-bearing.

     WHY NOT INSET THE BAR BY THE RADIUS. It works, but it stops the bar ~16px short of each end
     rather than following the curve, and it has to repeat --k-radius-lg so the two can drift apart.

     A BACKGROUND LAYER is clipped to the card's own rounded border-box by construction, so it hugs
     the corner curve exactly, at any width, radius or state, needs no second token, and touches
     neither `overflow` nor the stacking order. `background-position: 0 0` is the padding box's top
     edge — the same 2px band the old ::before occupied, so nothing else moves. ---- */
  background-image: linear-gradient(
      color-mix(in srgb, var(--k-accent) 55%, transparent),
      color-mix(in srgb, var(--k-accent) 55%, transparent));
  background-size: 100% 2px;
  background-repeat: no-repeat;
  background-position: 0 0;
}

/* The panel's own top accent is neutralised INSIDE a query card — the header owns
   the top edge now. */
.kl-shell-main .kl-qcard > .kl-filterpanel::before { content: none; }

/* Open: the header loses its bottom radius so it reads as one card with the body. */
.kl-shell-main .kl-qcard > .kl-searchrow.kl-qcard-open,
.kl-shell-main .kl-qcard > .kl-searchbar.kl-qcard-open {
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}

/* The search box carries the GROW on the header line (the primary is fixed-ish and
   the button is content-sized), with a floor so it never collapses to nothing. This
   overrides §8g's `flex: 1 1 16rem`, which would force a wrap on a three-control
   line at tablet width. */
.kl-shell-main .kl-qcard .kl-searchbox,
.kl-shell-main .kl-qcard .kl-search-shell {
  flex: 1 1 12rem;
  min-inline-size: 8rem;
}

/* A .kf-field primary on the header line: the form kit gives every field a
   --k-space-4 bottom margin for the STACKED grid, which on a single line would drop
   the control ~16px below the search box and the button. Zero it HERE only (the
   surplus grid's fields, which really do stack, are untouched). The width band keeps
   a select readable without letting it eat the line a page has to share. */
.kl-shell-main .kl-qcard > .kl-searchrow > .kf-field,
.kl-shell-main .kl-qcard > .kl-searchbar > .kf-field {
  margin-block-end: 0;
  flex: 0 1 15rem;
  min-inline-size: 9rem;
}

/* A non-field primary (a segmented tablist, a chip bar) sits content-sized on the
   header line and never grows into the search box's room. */
.kl-shell-main .kl-qcard-primary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--k-space-2);
  flex: 0 1 auto;
  margin-block-end: 0;
}

/* ---- THE one disclosure button. A real <button>: focusable, Enter/Space-operable
   for free, --k-touch tall (A6 — no target is ever shrunk to buy this space), in the
   quiet ghost-control language the rest of the header uses. ---- */
.kl-shell-main .kl-qcard-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--k-space-2);
  flex: 0 0 auto;
  min-block-size: var(--k-touch);
  padding: 0 var(--k-space-3);
  background: transparent;
  border: 1px solid var(--k-border);
  border-radius: var(--k-radius-md);
  color: var(--k-text);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--k-dur-1) var(--k-ease), border-color var(--k-dur-1) var(--k-ease);
}

.kl-shell-main .kl-qcard-toggle:hover {
  background: var(--k-surface-2);
  border-color: var(--k-accent);
}

.kl-shell-main .kl-qcard-toggle:focus-visible {
  outline: 2px solid var(--k-accent);
  outline-offset: 2px;
}

.kl-shell-main .kl-qcard-toggle .ki {
  font-size: 16px;
  line-height: 1;
  color: var(--k-text-muted);
}

/* B8 — a collapsed card must NEVER hide that the list on screen is narrowed. The
   accent + the count badge say "something folded in here is restricting this list";
   the count comes from the EXECUTED query, never the pending form state. */
.kl-shell-main .kl-qcard-toggle--active { border-color: var(--k-accent); }
.kl-shell-main .kl-qcard-toggle--active .kl-qcard-toggle-label { color: var(--k-accent); }
.kl-shell-main .kl-qcard-toggle--active .ki { color: var(--k-accent); }

.kl-shell-main .kl-qcard-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 18px;
  padding-inline: var(--k-space-1);
  border-radius: var(--k-radius-full);
  background: var(--k-accent);
  color: var(--k-surface);
  font-family: var(--k-font-mono);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ---- PHONE (max-width 640px). The collapsed header must stay ONE line:
   [search] [Filters]. A primary filter on that line would regrow the very card just
   reclaimed and, at 393px, would wrap to do it — so on a page that HAS a search box
   the primary travels WITH the disclosure: hidden while collapsed, full-width on its
   own line inside the card when open. It is not lost while collapsed; the toggle's
   accent + count badge still say the list is restricted.

   A page with NO search box keeps its primary visible at every width (`--hassearch`
   is absent there) — hiding it would leave a header line containing nothing but the
   button, which is chrome disclosing chrome. ---- */
@media (max-width: 640px) {
  .kl-shell-main .kl-qcard--hassearch > .kl-searchrow > .kf-field,
  .kl-shell-main .kl-qcard--hassearch > .kl-searchbar > .kf-field,
  .kl-shell-main .kl-qcard--hassearch > .kl-searchrow > .kl-qcard-primary,
  .kl-shell-main .kl-qcard--hassearch > .kl-searchbar > .kl-qcard-primary {
    display: none;
  }

  .kl-shell-main .kl-qcard--hassearch > .kl-searchrow.kl-qcard-open > .kf-field,
  .kl-shell-main .kl-qcard--hassearch > .kl-searchbar.kl-qcard-open > .kf-field {
    display: flex;                 /* the form kit's label-over-control column, restored */
    flex: 1 1 100%;
  }

  .kl-shell-main .kl-qcard--hassearch > .kl-searchrow.kl-qcard-open > .kl-qcard-primary,
  .kl-shell-main .kl-qcard--hassearch > .kl-searchbar.kl-qcard-open > .kl-qcard-primary {
    display: flex;
    flex: 1 1 100%;
  }

  /* The opened body's desk padding costs 24px of vertical either side of a 393px column. */
  .kl-shell-main .kl-qcard > .kl-filterpanel.kl-qcard-open {
    padding: var(--k-space-3);
  }

  /* With the primary gone, the box + the one disclosure own the header line; 8rem is
     what keeps them on ONE line at 393px. */
  .kl-shell-main .kl-qcard--hassearch .kl-searchbox,
  .kl-shell-main .kl-qcard--hassearch .kl-search-shell {
    flex: 1 1 8rem;
  }
}

/* ---- THE TIGHT BAND, 641px to 900px (measured defect, fixed 2026-07-25) ------------------------------------
   Just above the phone breakpoint the primary comes BACK onto the header line while the content column is at
   its narrowest — the rail is still a drawer, so the card gets the viewport minus `.kl-shell-main`'s fluid
   gutters (~574px of content at 641px). Three controls have to share it.

   WHY IT WRAPPED, and why the existing floors did not save it: flexbox decides LINE BREAKS from each item's
   HYPOTHETICAL main size — its `flex-basis` — and only shrinks items AFTER they are placed on a line. The bases
   sum to more than the card long before any `min-inline-size` floor is consulted:

       /records   18rem (288) + 12rem (192) + 112 button + 24 gaps = 616 > 574   -> the button wraps
       /dogs      15rem (240) + 12rem (192) + 112 button + 24 gaps = 568 ~ 574   -> wraps at the tightest widths

   So `min-inline-size: 8rem` on the search box never applied: the box was never asked to shrink, the BUTTON was
   asked to leave. Measured wrapping widths before this rule (live, 20px steps): /records 641-681, /dogs 641-661,
   /vehicles 641-661. (An earlier sweep read /records as 641-767 and the other two as clean — that run was taken
   while the Kanine.Presentation.Pages scoped-CSS bundle was 404ing, so it measured pages missing their own
   sheets. See .verify/scoped-css-delivery.mjs, which now guards that delivery.)

   THE FIX is on the BASES, not the floors, and it is a band rather than a global change because the desk widths
   were never wrong: above ~900px there is slack and the primary should keep its comfortable width. Nothing here
   shrinks a touch target — `.kl-qcard-toggle` keeps `min-block-size: var(--k-touch)` and is untouched; only the
   two SHRINKABLE text controls give up hypothetical width, and each still stops at its existing floor.
   ------------------------------------------------------------------------------------------------------------ */
@media (min-width: 641px) and (max-width: 900px) {
  /* The primary: basis down to its own floor, so it asks for exactly what it will settle for. */
  .kl-shell-main .kl-qcard > .kl-searchrow > .kf-field,
  .kl-shell-main .kl-qcard > .kl-searchbar > .kf-field,
  .kl-shell-main .kl-qcard > .kl-searchrow > .kl-qcard-primary,
  .kl-shell-main .kl-qcard > .kl-searchbar > .kl-qcard-primary {
    flex-basis: 9rem;
  }

  /* The search box keeps `flex-grow: 1`, so it still absorbs every spare pixel on the line — it simply stops
     CLAIMING 12rem up front, which is what was pushing the one disclosure button onto a second row. */
  .kl-shell-main .kl-qcard .kl-searchbox,
  .kl-shell-main .kl-qcard .kl-search-shell {
    flex-basis: 8rem;
  }
}

/* =============================================================================
   10. CARD LIST (kl-cardlist) — the CONTAINER for off-table cards. It supplies the
   list and the card BOX only; the title LINE inside each card is section 8b-n's
   `.kl-cardline`, unchanged and un-copied.
   -----------------------------------------------------------------------------
   WHY THIS EXISTS. Four components rendered a `<ul>`/`<li>` card list whose every
   class was defined NOWHERE in `app/src` — `Involvement/UseOfForcePanel`,
   `SessionRevocationAdmin`, `MasterNameDedupQueue` and `LinkIntoRecordPanel`. They
   were unstyled block `<div>`s, so every child stacked at every width, and each one
   PASSED the wrap-and-overflow sweeps because a block element cannot wrap. That is
   conformance by ABSENCE, and it is invisible to every other check in this repo.
   (`app/tools/page-review/unstyled-class-scan.mjs` is the mechanical version of the
   hand audit that found them; it reports 400+ more classes in the same state.)

   WHAT THIS BLOCK DELIBERATELY DOES NOT DO. It does NOT define a title, a badge or
   an action slot. Section 8b-n already made A10's anatomy universal off-table
   through `.kl-cardline` + `.kl-tcard-title` / `.kl-tcard-badge` / `.kl-tcard-actions`,
   and a second spelling of the same three ideas is precisely the drift this review
   has been unwinding — two hand-rolled corner slots had already diverged by 4.5px in
   July. These four surfaces therefore wear `.kl-cardline` like every other off-table
   card in the product, and this section adds only the two things a LIST needs and a
   single card does not:

     1. THE LIST RESET. It cannot be inherited. Section 5l's `[class$="-list"]` family
        matches the whole class ATTRIBUTE, so the moment a list reads
        `class="kl-x-list kl-cardlist"` it no longer ends in `-list` and stops matching
        — adding the opt-in class is exactly what would remove the reset.
     2. THE CARD BOX. A `<tr>` gets its card chrome from section 8b-m's reflow and a
        page card from section 5a's `-card` suffix; an `<li>` in a purpose-built list
        has neither, so the surface / border / radius / padding are declared here. The
        padding matches 8b-m's collapsed row, which is what lets `--k-tcard-line` mean
        the same thing in all three places.

   Body content below the title line (`-body`, `-chips`, `-foot`) is content
   vocabulary, not anatomy, so it lives here rather than in 8b-n.
   ========================================================================== */

/* The block-rhythm wrapper a card list sits in (a panel/section that stacks blocks at
   the house gap instead of collapsing to default block margins). */
.kl-cardstack { display: flex; flex-direction: column; gap: var(--k-space-3); }

.kl-cardlist {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--k-space-3);
}

/* THE CARD BOX — a simple flex COLUMN: the `.kl-cardline` header, then the body lines.
   It needs no wrap, no ordering and no generated line break, because the card's header
   element already bounds the title line (8b-n's third deliberate difference from the
   table form). */
.kl-cardlist .kl-cardlist-item {
  display: flex; flex-direction: column; gap: var(--k-space-2);
  background: var(--k-surface); color: var(--k-text);
  border: 1px solid var(--k-border); border-radius: var(--k-radius-lg);
  box-shadow: var(--k-elev-1);
  padding: var(--k-space-3) var(--k-space-4);
}

/* TWO SCOPED TUNINGS of 8b-n's line, both MEASURED on the live harness, both written
   THROUGH the `.kl-cardlist` wrapper so they cannot reach the cards 8b-n owns. Section
   A9 clause 3 is the precedent: a genuine per-surface delta is written through the
   wrapper, never as a private re-implementation. Both are candidates for promotion into
   8b-n — raised in the handover rather than taken unilaterally.

   1. THE TITLE'S BASE WIDTH. `flex-basis: 0` is exactly right for a card whose line
      holds a title and a badge: the title asks for nothing and grows into the rest. It
      is NOT enough once a labelled ACTION SET shares the line, and the failure is not
      obvious — flexbox picks lines from base sizes, so a zero-basis title never forces
      a break, and the title is then handed only the free space left over. Measured at
      393px on the use-of-force entry (a 149px injury chip + a 131px View/Edit pair):
      the title was allotted 23px and its timestamp wrapped down SEVEN lines, making a
      120px title line and a 206px card. A 6rem base makes the title claim its share up
      front, which pushes the action slot onto a line of its own exactly when it will
      not fit — and leaves the one-line card alone where it does (the involvement panel
      still lands all three on one 74px line). It is a BASE, not a floor: `flex-shrink`
      is still 1, so nothing overflows at 320px.
   2. THE ACTION SLOT MUST BE ABLE TO WRAP ITS OWN CONTROLS. `flex: 0 0 auto` +
      `display: flex` with no wrap means a slot holding a SET cannot narrow at all.
      Measured on the dedup queue (Accept (merge) + Not the same = 329px): the slot ran
      to 346px inside a 254px card at 320px — a real horizontal overflow of the document,
      not merely an ugly line. Wrapping inside the slot, capped at the card's width, is
      what makes a multi-action card list survive a 320px screen. */
.kl-cardlist .kl-cardline > .kl-tcard-title { flex-basis: 6rem; }
.kl-cardlist .kl-cardline > .kl-tcard-actions { flex-wrap: wrap; max-inline-size: 100%; }

/* A6 ON A TITLE THAT IS ITSELF TAPPABLE. Section 8b-n gives the title no touch floor,
   which is right for a plain text title — but the involvement panel's title IS the
   record drill-in anchor, a standalone control that measured 20px on the live harness
   at 393px, well under the WCAG 2.5.8 line this repo holds. Restoring the floor is FREE
   whenever an action slot shares the line (8b-n already holds that line at 48px), and
   A6 says the trade is not on offer anyway. Scoped to `.kl-cardlist` so it cannot
   disturb the off-table cards 8b-n owns.

   DELIBERATELY NOT APPLIED TO LINKS *INSIDE* A TITLE. The dedup queue's title is the
   phrase "ANDERSON, ALICE M ~ ANDERSEN, ALICE", two links reading as one identity —
   WCAG 2.5.8's own INLINE exception (a target in a sentence or block of text) covers
   exactly that, and forcing each to 48px measured a 232px title line and a 258px card
   at 393px, because each name then wrapped as a 48px block. A rule that makes a card
   two and a half times taller to satisfy an exception the standard grants is worse for
   the thumb, not better: it pushes the next row off the screen. */
.kl-cardlist .kl-cardline .kl-tcard-title:is(a, button, [role="button"]) {
  display: inline-flex; align-items: center;
  min-block-size: var(--k-touch);
}

/* A body field — its own line under the title line. */
.kl-cardlist .kl-cardlist-body {
  min-inline-size: 0;
  font-size: 13px; color: var(--k-text-muted); overflow-wrap: anywhere;
}
/* Related chips sharing ONE row (section B2) rather than stacking one per line. */
.kl-cardlist .kl-cardlist-chips {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--k-space-2);
}
/* A field that must come LAST whatever its DOM order (A10 clause 7's foot line). */
.kl-cardlist .kl-cardlist-foot { order: 9; }

/* ── OPS-012 booking-conflict warning ────────────────────────────────────────────────────────────────────
   ONE family, shared by all three planning surfaces (Scheduler's selected-event pane, EventCreate, EditEvent)
   per the one-shared-CSS-surface ruling — a scoped .razor.css cannot cross components, and three copies of a
   warning treatment is exactly how two of them end up looking different.

   It is styled as a WARNING, not a critical error: the conflict never blocks a write (see
   ScheduleConflictDetector), so it must not wear the refusal colour. Loud enough that a planner cannot scroll
   past it, quiet enough that it does not read as a failure. */
.kl-conflict-warning {
  margin: var(--k-space-3) 0 0 0;
  padding: var(--k-space-3);
  border: 1px solid var(--k-border);
  border-inline-start-width: 4px;
  border-radius: var(--k-radius-md, 6px);
  background: var(--k-surface-2);
  color: var(--k-text);
}

.kl-conflict-lede {
  margin: 0 0 var(--k-space-2) 0;
  font-weight: 600;
}

.kl-conflict-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--k-space-1);
}

.kl-conflict-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--k-space-2);
  min-inline-size: 0;
  font-size: 13px;
}

.kl-conflict-kind {
  font-weight: 600;
  white-space: nowrap;
}

.kl-conflict-what {
  min-inline-size: 0;
  overflow-wrap: anywhere;
}

/* The honest caveats (no roster yet / detection unavailable) are notes, not alarms. */
.kl-conflict-note,
.kl-conflict-unavailable {
  margin: var(--k-space-2) 0 0 0;
  font-size: 12px;
  color: var(--k-text-muted);
}

/* A day cell the sergeant must be able to spot WITHOUT opening anything. */
.kl-scheduler-cell--conflict {
  box-shadow: inset 0 0 0 2px var(--k-border);
}

.kl-scheduler-conflict-flag {
  position: absolute;
  inset-block-start: 2px;
  inset-inline-end: 4px;
  font-size: 11px;
  line-height: 1;
  font-weight: 700;
  color: var(--k-text);
}
