/* ==========================================================================
   components.css — REUSABLE UI COMPONENTS
   --------------------------------------------------------------------------
   SCAFFOLD — pre-design-system. Phase-1 owner: P1-04 (component library),
   built against the frozen inventory in the P1-02 blueprint. P1-03 supplies
   the real token values this file already reads through var().

   Phase 0 adds ONLY the components this prompt itself needs: the three
   feedback banners and the home link. No component is invented ahead of its
   phase — an empty file would be more honest than speculative markup, and the
   P1-02 state grammar (loading skeletons, empty states, save-failure banners)
   is explicitly NOT scaffolded here: Phase 1 must be free to design it without
   fighting a Phase-0 guess.

   Every value below is a var() from design-system.css. Nothing hardcodes a
   colour twice — that is the rule that makes the Phase-1 restyle a values-only
   change.
   ========================================================================== */

/* --- Feedback banners -----------------------------------------------------
   The repo-wide feedback pattern: routes pass `success` / `error` / `warning`
   as template variables and base.html renders them. Flashed messages are
   forbidden (CLAUDE.md; check_flash_messages). Rendered inside <main>, above
   the page content, so the message sits with what it is about.

   role="status" (polite) for success/warning; role="alert" (assertive) for
   error — set in the template, since it is semantics, not style. */
/* No border-COLOUR here: each variant below sets its own, and naming a colour
   (even `transparent`) would put a literal outside design-system.css and break
   the one rule that makes the P1-03 restyle a values-only change. Omitting it
   leaves border-color at its initial value, currentColor — which each variant
   overrides anyway. */
.banner {
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
  border-width: var(--border-width);
  border-style: solid;
  border-radius: var(--radius);
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
}

.banner-success {
  background: var(--color-success-bg);
  border-color: var(--color-success-ink);
  color: var(--color-success-ink);
}

.banner-error {
  background: var(--color-error-bg);
  border-color: var(--color-error-ink);
  color: var(--color-error-ink);
}

.banner-warning {
  background: var(--color-warning-bg);
  border-color: var(--color-warning-ink);
  color: var(--color-warning-ink);
}

/* --- Home link ------------------------------------------------------------
   The "way back home" on every error page (P1-02 blueprint, line 310). It is a
   primary action, so it honours the primary touch floor (>= 36px) — met by
   min-height plus padding, giving a tappable area larger than the text itself.
   inline-flex + align-items centres the label within that box, so the target
   is genuinely 36px tall rather than a 36px box with the text at the top. */
.link-home {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target-primary);
  padding: var(--space-2) var(--space-3);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius);
  color: var(--color-ink);
  font-weight: var(--weight-bold);
  text-decoration: none;
  transition: background var(--transition-quick);
}

.link-home:hover,
.link-home:focus {
  background: var(--color-surface);
}

/* --- Forms ----------------------------------------------------------------
   Added by P0-05 (auth) — a DECLARED scope deviation, approved by Charles:
   the prompt's Visual Spec requires a >= 36px tap floor on inputs and submit
   buttons, and there was no way to meet it without form styles, because none
   existed. The Scope Lock fenced the CSS files; this block is the agreed,
   reported exception rather than a silent one.

   The class names are GENERIC ON PURPOSE — .form-input, never .login-input.
   Auth happens to be the first surface in the repo with a form, but it has no
   claim on what a form looks like. Every later form (the public application
   form, the reference form, questionnaires, the manager day-board's editable
   tiles) and P1-04's real component library build on exactly these names, so
   an auth-specific name here would guarantee a re-template later.

   Every value is a var() from design-system.css — no new colours, no new
   tokens, no new file. That is what makes P1-03/P1-04's restyle a values-only
   change to the tokens, with this file untouched. */
.form-field {
  margin-bottom: var(--space-3);
}

/* Label ABOVE the input, never beside it: at 390px there is no room for a
   side-by-side label, and a placeholder is not a label (it vanishes the moment
   someone starts typing, which is exactly when they need it, and it fails
   contrast requirements besides). */
.form-label {
  display: block;
  margin-bottom: var(--space-1);
  color: var(--color-ink);
  font-weight: var(--weight-bold);
}

.form-input {
  display: block;
  width: 100%;
  /* The primary touch floor. min-height rather than a fixed height so the box
     grows if the text ever needs to, and the padding makes the tappable area
     larger than the glyphs inside it. */
  min-height: var(--touch-target-primary);
  padding: var(--space-2);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-input);   /* was --radius (14) — align to its form siblings (12), P1-04 rhythm */
  /* D116 ruling 6 / critic C1: was --color-bg — identical to the shell--auth
     page background, so a field rendered as a borderless void ("ghost
     inputs", login-390-dark.png). --color-surface gives every form input in
     the repo a real, distinct fill (bg->surface delta +11.1/255, clears the
     floor) — a login-specific class was deliberately avoided (Prereq 0.15:
     .form-input is generic on purpose) so this fixes every surface at once. */
  background: var(--color-surface);
  color: var(--color-ink);
  font-family: var(--font-body);
  /* 16px is not a style choice here — iOS Safari ZOOMS THE PAGE on focus for any
     input under 16px, which on a mobile-first staff app means the login form
     lurches sideways the instant someone taps it. --font-size-body is 16px. */
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}

.form-input:focus {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 1px;
  border-color: var(--color-accent);
}

.form-hint {
  margin-top: var(--space-1);
  color: var(--color-muted);
  font-size: var(--font-size-small);
}

/* --- Buttons (P1-04 §3.1; D108 — Flame is accent-only, never a text-bearing fill) -------
   .btn base carries shape + the 36px primary touch floor. Variants carry colour ONLY.
   No border-COLOUR / background literal here — omit to keep the value at its initial
   (transparent / currentColor) rather than name it (DL04: even `transparent` is a literal). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--touch-target-primary);
  padding: var(--space-2) var(--space-4);
  /* appearance:none strips native control chrome (the OS button bevel/fill) — a
     styled control must own its whole look or the platform leaks through (DL09).
     appearance:none does NOT clear the UA `background-color: buttonface`, so a
     no-fill button (ghost/danger) leaks a grey ~rgb(107,107,107); `initial` resets
     it to transparent WITHOUT naming a colour (DL04 bans `transparent` literally).
     Filled variants below override this with their token. */
  appearance: none;
  -webkit-appearance: none;
  background-color: initial;
  border-width: var(--border-width);
  border-style: solid;
  border-radius: var(--radius-button);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  font-weight: var(--weight-bold);
  line-height: var(--line-height-body);
  cursor: pointer;
  transition: background var(--transition-quick), border-color var(--transition-quick),
              color var(--transition-quick), box-shadow var(--transition-quick),
              transform var(--transition-quick);
}

.btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Tactile press — the button dips a hair on activation (quiet, 200ms). Guarded so
   a disabled control never moves. Reset on release via the base transition. */
.btn:not(:disabled):not(.is-disabled):active {
  transform: translateY(1px);
}

.btn:disabled,
.btn.is-disabled {
  background: var(--color-disabled-bg);
  border-color: var(--color-disabled-bg);
  color: var(--color-muted);
  cursor: not-allowed;
  box-shadow: none;
}

/* Every :hover/:active is guarded with :not(:disabled):not(.is-disabled) so a disabled
   button never picks up hover elevation/fill (they share specificity with :disabled;
   the guard makes the disabled rule win regardless of source order, and keeps the
   not-allowed cursor rather than killing pointer-events). */

