/* Cascade layer order — declared once, here, since this file loads first
 * (frontend/index.html). Layer order beats specificity and source order
 * entirely: anything in a later-declared layer always wins over anything in
 * an earlier one, regardless of how specific either selector is. `layout.css`
 * and every frontend/css/views/*.css / frontend/css/shared/*.css file (CSS
 * split, CLAUDE.md §12 — this layer's name predates and outlived the single
 * components.css file that originally held all of it) each wrap their own
 * entire content in `@layer layout { ... }` / `@layer components { ... }` —
 * matching the order they already load in via index.html's <link> tags, so
 * nothing about their existing relative precedence changes. `base` below is
 * this file's own reset/variables/element-defaults, everything except the
 * Utility block at the bottom, which is `utilities` — after component layers, so it
 * wins over `base`/`layout`/`components` without needing
 * `!important` anywhere. See CLAUDE.md §9 for the full history (this
 * replaced an interim, harder-to-maintain `!important`-on-every-utility-rule
 * version of the same guarantee) and docs/MAINTAINABILITY_PLAN.md's P0 entry.
 * The final `visibility` layer makes hidden state beat every display utility.
 */
@layer base, layout, components, utilities, visibility;

/* Inter 4.1 (SIL OFL 1.1, ../assets/fonts/LICENSE-Inter.txt), self-hosted so
 * a page load sends nothing to a third party. The one variable file covers
 * every weight the app uses; the relative URL stays inside the release's
 * versioned path (cmd/server/frontend.go). */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../assets/fonts/InterVariable.woff2') format('woff2');
}