/* Primary — D108: neutral ink fill, bg label. AAA both themes (17.44 / 17.24).
   D116 ruling 1/2: the old hover (box-shadow: var(--shadow-md)) is a black
   shadow on a near-black page — invisible (critic C4/D1, "the primary CTA has
   no visible hover at all"). Fixed with an INSET FLAME RING — a UI line, never
   a text-bearing fill, so D108 ("accent means NOW, resting UI carries no
   Flame") stays intact: Flame appears ONLY on :hover/:focus, never at rest.
   Ring vs ink fill clears 3:1 in both themes (dark 3.65:1, light 4.48:1 —
   verified against the fill it sits on, DL08 discipline). :active gets a real
   fill-darken via --overlay-press (ink has no headroom to "brighten" further
   on dark — it is already near-white — so touch/:active uses a translucent
   black scrim instead, the only channel a touchscreen has). */
.btn-primary {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-bg);
}
.btn-primary:not(:disabled):not(.is-disabled):hover {
  box-shadow: inset 0 0 0 2px var(--color-accent);
}
.btn-primary:not(:disabled):not(.is-disabled):active {
  box-shadow: inset 0 0 0 999px var(--overlay-press);
}

/* Ghost / secondary — no fill (background omitted = transparent), line border, ink label.
   D116 ruling 1: hover now clears the retuned --color-hover floor (surface->hover
   +14.7/255, was +7.1) AND brightens the border to --color-line-strong, so a
   ghost button reads as responding on two channels, not one faint tint. */
.btn-ghost {
  border-color: var(--color-line);
  color: var(--color-ink);
}
.btn-ghost:not(:disabled):not(.is-disabled):hover {
  background: var(--color-hover);
  border-color: var(--color-line-strong);
}

/* Danger / destructive — Flame OUTLINE + red-tint label (no text on a Flame fill).
   accent-text on surface/bg is AA (6.33 dark / 5.99 light); Flame border clears 3:1 UI.
   Hover re-verified against the RETUNED --color-hover (DL08: a token retune is
   a new pairing) — accent-text dark 5.38:1 / light 5.09:1 on it, both still AA. */
.btn-danger {
  border-color: var(--color-accent);
  color: var(--color-accent-text);
}
.btn-danger:not(:disabled):not(.is-disabled):hover {
  background: var(--color-hover);
}

/* Sizes — compact never carries white-on-Flame (moot: no variant does). */
.btn-sm {
  min-height: var(--touch-target-compact);
  padding: var(--space-1) var(--space-3);
  font-size: var(--font-size-small);
}
.btn-lg {
  padding: var(--space-3) var(--space-5);
  font-size: var(--font-size-lg);
}

.btn-block {
  display: flex;
  width: 100%;
}

/* FAB — circular compose (Feed). 40px = --space-5. Neutral fill, bg icon. */
.btn-fab {
  min-height: var(--space-5);
  width: var(--space-5);
  padding: 0;
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-bg);
}
.btn-fab:not(:disabled):not(.is-disabled):hover {   /* pointer feedback — mirrors btn-primary's ring (D116) */
  box-shadow: inset 0 0 0 2px var(--color-accent);
}
.btn-fab:not(:disabled):not(.is-disabled):active {
  box-shadow: inset 0 0 0 999px var(--overlay-press);
}

/* Loading — label hidden (visibility, NOT color:transparent — that is a literal), width held. */
.btn.is-loading .btn__label {
  visibility: hidden;
}
.btn.is-loading {
  position: relative;
  pointer-events: none;
}

/* Secondary text under a form — "Forgotten your password?", "Back to sign in". */
.form-aside {
  margin-top: var(--space-3);
  font-size: var(--font-size-small);
}

.form-link {
  color: var(--color-body);
}

/* --- Form controls (P1-04 §3.2) — extends the P0-05 .form-* base. --------- */
.form-input:focus,
.form-select:focus,
.form-textarea:focus,
.form-search:focus {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 1px;
  border-color: var(--color-accent);
}

/* Select — appearance:none strips the native dropdown chrome (the OS caret + grey
   frame — a 2010 tell, DL09); a token-coloured caret is drawn by the wrapper span,
   so the arrow follows the theme instead of the platform. */
.form-select-wrap {
  position: relative;
  display: block;
}
.form-select-wrap__caret {
  position: absolute;
  right: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-muted);
  font-size: var(--font-size-small);
  pointer-events: none;
}
.form-select {
  display: block;
  width: 100%;
  min-height: var(--touch-target-primary);
  padding: var(--space-2);
  padding-right: var(--space-5);      /* room for the caret */
  appearance: none;
  -webkit-appearance: none;
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-input);
  background: var(--color-surface);     /* D116 ruling 6 — see .form-input's comment */
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
}
.form-select::-ms-expand {            /* legacy Edge native caret */
  display: none;
}

.form-textarea {
  display: block;
  width: 100%;
  min-height: calc(var(--touch-target-primary) * 2);
  padding: var(--space-2);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-input);
  background: var(--color-surface);     /* D116 ruling 6 — see .form-input's comment */
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  resize: vertical;
}

/* Native checkbox/radio — checked = GREEN (--color-done), the system's "on/affirmative"
   signal, NOT Flame. Flame stays reserved for urgent/NOW (R01); "selected" chips read
   in ink; "on/checked" form controls read in green. Checkmark is graphical (3:1). */
.form-checkbox,
.form-radio {
  width: var(--space-3);
  height: var(--space-3);
  accent-color: var(--color-done);
}

/* A selectable row wrapping a checkbox/radio + label — the >=32px tappable line. */
.form-choice {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target-row);
  color: var(--color-ink);
}

/* Toggle switch — track + knob. State is shown by KNOB POSITION (not colour alone,
   WCAG 1.4.1). Off-track = --color-muted so the control is visible vs the page (3:1
   UI floor: line-on-bg is only ~1.3:1, invisible — muted is 5.38/4.99). On = green (--color-done).
   Knob = --color-ink so it reads on both track states. Markup: <label class="form-toggle">
   <input type="checkbox">  <span class="form-toggle__track"></span>
   <span class="form-toggle__knob"></span></label> — input FIRST (the ~ sibling selectors
   below depend on DOM order). */
.form-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: calc(var(--space-5) + var(--space-1));
  min-height: var(--touch-target-compact);
  cursor: pointer;
}
.form-toggle input {                  /* the real checkbox — invisible but focusable/clickable */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.form-toggle__track {
  width: 100%;
  height: var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--color-muted);
  transition: background var(--transition-quick);
}
.form-toggle__knob {
  position: absolute;
  left: var(--space-1);
  top: 50%;
  margin-top: calc(var(--space-2) / -2);   /* vertical centre (knob is --space-2 tall) */
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  transition: transform var(--transition-quick);
}
.form-toggle input:checked ~ .form-toggle__track {
  background: var(--color-done);        /* On = green (see .form-checkbox note) — knob (ink) contrasts on it */
}
.form-toggle input:checked ~ .form-toggle__knob {
  transform: translateX(var(--space-4));
}
.form-toggle input:focus-visible ~ .form-toggle__track {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
/* Disabled — after :checked so a disabled-on toggle reads disabled, not accent. */
.form-toggle input:disabled {
  cursor: not-allowed;
}
.form-toggle input:disabled ~ .form-toggle__track {
  background: var(--color-disabled-bg);
}
.form-toggle input:disabled ~ .form-toggle__knob {
  background: var(--color-muted);
}

/* Quantity stepper — two btn-sm ghosts + a number field. */
.form-stepper {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.form-stepper__value {
  min-width: var(--space-5);
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--color-ink);
}

/* Search — leading glyph via padding + an absolutely-placed glyph span in markup. */
.form-search-wrap {
  position: relative;
}
.form-search {
  display: block;
  width: 100%;
  min-height: var(--touch-target-primary);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-5);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-input);
  background: var(--color-surface);     /* D116 ruling 6 — see .form-input's comment */
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
}
.form-search-wrap__icon {
  position: absolute;
  left: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-muted);
  pointer-events: none;
}

/* Input variants. */
.form-input--currency {
  padding-left: var(--space-4);       /* room for the £ prefix span */
}
.form-currency-wrap {
  position: relative;
}
.form-currency-wrap__symbol {
  position: absolute;
  left: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-muted);
  pointer-events: none;
}
.form-input--pin {
  letter-spacing: 0.4em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Validation — inline, per field, at submit. */
.form-error {
  margin-top: var(--space-1);
  color: var(--color-accent-text);
  font-size: var(--font-size-small);
}
.field--invalid .form-input,
.field--invalid .form-select,
.field--invalid .form-textarea {
  border-color: var(--color-accent);
}

/* Disabled — any control. */
.form-input:disabled,
.form-select:disabled,
.form-textarea:disabled,
.form-search:disabled {
  background: var(--color-disabled-bg);
  color: var(--color-muted);
  cursor: not-allowed;
}

/* --- Admin data set (P1-04 §3.3) — the G1-a data gate. -------------------- */

/* Table — component supplies grid + row states; each surface sets its own
   grid-template-columns in its {% block extra_css %} (inline style is banned). */
.data-table {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.table-head,
.table-row {
  display: grid;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target-row);
  padding: var(--space-2) var(--space-3);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-input);
  background: var(--color-surface);
  transition: background var(--transition-quick), border-color var(--transition-quick);
}
.table-head {
  background: var(--color-bg);
  color: var(--color-muted);
  font-size: var(--font-size-small);
  font-weight: var(--weight-bold);
  min-height: auto;
}
.table-row:hover {
  background: var(--color-hover);
}
.table-row--now {                     /* attention / current — 2px Flame border (the "NOW" language) */
  border-width: 2px;
  border-color: var(--color-accent);
}
.table-row--dim {                     /* archived / inactive / not-yet-active */
  color: var(--color-muted);
}
/* D116 ruling 1: the retuned --color-hover clears the perceptibility floor,
   but muted-on-hover is only 4.20:1 (design-system.css token comment) — a dim
   row's own text is --color-muted, so hovering it would drop below 4.5
   small-text AA for the duration of the hover. Bump to --color-body only
   while hovered; the resting dim look is unchanged. Guarded with the DL12
   shape (Codex round 1) for consistency with the rest of the file, even
   though no `.table-row.is-disabled`/`:disabled` variant exists today — the
   guard costs nothing and holds if one is ever added. */
.table-row--dim:not(:disabled):not(.is-disabled):hover {
  color: var(--color-body);
}
/* Flagged / problem — a quiet danger WASH, not a second Flame border. This keeps
   the Flame border unique to "now" (the two used to be near-identical), and reads
   as a problem row without shouting. Text stays AA on the wash (verified). */
.table-row--flagged {
  background: var(--color-error-bg);
  border-color: var(--color-error-ink);
  font-weight: var(--weight-bold);
}
.table-row--flagged:hover {
  background: var(--color-error-bg);   /* hold the wash on hover (don't fall back to neutral) */
}
.table-group-head {                   /* role/section sub-header between row groups */
  padding: var(--space-2) var(--space-3) var(--space-1);
  color: var(--color-muted);
  font-size: var(--font-size-caption);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Chip / status-pill — pill; text-bearing; semantic variants reuse feedback tokens.
   .status-pill shares the base (an alias for a status-column chip). */
.chip,
.status-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--touch-target-compact);   /* the frozen 28px chip floor (manifest §3.3) */
  padding: var(--space-1) var(--space-3);     /* a touch more side padding — pills breathe */
  appearance: none;                           /* chips can be <button>; own the fill (DL09) */
  -webkit-appearance: none;
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-body);
  font-size: var(--font-size-small);
  font-weight: var(--weight-bold);
  line-height: var(--line-height-tight);
  transition: background var(--transition-quick), border-color var(--transition-quick),
              color var(--transition-quick);
}
.chip.on {                            /* selected / "Yes" — neutral ink fill (AAA) */
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-bg);
}
.chip--success, .status-pill--success {   /* done / resolved / confirmed (green) */
  background: var(--color-success-bg);
  border-color: var(--color-success-ink);
  color: var(--color-success-ink);
}
.chip--warning, .status-pill--warning {   /* open / awaiting / in-progress (amber) */
  background: var(--color-warning-bg);
  border-color: var(--color-warning-ink);
  color: var(--color-warning-ink);
}
.chip--danger, .status-pill--danger {     /* flagged / overdue / error (red-tint) */
  background: var(--color-error-bg);
  border-color: var(--color-error-ink);
  color: var(--color-error-ink);
}
.chip--neutral, .status-pill--neutral {   /* info / skipped / inactive */
  background: var(--color-surface);
  border-color: var(--color-line);
  color: var(--color-muted);
}
.chip--action {                       /* tappable row action (edit / view / triage) */
  min-height: var(--touch-target-compact);
  background: var(--color-surface);
  border-color: var(--color-line);
  color: var(--color-ink);
  cursor: pointer;
}
.chip--action:not(:disabled):not(.is-disabled):hover {   /* a disabled action chip must not light up (native :disabled OR the class) */
  background: var(--color-hover);
}
.chip.is-disabled,
.chip:disabled {
  color: var(--color-muted);
  cursor: not-allowed;
}
/* Branded focus ring for chips-as-buttons (action / day-picker) — the rest of the
   library rings; these used to fall back to the UA default only. Harmless on span
   chips (:focus-visible never fires on non-focusable elements). */
.chip:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Count badge — neutral fill (AAA). A Flame count badge would fail AA; use neutral. */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--space-3);
  height: var(--space-3);
  padding: 0 var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  color: var(--color-bg);
  font-size: var(--font-size-caption);
  font-weight: var(--weight-bold);
  line-height: 1;
}

/* Filter / facet chip — segmented control; selected = ink fill. */
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--touch-target-compact);
  padding: var(--space-1) var(--space-3);
  appearance: none;
  -webkit-appearance: none;
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-ink);
  font-size: var(--font-size-small);
  font-weight: var(--weight-bold);
  cursor: pointer;
  transition: background var(--transition-quick), border-color var(--transition-quick),
              color var(--transition-quick);
}
.filter-chip:not(:disabled):not(.is-disabled):hover:not(.on) {
  background: var(--color-hover);
}
.filter-chip.on {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-bg);
}
.filter-chip:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
.filter-chip:disabled,
.filter-chip.is-disabled {
  color: var(--color-muted);
  cursor: not-allowed;
}

/* Day-of-week picker — a row of toggle chips (task drawer, sessions, availability). */
.day-picker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.day-picker .chip {
  min-height: var(--touch-target-compact);
  cursor: pointer;
}

/* --- Containers & overlays (P1-04 §3.4) — the G1-a container gate. -------- */