@layer base {

/* ── Reset & CSS variables ─────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  /* ── Primary ramp ──────────────────────────────────────────────────────
     Design system v1 — a real 9-step ramp
     replaces the old single value + hover pair, so an active-nav tint,
     a focus ring, and a hover state all read as one color family instead
     of unrelated ad hoc blues. */
  --p-50:  #eef3fd;
  --p-100: #dde8fb;
  --p-200: #b9d0f6;
  --p-300: #8fb0ef;
  --p-400: #628fe6;
  --p-500: #4272da;
  --p-600: #2f5bd1; /* default primary */
  --p-700: #2447a8; /* hover */
  --p-800: #1c3880; /* active/pressed */
  --p-900: #14285c; /* text-on-tint */
  /* The brand accent by role (links, primary buttons, focus, selection). */
  --primary:       var(--p-600);
  --primary-hover: var(--p-700);

  /* ── Neutrals ── cool-blue-biased, not pure grey — a neutral picked to
     agree with the primary ramp above, not a stock grey scale. */
  --bg:            #f7f8fb;
  --surface:       #ffffff;
  --surface-sunk:  #eef1f7;
  --border:        #e1e5ee;
  --border-strong: #c7cede;
  --text:          #10162b;
  --text-2:        #434b60;
  --muted:         #626a81;

  /* ── Status ── semantic, deliberately distinct from the primary accent
     (never the same hue family as --p-*, so "this is the brand color"
     and "this is a severity signal" can never be confused). */
  --danger:      #b92f35;
  --danger-hover:#b32c2c;
  --danger-bg:   #fdecec;
  --warning:      #855307;
  --warning-hover:#8f5806;
  --warning-bg:   #fdf1de;
  --success:      #116c3b;
  --success-bg:   #e6f7ec;
  --info:    var(--p-600);
  --info-bg: var(--p-50);

  /* Text on solid semantic fills differs from text on tinted surfaces. */
  --on-primary: #ffffff;
  --on-danger: #ffffff;
  --on-warning: #ffffff;
  --on-success: #ffffff;

  /* Hover-lift for clickable calendar cells/chips/rows. Deliberately its
     own token, not reused from --p-50 in dark mode: dark mode below uses a
     neutral slate lift instead of a saturated blue tint (a colour wash
     reads as louder on a dark ground), while light mode is a blue tint —
     that asymmetry is why this needs its own name instead of just aliasing
     --p-50 everywhere. */
  --hover-tint: var(--p-50);

  /* Period lifecycle family (color-engine.js LIFECYCLE, palette slots
     .lifecycle-open/-concept/-published below): three hues that mean
     "which situation is this period in" and nothing else. Open (wishes
     open or closed, no roster yet) shares the success values; Concept
     (draft, locked: staff cannot see it) is purple; Published (staff see
     every change) is sky blue. Workspace chrome only, never a chip or cell. */
  --lifecycle-open:         #116c3b;
  --lifecycle-open-bg:      #e6f7ec;
  --lifecycle-concept:      #6b21a8;
  --lifecycle-concept-bg:   #f3e8ff;
  --lifecycle-published:    #075985;
  --lifecycle-published-bg: #e0f2fe;
  --on-lifecycle-open:      #ffffff;
  --on-lifecycle-concept:   #ffffff;
  --on-lifecycle-published: #ffffff;

  /* COMP — a compensation day off (docs/COMPENSATION_COMBINATIONS_PLAN_2026-10-16.md
     §7): the outcome a compensation rule gives, always drawn with a dashed
     border in this hue (views/compensation.css: the rule cards' outcome
     token, strip and tag). Reserved for the planned compensation day, so the
     People × days COMP status and its legend signal reuse these tokens. */
  --comp:    #6b3fa0;
  --comp-bg: #f1eafb;

  /* CHANGE DAY — a contract change that moves only the part-time day off,
     FTE unchanged (docs/FTE_OVER_TIME_PLAN_2026-10-20.md D12): the
     .fte-day-pill on FTE & capacity's plane cells, next change and rail
     (views/fte-capacity.css). Reserved for that meaning; deliberately not
     the blue/amber of FTE up/down. Text ≥ 7:1 on its fill in both themes. */
  --change-day-bg: #dff3f0;
  --change-day-fg: #0d5c54;

  /* Roster search hit (.search-match on chips, people-matrix names and
     coverage cells, views/roster-grid.css): a yellow highlighter, its
     text, and its ring. Deliberately outside the status family — a search
     hit is not a warning. */
  --search-match-bg:   #fef08a;
  --search-match-text: #713f12;
  --search-match-ring: #ca8a04;

  /* "Find it" flash tint (flash-highlight keyframe, shared/misc.css). */
  --flash-tint: rgba(37, 99, 235, .18);

  /* ── Status vs. category tokens ──────────────────────────────────────────
     Design-language rule (see CHANGELOG.md "Anesthesiologist clarity pass"):
     a colour either says HOW something is doing (status — severity/outcome:
     --danger/--warning/--success/--info above, with --muted as the neutral)
     or WHAT KIND of thing it is (category — an identity, no judgement
     attached). The two families are never used interchangeably: category
     tokens are deliberately muted/tonal so a wish-type or shift-type badge
     never reads as an alert next to a real status badge. */

  /* Category — six tonal identities for recurring "what kind of thing is
     this" badges: preferred-off wishes / ANW shifts (rose), day shifts /
     preferred-shift wishes (denim), on-call shifts (clay), holiday leave
     (moss), educational leave (plum), resident/supernumerary assignment
     (berry — a saturated pink-purple, clearly apart from rose's wine tone
     and plum's lavender, so it reads as its own identity rather than a
     near-duplicate of either). */
  --cat-rose:     #b3536e;
  --cat-rose-bg:  #f8ebef;
  --cat-denim:    #3d5f96;
  --cat-denim-bg: #eaf0f8;
  --cat-clay:     #a8752c;
  --cat-clay-bg:  #f6eedd;
  --cat-moss:     #4f7d57;
  --cat-moss-bg:  #eaf2ea;
  --cat-plum:     #8462a8;
  --cat-plum-bg:  #efe7f5;
  --cat-berry:     #9c3986;
  --cat-berry-bg:  #f8e6f2;

  /* Location — up to eight fixed-order categorical hues for per-location
     identity (FRONTEND.md §7: hue = location; color-engine.js hands out
     the .loc-N slots, see "Palette slots" below). Deliberately
     separate from --cat-* above, which already carries specific meanings
     elsewhere in the app. */
  --loc-1: #2a78d6; /* blue */
  --loc-2: #eb6834; /* orange */
  --loc-3: #1baf7a; /* aqua */
  --loc-4: #eda100; /* yellow */
  --loc-5: #e87ba4; /* magenta */
  --loc-6: #008300; /* green */
  --loc-7: #4a3aa7; /* violet */
  --loc-8: #e34948; /* red */

  /* Type — a second, independent categorical channel from --loc-* above,
     for "what kind of shift/duty is this" identity (Day-Ahead board,
     frontend/js/color-engine.js's assignTypeColors). Exists because
     Day-Ahead's board is already scoped to one location, making --loc-*
     hue a no-op there — this covers shift types (Polikliniek, Sedatie
     Supervisor, Dagdienst, Achterwacht, ANW, ...) and duty types (On duty,
     IAT, ...) sharing one 8-slot palette, deliberately a different hue set
     than --loc-* or --cat-* so none of the three channels can be mistaken for
     one another. */
  --type-1: #0f9b8e; /* teal */
  --type-2: #b5502e; /* terracotta */
  --type-3: #6457c9; /* indigo */
  --type-4: #8a9b1f; /* olive */
  --type-5: #c94f8a; /* rose */
  --type-6: #1f7a3d; /* forest */
  --type-7: #8a5a2b; /* brown */
  --type-8: #555f73; /* slate */

  /* Person — a small, deliberately ephemeral palette for Day-Ahead's OR
     squares (frontend/js/components/day-ahead-board.js): colors whichever
     anesthesiologist currently holds a room, recomputed fresh on every
     board load rather than a stable per-person identity, so it needs no
     dark/light-independent meaning beyond "these two rooms are the same
     person today." */
  --person-1: #3478e5; /* blue */
  --person-2: #8a5bd1; /* purple */
  --person-3: #de7b32; /* orange */
  --person-4: #17a06d; /* green */
  --person-5: #d1367a; /* pink */
  --person-6: #6b7b8c; /* slate */

  /* Diverging scale (viz-system §3) — under/over a reference, for the
     .dist-heat-* cells (views/reports.css: Shift Distribution and My
     Stats). color-mix() against the theme tokens, so no dark block. */
  --div-neg-3: color-mix(in srgb, var(--primary) 36%, transparent);
  --div-neg-2: color-mix(in srgb, var(--primary) 24%, transparent);
  --div-neg-1: color-mix(in srgb, var(--primary) 12%, transparent);
  --div-mid:   var(--surface-sunk);
  --div-pos-1: color-mix(in srgb, var(--danger) 12%, transparent);
  --div-pos-2: color-mix(in srgb, var(--danger) 24%, transparent);
  --div-pos-3: color-mix(in srgb, var(--danger) 36%, transparent);

  /* Typography */
  --font: 'Inter', system-ui, sans-serif;
  --font-mono: ui-monospace, 'SF Mono', 'Cascadia Mono', Menlo, Consolas, monospace;
  --text-2xs: 0.6875rem;
  --text-xs:  0.75rem;
  --text-sm:  0.8125rem;
  --text-base: 0.9375rem;
  --text-lg:  1.125rem;
  --text-xl:  1.375rem;
  --text-2xl: 1.75rem;

  /* Role-based type scale — layered on top of the raw
     size steps above rather than replacing them, so existing badge/button/
     chip sizing (already built on --text-sm/lg/xl/2xl) is untouched; these
     four are for document-level hierarchy specifically: h1/h2/h3 below,
     plus --text-eyebrow for uppercase group labels and section markers
     (applied directly via var(), not through a utility class — see its
     component call sites). Body text stays --text-base (already 15px). */
  --text-title:   1.875rem;   /* 30px — page title (h1) */
  --text-section: 1.25rem;    /* 20px — section header (h2) */
  --text-card-h:  0.90625rem; /* 14.5px — card header (h3) */
  --text-eyebrow: 0.71875rem; /* 11.5px — uppercase group label / meta */

  /* Spacing scale */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;

  /* ── Radius ── role-based, not size-based: controls stay tight, panels
     get a touch more, overlays the most — replaces the old flat
     sm/default/lg triple with the same names so nothing breaks. */
  --r-control: 6px;
  --r-panel:   10px;
  --r-overlay: 14px;
  --r-pill:    999px;
  --radius-sm: var(--r-control);
  --radius:    var(--r-panel);
  --radius-lg: var(--r-overlay);

  /* ── Elevation ── border-first: a static surface gets
     a border + background-tone shift only, never a shadow. Shadow is
     reserved for things that actually float above the page — modals,
     dropdowns, the notification panel, toasts, popovers. One tier is
     enough; --shadow-sm collapses to none so every card/panel currently
     styled with it goes flat automatically. */
  --shadow-overlay: 0 12px 32px rgba(16,22,43,.16), 0 2px 8px rgba(16,22,43,.08);
  --shadow-sm: none;
  --shadow:    var(--shadow-overlay);
  --shadow-lg: var(--shadow-overlay);

  /* Transitions */
  --transition: 150ms ease;

  /* Shared "not currently actionable" dimming — one
     token for every disabled control and inactive-record treatment, so
     .btn:disabled, .row-inactive, and the toggle/input/select disabled
     states never drift apart the way 0.5 vs 0.55 once did. */
  --opacity-disabled: 0.5;
}