/* Card — the workhorse container. D116 ruling 3: rides --color-raised, not
   --color-surface — a card sitting directly on the page needs to read as a
   LIFTED plane (critic C5, "cards barely separate from the page"); shadow-sm
   is kept for the light theme (where shadows read) but on dark the tint IS
   the lift. Any --color-muted caption a .card-family component carries has
   been re-verified/bumped against --color-raised (see design-system.css's
   token comment) — .feed-card__timestamp below is the one this prompt found. */
.card {
  background: var(--color-raised);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-card);
  padding: var(--space-3);
  box-shadow: var(--shadow-sm);
}
.card__title {
  margin: 0 0 var(--space-2);
  color: var(--color-ink);
  font-size: var(--font-size-lg);
  font-weight: var(--weight-bold);
}
.card--now {                          /* attention / NOW — 2px Flame border (D108-safe) */
  border-width: 2px;
  border-color: var(--color-accent);
}
/* --color-body, not --color-muted (Codex round 1) — .card now rides
   --color-raised at rest (D116 ruling 3); muted-on-raised is 4.31:1, below
   4.5 small-text AA (design-system.css token comment). Same fix class as
   .stat-tile__label / .feed-card__timestamp. */
.card--dim {                          /* later / disabled stage */
  color: var(--color-body);
  box-shadow: none;
}
.card--hint {                         /* meta / empty / first-run — dashed, blends to page */
  background: var(--color-bg);
  border-style: dashed;
  box-shadow: none;
}

/* Drawer — Pattern A: scrim + off-canvas panel. role=dialog + aria-modal in markup. */
.drawer-backdrop {
  position: fixed;
  inset: 0;
  background: var(--color-overlay);
}
.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(360px, 100%);
  padding: var(--space-4);
  background: var(--color-raised);      /* D116 ruling 3 — a lifted overlay panel */
  box-shadow: var(--shadow-md);
  overflow-y: auto;
}

/* Edit-card — Pattern B: in-place expanding editor beneath a row. NOT a slide-over. */
.edit-card {
  margin-top: var(--space-2);
  padding: var(--space-3);
  background: var(--color-raised);      /* D116 ruling 3 */
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-sm);
}

/* Modal — centred confirm/dialog. `display` is applied ONLY via :not([hidden])
   — a bare `display: flex` here has AUTHOR-origin priority over the `[hidden]`
   attribute's USER-AGENT-origin `display: none` regardless of selector
   specificity (CSS cascade origin order beats specificity), so the modal would
   otherwise render permanently open even while `hidden` is set. Found and
   fixed P1-05 (render-before-trust, DL06 — getComputedStyle on the actual
   render, not source) as a pre-existing P1-04 defect; the sibling
   .urgent-sheet__backdrop below carries the same guard from day one. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  align-items: center;
  justify-content: center;
  padding: var(--space-3);
  background: var(--color-overlay);
}
.modal-backdrop:not([hidden]) {
  display: flex;
}
.modal {
  width: min(480px, 100%);
  padding: var(--space-4);
  background: var(--color-raised);      /* D116 ruling 3 */
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-md);
}

/* Expander — disclosure (<details>-based). */
.expander {
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-input);
  background: var(--color-surface);
}
.expander__summary {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target-row);
  padding: var(--space-2) var(--space-3);
  color: var(--color-ink);
  font-weight: var(--weight-bold);
  cursor: pointer;
  list-style: none;                   /* drop the native <summary> disclosure triangle */
  transition: background var(--transition-quick);
}
.expander__summary::-webkit-details-marker {   /* …and its WebKit twin */
  display: none;
}
.expander__summary:hover {
  background: var(--color-hover);
}
.expander__summary:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: -2px;               /* inset so the ring stays inside the rounded box */
}
.expander[open] .expander__summary {
  border-bottom: var(--border-width) solid var(--color-line);
}
/* Disclosure caret — rotates 180° on open (a polished disclosure animates). */
.expander__caret {
  display: inline-flex;
  transition: transform var(--transition-quick);
}
.expander[open] .expander__caret {
  transform: rotate(180deg);
}
.expander__body {
  padding: var(--space-3);
}

/* Confirm-panel — server-detected guarded edit ("retire instead", "has history"). */
.confirm-panel {
  padding: var(--space-3);
  background: var(--color-warning-bg);
  border: var(--border-width) solid var(--color-warning-ink);
  border-radius: var(--radius-card);
  color: var(--color-warning-ink);
}

/* Side-panel — persistent detail pane (desktop side-by-side; stacks per-surface at 390px). */
.side-panel {
  padding: var(--space-3);
  background: var(--color-raised);      /* D116 ruling 3 */
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-card);
}

/* --- Navigation, feedback & loading (P1-04 §3.5, first group) ------------- */

/* Staff bottom tab bar — styled here; PLACED by P1-06.
   Refined (P1-04 design pass): real nav height (not the 36px tap-min); each tab
   stacks a ~20px icon over a caption label. Active is NOT a faint detached top
   hairline — it is a seated indicator near the thumb: a quiet neutral pill behind
   the icon + the icon tinted Flame (the one brand moment; icon = graphical, ≥3:1
   verified, so D108/DL08 hold — no text ever sits on a Flame fill) + an ink,
   bold label. Notifications ride the icon's corner, never a third stacked line. */
.tab-bar {
  display: flex;
  border-top: var(--border-width) solid var(--color-line);
  background: var(--color-surface);
}
.tab {
  flex: 1;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--nav-height);
  padding: var(--space-1) var(--space-1) var(--space-2);
  color: var(--color-muted);
  font-size: var(--font-size-caption);
  line-height: var(--line-height-tight);
  text-decoration: none;
  transition: color var(--transition-quick);
}
.tab:not(.on):hover {                 /* desktop pointer feedback */
  color: var(--color-body);
}
/* Icon capsule — also the seated active indicator and the badge anchor. */
.tab__icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: calc(var(--space-5) + var(--space-2));   /* 48px pill */
  height: var(--space-4);                              /* 24px pill */
  border-radius: var(--radius-pill);
  font-size: var(--font-size-lg);
  line-height: 1;
  transition: background var(--transition-quick), color var(--transition-quick);
}
.tab.on {
  color: var(--color-ink);
  font-weight: var(--weight-bold);
}
.tab.on .tab__icon {
  background: var(--color-hover);     /* quiet neutral seat (one step off surface) */
  color: var(--color-accent);         /* the brand tint — icon only, never text-on-fill */
}
.tab:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: -2px;               /* inset so the ring never clips at the screen edge */
}
/* Count badge on a tab — rides the icon's top-right corner (compose with .badge). */
.tab__icon .badge {
  position: absolute;
  top: calc(var(--space-1) * -1);
  left: calc(100% - var(--space-3));
}
/* Unread dot — attention without a number (Flame; a small graphical mark). */
.tab__dot {
  position: absolute;
  top: 0;
  left: calc(100% - var(--space-3));
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  border: 2px solid var(--color-surface);   /* separates the dot from the icon */
}

/* In-page tabs (admin). appearance:none + border:0 strip native <button> chrome
   (the OS grey bevel that used to leak here — DL09); only the underline remains. */
.tabs {
  display: flex;
  gap: var(--space-4);
  border-bottom: var(--border-width) solid var(--color-line);
}
.tab-item {
  appearance: none;
  -webkit-appearance: none;
  background-color: initial;          /* clear the UA buttonface grey (DL09/DL04) */
  min-height: var(--touch-target-row);
  padding: var(--space-2) var(--space-1);
  border: 0;
  border-bottom: 2px solid var(--color-line);
  color: var(--color-muted);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  font-weight: var(--weight-bold);
  cursor: pointer;
  transition: color var(--transition-quick), border-color var(--transition-quick);
}
/* D116 ruling 1: "tabs" are explicitly named among the surfaces the
   perceptibility floor covers. The pre-existing text-colour shift alone
   (muted->body) is a large, real delta, but the floor is stated in terms of
   a background/border channel — added a border-brighten (line-strong) so
   the hover also clears it on that literal channel, not just via text. */
.tab-item:not(:disabled):not(.is-disabled):not(.on):hover {
  border-bottom-color: var(--color-line-strong);
  color: var(--color-body);
}
/* D116 ruling 5: the unified ink-edge selection idiom — was --color-accent
   (Flame), a routine "selected" state, not a NOW moment (ruling 2: Flame at
   rest is zero). No border-radius on .tab-item, so the plain border-bottom
   mechanism carries no corner-hook risk (Step 7 sweep is clean here). */
.tab-item.on {
  border-bottom-color: var(--color-ink);
  color: var(--color-ink);
}
.tab-item:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
.tab-item:disabled,
.tab-item.is-disabled {
  color: var(--color-muted);
  cursor: not-allowed;
}

/* Toast — transient feedback; reuses the banner token trio in a positioned region. */
.toast-region {
  position: fixed;
  left: 50%;
  bottom: var(--space-4);
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: min(360px, calc(100% - var(--space-4)));
}
.toast {
  padding: var(--space-2) var(--space-3);
  border-width: var(--border-width);
  border-style: solid;
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  font-size: var(--font-size-small);
}
.toast-success { background: var(--color-success-bg); border-color: var(--color-success-ink); color: var(--color-success-ink); }
.toast-error   { background: var(--color-error-bg);   border-color: var(--color-error-ink);   color: var(--color-error-ink); }
.toast-warning { background: var(--color-warning-bg); border-color: var(--color-warning-ink); color: var(--color-warning-ink); }

/* Flagbar — the neutral ATTENTION banner (setup-incomplete, save-failure retry,
   variance flag). Distinct from success/error/warning: a 2px accent border + bold
   ink on surface (the "NOW = 2px Flame border" language, applied to a banner). */
.flagbar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 2px solid var(--color-accent);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-ink);
  font-size: var(--font-size-small);
  font-weight: var(--weight-bold);
}

/* Empty state — designed first-run/empty block (dashed, blends to page). */
.empty-state {
  padding: var(--space-5) var(--space-3);
  text-align: center;
  background: var(--color-bg);
  border: var(--border-width) dashed var(--color-line);
  border-radius: var(--radius-card);
  color: var(--color-muted);
}

/* Skeleton — loading placeholder. Opacity pulse (no colour animation). Width via
   MODIFIER CLASSES (the blueprint's .w40/.w60/.w80), never inline style=. */
.skeleton {
  background: var(--color-skeleton);
  border-radius: var(--radius-input);
  animation: sg-pulse 1.4s ease-in-out infinite;
}
.skeleton--text { height: var(--font-size-body); }   /* a text-line bar */
.skeleton--w40 { width: 40%; }
.skeleton--w60 { width: 60%; }
.skeleton--w80 { width: 80%; }
@keyframes sg-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }

/* Spinner — inline; slots into .btn.is-loading (04-01 hook). */
.spinner {
  display: inline-block;
  width: var(--space-3);
  height: var(--space-3);
  border: 2px solid var(--color-line);
  border-top-color: var(--color-accent);
  border-radius: var(--radius-pill);
  animation: sg-spin 0.6s linear infinite;
}
@keyframes sg-spin { to { transform: rotate(360deg); } }
.btn.is-loading .spinner {
  /* centre via negative margins, NOT transform — the sg-spin keyframe owns transform */
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: calc(var(--space-3) / -2);
  margin-top: calc(var(--space-3) / -2);
}

@media (prefers-reduced-motion: reduce) {
  .skeleton, .spinner { animation: none; }
  /* A frozen ring + hidden label reads as nothing loading — so under reduced motion
     show the label and drop the (static) spinner instead. */
  .btn.is-loading .btn__label { visibility: visible; }
  .btn.is-loading .spinner { display: none; }
}

/* Progress — the native <progress> element so the value rides the value/max
   ATTRIBUTES (no inline style=, which is banned) and is announced to AT for free.
   Green fill = completing toward done. WCAG 1.4.11: fill(done) vs track(line) =
   4.29 dark / 3.91 light, fill vs page = 5.75 / 4.72 — the value is perceivable;
   the track is a quiet --color-line by intent. Markup:
   <progress class="progress" value="3" max="5"></progress> */
.progress {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: var(--space-1);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--color-line);        /* track (Firefox reads this) */
  overflow: hidden;
}
.progress::-webkit-progress-bar { background: var(--color-line); }
.progress::-webkit-progress-value { background: var(--color-done); }
.progress::-moz-progress-bar { background: var(--color-done); }
.progress__text {
  color: var(--color-muted);
  font-size: var(--font-size-small);
  font-variant-numeric: tabular-nums;
}

/* Avatar — initials circle. */
.avatar {
  /* muted fill so the circle is visible vs the page (muted-on-bg 5.38/4.99 >= 3);
     --color-bg initials are AA on it (5.38/4.99). A --color-line fill would be an
     invisible circle (line-on-bg ~1.3:1). */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-4);
  height: var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--color-muted);
  color: var(--color-bg);
  font-size: var(--font-size-caption);
  font-weight: var(--weight-bold);
}

/* Breadcrumb. */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  color: var(--color-muted);
  font-size: var(--font-size-small);
}
.breadcrumb a { color: var(--color-body); }

/* Tooltip — bubble on hover/focus of a wrapped control. ink bg / bg text (AAA). */
.tooltip { position: relative; display: inline-flex; }
.tooltip__bubble {
  position: absolute;
  bottom: calc(100% + var(--space-1));
  left: 50%;
  transform: translateX(-50%);
  padding: var(--space-1) var(--space-2);
  background: var(--color-ink);
  color: var(--color-bg);
  border-radius: var(--radius-input);
  font-size: var(--font-size-caption);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-quick);
}
.tooltip:hover .tooltip__bubble,
.tooltip:focus-within .tooltip__bubble { opacity: 1; }

/* Stat grid / KPI tile. */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}
.stat-tile {
  padding: var(--space-3);
  background: var(--color-raised);      /* D116 ruling 3 — a KPI tile is a lifted tile, critic C4 */
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-card);
  transition: background var(--transition-quick), border-color var(--transition-quick),
              box-shadow var(--transition-quick), transform var(--transition-quick);
}
.stat-tile__num {
  color: var(--color-ink);
  font-size: var(--font-size-display);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  line-height: var(--line-height-tight);
}
/* --color-body, not --color-muted — --color-raised is now this tile's REST
   background (not just a hover state), and muted-on-raised is 4.31:1
   (clears 3:1 non-text but not 4.5 small-text AA, design-system.css token
   comment). body is 9.08:1 on raised, comfortably AAA. */