/* ── Dark theme — variable overrides ────────────────────────────────────────── */
[data-theme="dark"] {
  --on-primary: #10162b;
  --on-danger: #10162b;
  --on-warning: #10162b;
  --on-success: #10162b;
  /* Primary ramp — lighter/less saturated so it reads correctly on a dark
     ground; --primary/--primary-hover (and --info) above resolve through
     automatically since they're declared once as var() references. */
  --p-50:  #1b2947;
  --p-100: #1e2f52;
  --p-200: #2a3d6c;
  --p-300: #3c5590;
  --p-400: #5678c2;
  --p-500: #6f93e8;
  --p-600: #7ea0ec;
  --p-700: #9db4f2;
  --p-800: #c1d0f7;
  --p-900: #e6ecfc;

  /* Neutrals */
  --bg:            #0c111f;
  --surface:       #131a2c;
  --surface-sunk:  #0f1524;
  --border:        #232c44;
  --border-strong: #303c5c;
  --text:          #eef1f8;
  --text-2:        #b7bfd4;
  --muted:         #838db0;

  /* Status */
  --danger:      #f38b8b;
  --danger-hover:#f7abab;
  --danger-bg:   #3a1616;
  --warning:      #e3ab5b;
  --warning-hover:#eec084;
  --warning-bg:   #3a2a10;
  --success:      #6fd39a;
  --success-bg:   #123322;

  --lifecycle-open:         #6fd39a;
  --lifecycle-open-bg:      #123322;
  --lifecycle-concept:      #d8b4fe;
  --lifecycle-concept-bg:   #2e1a44;
  --lifecycle-published:    #7dd3fc;
  --lifecycle-published-bg: #0c2d42;
  --on-lifecycle-open:      #10162b;
  --on-lifecycle-concept:   #10162b;
  --on-lifecycle-published: #10162b;

  --comp:    #c2a1ee;
  --comp-bg: #2a1f3d;
  --change-day-bg: #11332f;
  --change-day-fg: #8ad8cc;

  --search-match-bg:   #713f12;
  --search-match-text: #fef08a;
  --search-match-ring: #fbbf24;

  --flash-tint: rgba(96, 165, 250, .28);

  --hover-tint: #243347;

  /* Category tokens — dark equivalents, same light-text-on-dark-tint pattern
     already used for badges elsewhere in shared/base-ui.css. */
  --cat-rose:     #e8a3b8;
  --cat-rose-bg:  #3a2530;
  --cat-denim:    #9db8e0;
  --cat-denim-bg: #202d40;
  --cat-clay:     #e0b876;
  --cat-clay-bg:  #3a2f18;
  --cat-moss:     #9ecaa4;
  --cat-moss-bg:  #1e3324;
  --cat-plum:     #c9a8dd;
  --cat-plum-bg:  #33243f;
  --cat-berry:     #e296cf;
  --cat-berry-bg:  #391e33;

  /* Location tokens — dark-stepped equivalents of the light --loc-* set above. */
  --loc-1: #3987e5;
  --loc-2: #d95926;
  --loc-3: #199e70;
  --loc-4: #c98500;
  --loc-5: #d55181;
  --loc-6: #008300;
  --loc-7: #9085e9;
  --loc-8: #e66767;

  /* Type/person tokens — dark-stepped equivalents of the light sets above. */
  --type-1: #29b6a8;
  --type-2: #d97a4f;
  --type-3: #8f82e0;
  --type-4: #b9c93f;
  --type-5: #e07aa8;
  --type-6: #3fae63;
  --type-7: #b98552;
  --type-8: #8c96ab;

  --person-1: #6fa0f2;
  --person-2: #ae8bea;
  --person-3: #f0a366;
  --person-4: #3fc79a;
  --person-5: #e56aa0;
  --person-6: #93a3b3;

  /* Overlay shadow needs more contrast against a dark ground than a soft
     ink shadow gives on light — everything referencing --shadow-overlay
     (and --shadow/--shadow-lg through it) picks this up automatically. */
  --shadow-overlay: 0 12px 32px rgba(0,0,0,.5), 0 2px 8px rgba(0,0,0,.3);
}