.stat-tile__label {
  margin-top: var(--space-1);
  color: var(--color-body);
  font-size: var(--font-size-small);
}
.stat-tile--editable { cursor: pointer; }
.stat-tile--editable:not(:disabled):not(.is-disabled):hover {   /* D116 ruling 1: border-brighten + lift — shadow-md doesn't read on dark (critic C4) */
  border-color: var(--color-line-strong);
  transform: translateY(-1px);
}
.stat-tile--editable:focus-visible {   /* ring when the surface makes it a focusable control */
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* --- Specialised controls (P1-04 §3.5, second group) --------------------- */

/* Swipe-to-confirm — briefing/kiosk gate. Track >= 36px (frozen; briefings/03:69). */
.swipe-confirm {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--touch-target-primary);
  padding: var(--space-1);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: var(--font-size-small);
  transition: border-color var(--transition-quick), color var(--transition-quick);
}
.swipe-confirm:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
.swipe-confirm__knob {
  width: calc(var(--touch-target-primary) - var(--space-2));
  height: calc(var(--touch-target-primary) - var(--space-2));
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  color: var(--color-bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.swipe-confirm__label {
  flex: 1;
  text-align: center;
}
/* A spacer the width of the knob balances the flex row, so the label centres on
   the whole TRACK rather than only in the space to the right of the knob. */
.swipe-confirm::after {
  content: "";
  flex: 0 0 auto;
  width: calc(var(--touch-target-primary) - var(--space-2));
}
.swipe-confirm.is-confirmed {
  border-color: var(--color-done);
  color: var(--color-done);
}

/* PIN keypad — kiosk. 3-col numeric grid + dots. Large tap targets. */
.pin-keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
}
.pin-key {
  min-height: var(--space-5);
  padding: var(--space-3);
  appearance: none;
  -webkit-appearance: none;
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-input);
  background: var(--color-surface);
  color: var(--color-ink);
  font-size: var(--font-size-lg);
  font-weight: var(--weight-bold);
  cursor: pointer;
  transition: background var(--transition-quick), transform var(--transition-quick);
}
/* Guarded like .btn — a disabled key never lights up or presses (Codex R1). */
.pin-key:not(:disabled):not(.is-disabled):hover { background: var(--color-hover); }
.pin-key:not(:disabled):not(.is-disabled):active { transform: translateY(1px); }   /* tactile press for the kiosk keypad */
.pin-key:disabled,
.pin-key.is-disabled {
  color: var(--color-muted);
  cursor: not-allowed;
}
/* Confirm/enter key — `.pin-key` is defined after `.btn-primary` at equal specificity
   and would otherwise swallow the ink fill (the enter key looked like a number key).
   Two-class rules restore + hold the primary emphasis, incl. on hover. */
.pin-key.btn-primary:not(:disabled):not(.is-disabled) {
  background: var(--color-ink);       /* guarded so a disabled confirm key falls through to the
                                         disabled style below, not this ink fill (Codex R2) */
  border-color: var(--color-ink);
  color: var(--color-bg);
}
.pin-key.btn-primary:not(:disabled):not(.is-disabled):hover {
  background: var(--color-ink);       /* stay ink — don't wash to hover-tint */
  box-shadow: inset 0 0 0 2px var(--color-accent);   /* mirrors .btn-primary's ring (D116) */
}
.pin-key:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
.pin-dots {
  display: flex;
  gap: var(--space-2);
  justify-content: center;
}
.pin-dot {
  width: var(--space-2);
  height: var(--space-2);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-pill);
}
.pin-dot.is-filled {
  background: var(--color-ink);
  border-color: var(--color-ink);
}

/* Reorder — up/down icon-button pair (the frozen accessible reorder; inventory §4). */
.reorder {
  display: inline-flex;
  flex-direction: column;
  gap: var(--space-1);
}
.reorder__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-target-compact);
  min-width: var(--touch-target-compact);
  appearance: none;
  -webkit-appearance: none;
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-input);
  background: var(--color-surface);   /* own the fill so native chrome can't leak (DL09) */
  color: var(--color-ink);
  cursor: pointer;
  transition: background var(--transition-quick), border-color var(--transition-quick);
}
.reorder__btn:not(:disabled):hover { background: var(--color-hover); }
.reorder__btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
.reorder__btn:disabled {
  color: var(--color-muted);
  cursor: not-allowed;
}

/* Media / file — image/photo placeholder + upload affordance (visual only; presign is the feature's). */
.media-frame {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(var(--space-5) * 2);
  padding: var(--space-3);
  background: var(--color-surface);
  border: var(--border-width) dashed var(--color-line);
  border-radius: var(--radius-card);
  color: var(--color-muted);
  text-align: center;
}
.file-input {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target-primary);
  padding: var(--space-2) var(--space-3);
  border: var(--border-width) dashed var(--color-line);   /* dashed = "drop / choose", matches media-frame */
  border-radius: var(--radius-button);
  background: var(--color-surface);
  color: var(--color-ink);
  cursor: pointer;
  transition: background var(--transition-quick), border-color var(--transition-quick);
}
.file-input:hover {
  background: var(--color-hover);
  border-color: var(--color-muted);
}
/* The wrapped <input type=file> is visually hidden but KEPT IN THE TAB ORDER (not
   `hidden`/display:none, which makes the picker keyboard-unreachable — WCAG 2.1.1).
   Focusing it rings the label via :focus-within; Space/Enter opens the picker
   natively; a pointer click on the label forwards to it (DL09 sibling — a11y). */
.file-input input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.file-input:focus-within {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* --- Communications (P1-05) — the comms component inventory. ---------------
   Styleguide-only: 11 new components + 2 extensions (role-badge, empty-state
   variants). No comms routes/DB — this is the vocabulary Phase 5 builds the
   feed/threads/composer FEATURE from (comms-architecture.md §Component
   Inventory). Built entirely on the frozen P1-04 primitives — .card, .chip,
   .badge, .avatar, .empty-state, .swipe-confirm, .banner, .form-textarea,
   .file-input, .form-select-wrap — no primitive is redefined here.
   Runtime states (removed-by-moderation, sender-anonymised, live receipt
   counts, composer send/failure, kiosk-suppressed unread) are feature-driven
   (Phase 5) and are NOT CSS classes — this file styles mine/theirs, ack/unack,
   count/dot, the states a designer can see without a database. */

/* Feed card — extends .card. Author row: avatar + name + role-badge; an
   "edited" mark reuses .badge (text-bearing, neutral fill). Announcement
   variant carries an optional photo (.media-frame) and an ack-chip footer.
   Urgent-briefing variant composes with .card--now in markup (the frozen
   2px-Flame-border "NOW" idiom) rather than re-declaring the border here —
   one rule for "this is the attention state," not two. */
.feed-card {
  overflow: hidden;   /* clips .feed-card__media's corners to the card's own radius */
}
.feed-card--announcement {
  color: var(--color-body);   /* explicit base text colour for the routine/quiet variant */
}
.feed-card--urgent .feed-card__body {
  font-weight: var(--weight-bold);   /* an urgent briefing reads at a glance under time pressure */
}
.feed-card__header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}
.feed-card__author-info {
  flex: 1;
  min-width: 0;
}
.feed-card__author-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}
.feed-card__author-name {
  color: var(--color-ink);
  font-weight: var(--weight-bold);
}
/* --color-body, not --color-muted — feed-card extends .card, which now rides
   --color-raised at rest (D116 ruling 3); muted-on-raised does not clear
   4.5:1 small-text AA (design-system.css token comment). */
.feed-card__timestamp {
  display: block;
  margin-top: var(--space-1);
  color: var(--color-body);
  font-size: var(--font-size-caption);
}
.feed-card__pin {
  flex: 0 0 auto;
  color: var(--color-accent-text);   /* small-text-safe accent tint (never raw Flame) — decorative, aria-hidden */
  font-size: var(--font-size-lg);
  line-height: 1;
}
.feed-card__media {
  margin: var(--space-2) 0;
}
.feed-card__body {
  color: var(--color-body);
  line-height: var(--line-height-body);
}
.feed-card__footer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
/* Ops-data row (urgent briefing) — a quiet label/value line, hairline-separated. */
.feed-card__ops-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-1) 0;
  border-top: var(--border-width) solid var(--color-line);
  color: var(--color-body);
  font-size: var(--font-size-small);
}

/* Channel switcher — horizontal avatar row (All Gravy pattern 1). Active tab
   carries a 2px ink underline — the unified selection idiom (D116 ruling 5),
   not Flame: a channel switch is routine, not urgent (ruling 2 supersedes
   this block's original "accent as a UI line" framing). At rest the border is
   STYLE:none — the DL04-safe way to omit a colour is to not draw the line at
   all, never to name a literal keyword for "invisible". appearance:none +
   background-color:initial clears native <button> chrome (DL09) since this is
   a real button, not a link. */
.channel-switcher {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-1);
  overflow-x: auto;
}
.channel-switcher__item {
  display: inline-flex;
  flex: 0 0 auto;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--touch-target-compact);
  padding: var(--space-1) var(--space-1) var(--space-2);
  appearance: none;
  -webkit-appearance: none;
  background-color: initial;
  border: 0;
  border-bottom: 2px solid;
  border-bottom-style: none;
  color: var(--color-muted);
  font-family: var(--font-body);
  font-size: var(--font-size-caption);
  cursor: pointer;
  transition: color var(--transition-quick), border-color var(--transition-quick);
}
/* D116 ruling 1: same reasoning as .tab-item above — a real border-brighten
   alongside the pre-existing text shift, on the literal channel the floor is
   stated in. `border-bottom-style: none` at rest means a colour alone would
   render nothing; `solid` must also be set here to make it visible. */
.channel-switcher__item:not(:disabled):not(.is-disabled):not(.on):hover {
  border-bottom-style: solid;
  border-bottom-color: var(--color-line-strong);
  color: var(--color-body);
}
.channel-switcher__item:not(:disabled):not(.is-disabled):not(.on):active {
  color: var(--color-ink);
}
/* D116 ruling 5/6: the unified ink-edge selection idiom — was --color-accent
   (the Flame underline critic §A named as "the NOW idiom on a routine tab").
   No border-radius on .channel-switcher__item, so this plain border-bottom
   mechanism carries no corner-hook risk. */
.channel-switcher__item.on {
  border-bottom-style: solid;
  border-bottom-color: var(--color-ink);
  color: var(--color-ink);
  font-weight: var(--weight-bold);
}
.channel-switcher__item:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
.channel-switcher__item:disabled,
.channel-switcher__item.is-disabled {
  color: var(--color-muted);
  cursor: not-allowed;
}

/* Urgent sheet — the ONE place drama is earned: a full-screen, blurred-backdrop
   takeover with a centred sender .card and the reused .swipe-confirm to
   dismiss/acknowledge. Markup carries role="dialog" + aria-modal="true"; the
   production focus-trap + Escape is Phase-5 runtime (GAP, not built here). */
.urgent-sheet {
  display: contents;   /* pure grouping wrapper — the backdrop below owns all positioning */
}
.urgent-sheet__backdrop {
  position: fixed;
  inset: 0;
  z-index: 1000;
  align-items: center;
  justify-content: center;
  padding: var(--space-3);
  background: var(--color-overlay);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.urgent-sheet__backdrop:not([hidden]) {
  display: flex;
}
.urgent-sheet__card {
  width: min(420px, 100%);
  max-height: 90vh;
  overflow-y: auto;
}

/* Message bubble — own message is LIGHT-INVERTED (R01/§A tokens), never Flame
   body text (D108). "Theirs" reuses the plain surface/body pairing (already
   AA-proven token-value pairing). Runtime states (removed-by-moderation,
   sender-anonymised) are text swaps inside the same markup — no class (GAP). */
.message-bubble {
  display: block;
  width: fit-content;
  max-width: 80%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-card);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  overflow-wrap: break-word;
}
.message-bubble--mine {
  margin-left: auto;
  background: var(--color-bubble-mine-bg);
  color: var(--color-bubble-mine-text);
}
.message-bubble--theirs {
  margin-right: auto;
  background: var(--color-surface);
  color: var(--color-body);
}

/* Thread list row — avatar, name, truncated preview, timestamp, unread-badge.
   A tappable <a> row: colour/text-decoration reset, hover/focus/active states,
   >=32px row floor. */
.thread-list-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--touch-target-row);
  padding: var(--space-2) var(--space-3);
  border-bottom: var(--border-width) solid var(--color-line);
  color: inherit;
  text-decoration: none;
  transition: background var(--transition-quick);
}
.thread-list-row:hover,
.thread-list-row:active {
  background: var(--color-hover);
}
/* D116 ruling 1: muted-on-(retuned)hover is only 4.20:1 (design-system.css
   token comment) — the row's own preview/timestamp text is --color-muted, so
   hovering/pressing the row would drop them below 4.5 small-text AA for the
   duration. Bump to --color-body only while hovered/pressed; resting stays
   muted (the quiet, de-emphasised look this row wants). `.is-disabled`
   excluded (Codex round 1, DL12): `.thread-list-row.is-disabled` below sets
   `pointer-events: none`, which already stops a pointer :hover from matching,
   but the explicit `:not(.is-disabled)` matches this file's own guard
   convention rather than relying on a second mechanism silently doing the
   same job. */
.thread-list-row:not(.is-disabled):hover .thread-list-row__preview,
.thread-list-row:not(.is-disabled):hover .thread-list-row__timestamp,
.thread-list-row:not(.is-disabled):active .thread-list-row__preview,
.thread-list-row:not(.is-disabled):active .thread-list-row__timestamp {
  color: var(--color-body);
}
.thread-list-row:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: -2px;
}
.thread-list-row.is-disabled {
  color: var(--color-muted);
  cursor: not-allowed;
  pointer-events: none;
}
.thread-list-row__info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.thread-list-row__name {
  color: var(--color-ink);
  font-weight: var(--weight-bold);
}
.thread-list-row__preview {
  overflow: hidden;
  color: var(--color-muted);
  font-size: var(--font-size-small);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.thread-list-row__timestamp {
  flex: 0 0 auto;
  color: var(--color-muted);
  font-size: var(--font-size-caption);
  white-space: nowrap;
}

/* Composer — message (thread) and announcement variants. Input rides
   .form-textarea; photo-attach rides .file-input; the nudge-mode picker rides
   the frozen .form-select-wrap (a select is never bare caret-less .form-select
   — inventory §3.2 / DL09); send rides .btn-primary. Announcement mode gets a
   quiet 2px accent top border — a UI line, never a text fill (D108). */
.composer {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--color-surface);
  border: var(--border-width) solid var(--color-line);
  border-radius: var(--radius-card);
}
/* Announcement mode reads via a heavier NEUTRAL top border, not accent — the
   4-lens hierarchy/rhythm review (P1-05 in-session) found the Flame accent
   spread across too many routine surfaces (composer, channel-switcher, the
   always-visible expectation-banner) alongside its two genuinely urgent uses
   (the urgent feed-card, the urgent-sheet takeover), diluting the "accent
   means NOW" signal by the time a user reaches the takeover. This border was
   this prompt's own discretionary addition (unlike the channel-switcher underline
   and expectation-banner border, which are Visual-Spec-mandated idioms) — so
   it is the one accent use safe to soften without deviating from spec.

   D116 (the no-ruling defect, critic §B): `border-top-width: 2px` against
   1px sides on a --radius-card box tapers into a heavy corner hook at both
   top corners (the browser blends the two border widths ALONG the curve).
   Never mix border-widths on a radiused box — expressed instead as an inset
   box-shadow line, which follows no corner and does not arc (0 blur/spread,
   so it renders as a crisp 2px bar, clipped cleanly to the radius). */