/* ── Palette slots ── color-engine.js hands out one class per entity on each
   categorical axis (.loc-N, .type-N, .person-N). Each class sets its axis's
   one custom property (--loc, --type, --person) here, once; a component reads
   that property (`border-left-color: var(--loc)`) behind a
   `:is(.loc-1, …, .loc-8)` gate instead of repeating a rule per slot. The
   property resolves the slot token on the element itself, so the dark
   overrides above apply without a second rule, and descendants (a chip's
   .chip-dot) inherit their own row's hue. loc-other and the generated
   *-gen tier deliberately set nothing: their consumers keep their own
   fallback (`var(--loc, var(--muted))`) or rule. */
.loc-1 { --loc: var(--loc-1); }
.loc-2 { --loc: var(--loc-2); }
.loc-3 { --loc: var(--loc-3); }
.loc-4 { --loc: var(--loc-4); }
.loc-5 { --loc: var(--loc-5); }
.loc-6 { --loc: var(--loc-6); }
.loc-7 { --loc: var(--loc-7); }
.loc-8 { --loc: var(--loc-8); }
.type-1 { --type: var(--type-1); }
.type-2 { --type: var(--type-2); }
.type-3 { --type: var(--type-3); }
.type-4 { --type: var(--type-4); }
.type-5 { --type: var(--type-5); }
.type-6 { --type: var(--type-6); }
.type-7 { --type: var(--type-7); }
.type-8 { --type: var(--type-8); }
.person-1 { --person: var(--person-1); }
.person-2 { --person: var(--person-2); }
.person-3 { --person: var(--person-3); }
.person-4 { --person: var(--person-4); }
.person-5 { --person: var(--person-5); }
.person-6 { --person: var(--person-6); }
/* Lifecycle slots (color-engine.js lifecycleClass): one class per period
   lifecycle family sets --lifecycle, --lifecycle-bg and --on-lifecycle;
   the badge, the Roster Editor mode ribbon, the period selector and the
   workspace edge read them. lifecycle-none (an unknown status) sets
   nothing and its consumers fall back to neutral. */
.lifecycle-open      { --lifecycle: var(--lifecycle-open);      --lifecycle-bg: var(--lifecycle-open-bg);      --on-lifecycle: var(--on-lifecycle-open); }
.lifecycle-concept   { --lifecycle: var(--lifecycle-concept);   --lifecycle-bg: var(--lifecycle-concept-bg);   --on-lifecycle: var(--on-lifecycle-concept); }
.lifecycle-published { --lifecycle: var(--lifecycle-published); --lifecycle-bg: var(--lifecycle-published-bg); --on-lifecycle: var(--on-lifecycle-published); }

html { font-size: 16px; }

body {
  font-family: var(--font);
  font-size: var(--text-base);
  color: var(--text);
  background: var(--bg);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

h1 { font-size: var(--text-title); font-weight: 800; letter-spacing: -0.015em; text-wrap: balance; }
h2 { font-size: var(--text-section); font-weight: 700; }
h3 { font-size: var(--text-card-h); font-weight: 600; }

button, input, select, textarea {
  font: inherit;
  outline: none;
}

button { cursor: pointer; }

} /* @layer base */

@layer utilities {

/* Utility
 *
 * A small atomic-CSS layer, added to eliminate markup-time `style="..."`
 * attributes app-wide — the app's CSP (`default-src 'self'`, no `style-src`,
 * §9) silently drops every one of those, so any view still using them is
 * either dead weight or (for anything controlling initial visibility, e.g.
 * `display:none`) a live bug. Compose these atoms on one element
 * (`class="d-flex items-center gap-3"`) the same way utility-first CSS
 * normally works; reach for a real named component/semantic class instead
 * when three or more atoms would always appear together for the same
 * reason, or when the existing component class already covers the intent
 * (e.g. `.checkbox-label` already sets its own flex/gap/align-items — don't
 * also add flex utilities next to it).
 *
 * No rule here needs `!important`: this whole block lives in the
 * `utilities` layer, declared last in this file's top-of-file
 * `@layer base, layout, components, utilities, visibility;` statement — a later-declared
 * layer always wins over an earlier one regardless of selector specificity,
 * so a utility class applied alongside any `base`/`layout`/`components`
 * class always wins for that property, by construction. (An earlier version
 * of this block used `!important` on every rule instead, before the
 * `@layer` structure existed — see CLAUDE.md §9 and
 * docs/MAINTAINABILITY_PLAN.md's P0 entry for that history.)
 */
.text-muted     { color: var(--muted); }
.text-danger    { color: var(--danger); }
.text-warning   { color: var(--warning); }
.text-primary   { color: var(--primary); }
.text-sm        { font-size: var(--text-sm); }
.text-2xs       { font-size: var(--text-2xs); }
.text-center    { text-align: center; }
.text-right     { text-align: right; }
.nowrap         { white-space: nowrap; }
.cursor-pointer { cursor: pointer; }
.font-normal    { font-weight: normal; }
.font-semibold  { font-weight: 600; }
.font-mono      { font-family: var(--font-mono); }


.d-flex         { display: flex; }
.d-inline-flex  { display: inline-flex; }
.d-block        { display: block; }
.d-inline-block { display: inline-block; }
.flex-col       { flex-direction: column; }
.flex-row       { flex-direction: row; }
.flex-1         { flex: 1; }
.flex-wrap      { flex-wrap: wrap; }
.items-center   { align-items: center; }
.items-start    { align-items: flex-start; }
.items-end      { align-items: flex-end; }
.justify-between { justify-content: space-between; }

.w-100          { width: 100%; }
.w-auto         { width: auto; }
.w-6rem         { width: 6rem; }
.max-w-320      { max-width: 320px; }
.max-w-640      { max-width: 640px; }

.gap-1          { gap: var(--space-1); }
.gap-2          { gap: var(--space-2); }
.gap-3          { gap: var(--space-3); }
.gap-4          { gap: var(--space-4); }
.gap-6          { gap: var(--space-6); }

.m-0            { margin: 0; }
.mt-0           { margin-top: 0; }
.mb-0           { margin-bottom: 0; }
.mt-1           { margin-top: var(--space-1); }
.mt-2           { margin-top: var(--space-2); }
.mt-3           { margin-top: var(--space-3); }
.mt-4           { margin-top: var(--space-4); }
.mt-6           { margin-top: var(--space-6); }
.mb-1           { margin-bottom: var(--space-1); }
.mb-2           { margin-bottom: var(--space-2); }
.mb-3           { margin-bottom: var(--space-3); }
.mb-4           { margin-bottom: var(--space-4); }
.mb-6           { margin-bottom: var(--space-6); }
.ml-2           { margin-left: var(--space-2); }
.ml-4           { margin-left: var(--space-4); }

.p-0            { padding: 0; }
.p-2            { padding: var(--space-2); }
.p-3            { padding: var(--space-3); }
.p-4            { padding: var(--space-4); }
.pb-3           { padding-bottom: var(--space-3); }
.py-1           { padding-top: var(--space-1); padding-bottom: var(--space-1); }
.py-2           { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.py-3           { padding-top: var(--space-3); padding-bottom: var(--space-3); }
.py-4           { padding-top: var(--space-4); padding-bottom: var(--space-4); }

.border-bottom  { border-bottom: 1px solid var(--border); }

/* A scrollable list capped to a modal-friendly height — roster-editor's
 * alert/warning lists (e.g. unfilled-slot, compensation-override summaries). */
.modal-scroll-list { max-height: 200px; overflow-y: auto; padding-left: 1.2em; }

/* Smaller inline "nothing here yet" placeholder inside an existing card or
 * section; the full .empty-state component is shared/misc.css. */
.empty-state-sm { text-align: center; padding: var(--space-4); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Visible keyboard focus without treating route headings as editable fields. */
button:focus-visible, a:focus-visible, summary:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
}
#main-content h1[tabindex="-1"]:focus { outline: none; text-decoration: underline; text-decoration-color: var(--p-200); text-underline-offset: 8px; text-decoration-thickness: 2px; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none; transition: none; scroll-behavior: auto; }
}

} /* @layer utilities */

/* Hidden state wins over layout utilities, including native hidden rows. */
@layer visibility {
  .hidden, [hidden] { display: none; }
}