.composer--announcement {
  box-shadow: inset 0 2px 0 var(--color-body);
}
.composer__input {
  min-height: calc(var(--touch-target-primary) * 3);   /* composer wants more room than a generic textarea */
}
.composer__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.composer__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Audience picker — checkbox chips on the .chip machinery (.chip.on when
   selected), a live recipient count. People and job-role variants share this
   markup; content differs, not CSS. The checkbox is visually hidden but kept
   in the tab order (same pattern as .file-input) so it stays keyboard-operable. */
.audience-picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.audience-picker__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}
.audience-picker__chip {
  position: relative;
  cursor: pointer;
}
.audience-picker__chip input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.audience-picker__chip:not(.is-disabled):not(.on):hover {
  background: var(--color-hover);
}
.audience-picker__chip:not(.is-disabled):not(.on):active {
  background: var(--color-hover);
}
.audience-picker__chip:focus-within {
  outline: var(--focus-ring-width) solid var(--color-focus-ring);
  outline-offset: 2px;
}
.audience-picker__chip.is-disabled {
  color: var(--color-muted);
  cursor: not-allowed;
}
.audience-picker__chip.is-disabled input[type="checkbox"] {
  cursor: not-allowed;
}
.audience-picker__count {
  margin: 0;
  color: var(--color-muted);
  font-size: var(--font-size-small);
}

/* Receipt list — two modes (comms-architecture.md round 7): named rows
   (avatar + name + ack-chip) and an aggregate count-only line. Which mode is
   SHOWN is a D15-scoping feature decision (Phase 5) — this is CSS for both. */
.receipt-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.receipt-list__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--touch-target-row);
  padding: var(--space-1) 0;
}
.receipt-list__name {
  flex: 1;
  min-width: 0;
  color: var(--color-ink);
}
.receipt-list--aggregate {
  padding: var(--space-2) 0;
  color: var(--color-muted);
  font-size: var(--font-size-small);
}

/* Ack chip — extends .chip. Pending = warning tint; done = success tint +
   timestamp (done = the success/done token, never Flame — D108). Self-contained token pairings
   (not a two-class compose with .chip--warning/--success) so the markup
   cannot accidentally omit the tint. Text-bearing (WCAG 1.4.1). */
.ack-chip {
  font-variant-numeric: tabular-nums;   /* timestamp digits stay aligned */
}
.ack-chip--pending {
  background: var(--color-warning-bg);
  border-color: var(--color-warning-ink);
  color: var(--color-warning-ink);
}
.ack-chip--done {
  background: var(--color-success-bg);
  border-color: var(--color-success-ink);
  color: var(--color-success-ink);
}

/* Expectation banner — extends .banner. D116 ruling 5: STRIPPED of the
   flagbar "NOW" idiom (was a 2px accent border) — this is a routine,
   always-visible banner, not an urgent moment, and wearing the urgency mark
   diluted "accent means NOW" by the time a user reached a genuine one
   (critic A3, D116 ruling 2). Attention now reads via WEIGHT + TINT only:
   bold ink text on --color-surface, base .banner's 1px border at
   currentColor (= ink, no literal named — DL04). Static, non-dismissible,
   D34's copy verbatim. No close control in the markup. */
.expectation-banner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-surface);
  color: var(--color-ink);
  font-weight: var(--weight-bold);
}

/* Unread badge — extends .badge (count) / .tab__dot (dot). Count is neutral
   ink fill (a Flame count fails AA — inventory §3.3); dot is the same accent
   mark .tab__dot uses, sized for an inline context (not corner-anchored).
   Kiosk-suppression is a runtime state (the feature hides the element), not
   a class here. */
.unread-badge {
  flex: 0 0 auto;   /* never squashed/stretched as a flex child (e.g. inside .thread-list-row) */
}
.unread-badge--dot {
  display: inline-block;
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-accent);
}

/* Role badge — extends .chip. Four D27 tiers, each a labelled (text-bearing)
   chip, AA both themes, never a Flame text-fill (D108). Each pairing below
   reuses a token PAIR already AA-proven at token level (test_design_tokens.py:
   ink/bg, ink/surface, body/surface) — no new contrast claim is invented here.

   D117 CLOSES the tier-hierarchy question that P1-08/DL15 explicitly left
   open ("does NOT close the open tier-hierarchy design question... Charles's
   ruling there may still restyle role-badge entirely"): a MONOTONIC RAMP,
   Owner heaviest -> Staff lightest, by fill then border WEIGHT (width +
   colour-intensity together), correcting critic A4 ("an outlined Manager pill
   heavier than the author name beside it" — the old grammar's inversion).
   Supervisor's old --color-accent border is retired — D116 ruling 2 (Flame at
   rest is zero) supersedes P1-08's "the one tier that gets the brand mark,
   worn as a line" framing; a role tier is not a NOW moment.
     Owner:      filled ink               — heaviest (a fill outweighs any border)
     Manager:    2px border, --color-body — thick + bright
     Supervisor: 1px border, --color-body — thin + bright (width descends)
     Staff:      1px border, --color-muted — thin + dim (colour descends too)
   Every step is strictly lighter than the one before it. Border ratios reuse
   already-certified 3:1 non-text pairings vs --color-surface: body 11.33/12.15,
   muted 5.38/4.99 dark/light (design-system.css) — both clear WCAG 1.4.11 with
   large margin; see tests/test_gate_fixes.py for the pinned assertion. */
.role-badge {
  white-space: nowrap;   /* a tier label never wraps mid-word inside the compact chip */
}
.role-badge--owner {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-bg);
}
.role-badge--manager {
  background: var(--color-surface);
  border-width: 2px;
  border-color: var(--color-body);
  color: var(--color-ink);
}
.role-badge--supervisor {
  background: var(--color-surface);
  border-color: var(--color-body);
  color: var(--color-body);
}
.role-badge--staff {
  background: var(--color-surface);
  border-color: var(--color-muted);
  color: var(--color-body);
}

/* Empty state extensions — one line of warmth, never an illustration. Pure
   copy variants of the frozen .empty-state; the slightly smaller type keeps
   them quiet inside a comms surface rather than shouting like a first-run hero.
   Flex-centred with a shared min-height so three different one-line copy
   lengths still sit visually level when shown side by side (R2 design
   criticism, P1-05 in-session — the un-centred default made the row look
   accidental rather than deliberate; no icon added, still text-only per spec). */
.empty-state--feed,
.empty-state--messages,
.empty-state--kiosk {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(var(--space-5) * 2);
  font-size: var(--font-size-small);
}
