/* ===============================================================
   SAHE SMS — the Editorial Command Centre
   Navy chrome framing a warm paper canvas, with the information
   hierarchy of a well-set annual report rather than a dashboard
   template.  Specification: docs/design-system.md.

   THE TOKEN CONTRACT (§7).  Every paint in this product resolves
   through one of the custom properties below.  Never write a hex in
   a component rule, an inline style or an SVG — the single
   exception in the whole system is a national flag (§9.21).

   Every hue is defined five times (§4.1):
     (base)    graphics, chart series, 2px markers   >= 3:1 on surface
     -strong   ANY small text, solid fills           >= 4.5:1
     -hover    one step past -strong
     -soft     the tint ground
     -line     the tint's border
   In dark theme -strong LIGHTENS instead of darkening, so no
   component ever branches on the theme.

   RE-HUE (§4.6).  The brand navy var(--ink) takes --ink, --chrome and
   the light-theme shadow tint.  Coral keeps the primary slot and
   teal the positive slot at the system defaults; royal, amber,
   crimson, plum and slate are status colours and are left alone.
   Measured ratios are recorded in
   docs/reports/design-retrofit-phase1-foundation.md.
   =============================================================== */

:root {
    color-scheme: light;

    /* ---- Ink — the navy text ramp, re-hued to the brand navy ---- */
    --ink:        #1e3a5f;   /* primary text, chrome ground. 11.03:1 on paper */
    --ink-soft:   #33547f;   /* secondary text, dialog body.  7.41:1 on paper */
    --ink-mute:   #586d89;   /* tertiary text; carries every 10px caps label.
                                5.08:1 on paper — the system's hardest floor */
    --ink-faint:  #8495ac;   /* disabled glyphs, separator dots, decoration */
    --ink-line:   #c8d1de;   /* the ink hue's border, for avatar monograms */

    /* ---- Brand ---- */
    --coral:        #e9552b;  /* primary accent.        3.62:1 on surface */
    --coral-strong: #c73d0e;  /* 4.53:1 on -soft, 5.12:1 on white */
    --coral-hover:  #b4370d;
    --coral-soft:   #fdeee9;
    --coral-line:   #f7cbbc;

    --teal:        #20a187;   /* positive, success, growth. 3.23:1 on surface */
    --teal-strong: #167b69;   /* 4.58:1 on -soft, 5.16:1 on white */
    --teal-hover:  #136a5a;
    --teal-soft:   #e5f5f1;
    --teal-line:   #b3e0d4;

    --royal:        #123a85;  /* links, focus ring, informational */
    --royal-strong: #123a85;
    --royal-hover:  #0e2f6e;
    --royal-soft:   #e8eefa;
    --royal-line:   #bfd0ee;

    /* ---- Status ---- */
    --amber:        #b77a21;  /* warning, at-risk, pending, review */
    --amber-strong: #8f5e14;
    --amber-hover:  #744c0f;
    --amber-soft:   #fbf1de;
    --amber-line:   #ecd5a6;

    --crimson:        #b4331f; /* danger, overdue, destructive, failed */
    --crimson-strong: #b4331f;
    --crimson-hover:  #962917;
    --crimson-soft:   #fceeeb;
    --crimson-line:   #f2c4ba;

    --plum:        #6b3fa0;   /* fifth series colour, specialist states */
    --plum-strong: #5b2f92;
    --plum-hover:  #4b2680;
    --plum-soft:   #f2ecfb;
    --plum-line:   #d6c3ee;

    --slate:        #5b6b84;  /* neutral, inert, draft, paused, "none" */
    --slate-strong: #4c5a70;
    --slate-hover:  #3f4b5e;
    --slate-soft:   #eef1f5;
    --slate-line:   #d4dbe5;

    /* ---- Surfaces — warm, never pure grey ---- */
    --paper:          #fbfaf8;  /* the page canvas */
    --surface:        #ffffff;  /* panels, cards, dialogs, inputs */
    --surface-alt:    #f5f3ef;  /* sunken areas, hover wash, panel footers */
    --surface-sunken: #f0eee9;  /* progress tracks, the deepest recess */
    --rule:           #e6e3dd;  /* THE hairline. Most-used token in the system */
    --rule-strong:    #d3cfc6;  /* hairline on hover, table header underline */

    /* ---- Chrome — the rail and topbar keep their own scale, and it does
       not redefine per theme (see the dark block): the chrome is one navy
       everywhere. ---- */
    --chrome:             #061933;
    --chrome-alt:         #0d2a50;  /* active nav item, countdown tiles */
    --chrome-rule:        #1d4170;
    --chrome-text:        #a9bcd6;
    --chrome-text-mute:   #93a5c1;
    --chrome-text-strong: #ffffff;

    /* The type colour that sits ON a solid `-strong` fill.
       §9.1 writes this as literal white, which is right in the light theme
       -- but in dark theme `-strong` LIGHTENS (that is the whole point of
       the paired-token scheme), so white type on a solid amber button drops
       to about 1.6:1 and disappears. Making it a TOKEN keeps the promise
       that no component branches on the theme while letting the pair invert
       together: white on a dark fill, near-black on a light one. */
    --on-strong: #ffffff;

    /* ---- Effects. Navy at very low alpha, never black (§4.2) ---- */
    --shadow-raise:  0 1px 2px rgb(30 58 95 / 0.04), 0 4px 16px rgb(30 58 95 / 0.06);
    --shadow-float:  0 2px 4px rgb(30 58 95 / 0.05), 0 12px 32px rgb(30 58 95 / 0.11);
    --shadow-drawer: -12px 0 48px rgb(30 58 95 / 0.14);
    --focus-ring:    0 0 0 2px var(--surface), 0 0 0 4px var(--royal);

    /* ---- Typography (§5.1). Outfit carries the whole product; the
       serif/sans split is gone, so --font-serif is kept as an alias
       rather than chased through every rule that names it. ---- */
    --font-sans:  'Outfit', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
    --font-serif: var(--font-sans);
    --font-mono:  'JetBrains Mono', ui-monospace, 'SFMono-Regular', Consolas, monospace;

    /* ---- The nine-step scale (§5.2). Nothing else is permitted. ---- */
    --text-display: 2.5rem;    --text-display-lh: 1.1;  --text-display-ls: -0.022em;
    --text-figure:  2.125rem;  --text-figure-lh: 1;     --text-figure-ls: -0.02em;
    --text-h1:      1.875rem;  --text-h1-lh: 1.2;       --text-h1-ls: -0.018em;
    --text-h2:      1.25rem;   --text-h2-lh: 1.4;       --text-h2-ls: -0.012em;
    --text-h3:      0.9375rem; --text-h3-lh: 1.45;      --text-h3-ls: -0.004em;
    --text-body:    0.875rem;  --text-body-lh: 1.55;
    --text-sm:      0.8125rem; --text-sm-lh: 1.5;
    --text-xs:      0.75rem;   --text-xs-lh: 1.45;
    --text-label:   0.625rem;  --text-label-lh: 1.4;    --text-label-ls: 0.09em;

    --font-normal:   400;
    --font-medium:   500;
    --font-semibold: 600;
    --font-bold:     700;

    /* ---- Radius, one per role (§6.2) ---- */
    --radius-sm:   4px;   /* badges, chips, small buttons, focus ring */
    --radius-md:   6px;   /* inputs, default buttons, cards-in-a-list */
    --radius-lg:   10px;  /* panels, dialogs, dropdown menus, toasts */
    --radius-xl:   14px;  /* the command palette only */
    --radius-full: 9999px;
    --radius:      var(--radius-md);

    /* ---- Motion. ONE easing curve for the entire product (§11) ---- */
    --ease-out-editorial: cubic-bezier(0.2, 0, 0, 1);
    --dur-colour: 160ms;   /* colour / border / shadow */
    --dur-fade:   180ms;   /* backdrops, tab panels, tooltips */
    --dur-rise:   240ms;   /* dialogs, menus, toasts */
    --dur-slide:  260ms;   /* the drawer */
    --dur-fill:   500ms;   /* progress fill on mount */
    --dur-chart:  620ms;   /* one chart entrance, 25ms staggered */

    /* ---- Spacing rhythm (§6.1). Tailwind's 4px 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-7:  1.75rem;   --space-8:  2rem;
    --space-10: 2.5rem;    --space-12: 3rem;
    --space-16: 4rem;

    /* ---- Shell measurements (§8.1) ---- */
    --rail-w:           232px;
    --rail-w-collapsed: 68px;
    --topbar-h:         64px;
    --env-banner-h:     28px;   /* the non-production warning band */
    --canvas-max:       1600px;

    /* ---- The z-index ladder (§8.1). Keep these exact — they are the
       whole stacking contract. ---- */
    --z-topbar:  20;
    --z-rail:    30;
    --z-overlay: 50;   /* dialogs, drawers, command palette + backdrops */
    --z-toast:   60;
    --z-tooltip: 70;

    /* Inside the overlay band, the panel sits one step above its own
       backdrop. The reference implementation gets this from DOM order --
       backdrop and panel are siblings in one fixed container. Here the
       backdrop is appended to <body> by modal.js AFTER the dialog markup,
       so equal z-indexes would paint it on top and make every dialog
       unclickable (the regression core/test_modal_layering.py guards).
       51 is still inside the overlay band and still below toasts. */
    --z-overlay-panel: 51;

    /* ---- RGB channel triplets, for rgba() tints. These are token
       DEFINITIONS, not component paint, so the literal is correct here
       and nowhere else. ---- */
    --ink-rgb:     30, 58, 95;
    --coral-rgb:   233, 85, 43;
    --teal-rgb:    32, 161, 135;
    --royal-rgb:   18, 58, 133;
    --amber-rgb:   183, 122, 33;
    --crimson-rgb: 180, 51, 31;
    --plum-rgb:    107, 63, 160;
    --slate-rgb:   91, 107, 132;

    /* =============================================================
       COMPATIBILITY ALIASES — the previous token vocabulary, pointed
       at the new contract.  ~19,000 lines of stylesheet and ~330
       templates were written against these names; aliasing them is
       what carries the re-hue into every page at once instead of
       requiring 19,000 lines of edits before anything renders.

       NOT FOR NEW WORK.  Write --ink / --coral-strong / --rule
       directly.  Each alias is retired as the rules that use it are
       rewritten onto the real tokens.
       ============================================================= */
    --primary:       var(--ink);
    --primary-dark:  var(--chrome);
    --primary-light: var(--ink-soft);
    --primary-50:    var(--surface-alt);
    --primary-100:   var(--rule);

    --accent:      var(--coral-strong);
    --accent-dark: var(--coral-hover);
    --accent-light:var(--coral);
    --accent-50:   var(--coral-soft);

    --success:       var(--teal-strong);
    --success-light: var(--teal-soft);
    --success-dark:  var(--teal-hover);
    --warning:       var(--amber-strong);
    --warning-light: var(--amber-soft);
    --warning-dark:  var(--amber-hover);
    --danger:        var(--crimson-strong);
    --danger-light:  var(--crimson-soft);
    --danger-dark:   var(--crimson-hover);
    --info:          var(--royal-strong);
    --info-light:    var(--royal-soft);
    --info-dark:     var(--royal-hover);

    --white:    var(--surface);
    --gray-25:  var(--paper);
    --gray-50:  var(--paper);
    --gray-100: var(--surface-alt);
    --gray-200: var(--rule);
    --gray-300: var(--rule-strong);
    --gray-400: var(--ink-faint);
    --gray-500: var(--ink-mute);
    --gray-600: var(--ink-mute);
    --gray-700: var(--ink-soft);
    --gray-800: var(--ink);
    --gray-900: var(--ink);

    --border:        var(--rule);
    --border-light:  var(--rule);
    --border-strong: var(--rule-strong);
    --border-color:  var(--rule);
    --bg:            var(--surface);
    --bg-light:      var(--paper);
    --bg-muted:      var(--surface-alt);
    --bg-subtle:     var(--paper);
    --background:    var(--paper);
    --surface-muted: var(--surface-alt);

    --text:           var(--ink);
    --text-primary:   var(--ink);
    --text-secondary: var(--ink-soft);
    --text-muted:     var(--ink-mute);
    --muted:          var(--ink-mute);

    /* Old nine-step type scale, folded onto the new nine steps (§5.2). */
    --text-base: var(--text-body);
    --text-md:   var(--text-body);
    --text-lg:   var(--text-h3);
    --text-xl:   var(--text-h2);
    --text-2xl:  var(--text-h2);
    --text-3xl:  var(--text-h1);
    --text-4xl:  var(--text-display);

    --radius-2xl: var(--radius-xl);

    /* Old shadow ramp, re-tinted to navy. The resting-shadow uses are
       removed component by component in Phase 2 (Law 3: rules, not
       shadows), which is what finally retires these. */
    --shadow-xs: var(--shadow-raise);
    --shadow-sm: var(--shadow-raise);
    --shadow-md: var(--shadow-raise);
    --shadow-lg: var(--shadow-float);
    --shadow-xl: var(--shadow-float);

    --transition-fast: var(--dur-colour) var(--ease-out-editorial);
    --transition-base: var(--dur-rise) var(--ease-out-editorial);
    --transition-slow: var(--dur-slide) var(--ease-out-editorial);

    --z-dropdown:       var(--z-overlay);
    --z-sticky:         var(--z-topbar);
    --z-fixed:          var(--z-rail);
    --z-modal-backdrop: var(--z-overlay);
    --z-modal:          var(--z-overlay-panel);

    --primary-rgb: var(--ink-rgb);
    --success-rgb: var(--teal-rgb);
    --warning-rgb: var(--amber-rgb);
    --danger-rgb:  var(--crimson-rgb);
    --info-rgb:    var(--royal-rgb);

    --color-primary:       var(--ink);
    --color-primary-soft:  var(--surface-alt);
    --color-accent-soft:   var(--coral-soft);
    --color-accent-border: var(--coral-line);
    --color-success:       var(--teal-strong);
    --color-warning:       var(--amber-strong);
    --color-danger:        var(--crimson-strong);
    --color-info:          var(--royal-strong);
    --color-surface-muted: var(--surface-alt);

    /* Sticky offsets. Anything pinned to the top of the viewport has to
       clear the chrome already pinned there. `--sticky-offset` is the one
       number to write in a `top:`. */
    --sticky-offset: var(--space-4);
}

/* ---- Dark theme (§4.3). Derived by inversion at the brand hue, not
   guessed: the same lightness ramp as the reference implementation,
   rotated onto hsl(214, …). The canvas is navy, not grey or black, so
   the whole product stays one family. -soft tints become very dark,
   desaturated versions of the hue; shadows switch to black at high
   alpha, because a navy shadow on a navy ground is invisible. ---- */
[data-theme="dark"] {
    color-scheme: dark;

    --ink:       #eef3f9;
    --ink-soft:  #a9bcd6;
    --ink-mute:  #8ba2c0;
    --ink-faint: #6b84a4;
    --ink-line:  #264c7e;

    --coral: #ff6b41;  --coral-strong: #ff8a68;  --coral-hover: #ffa286;
    --coral-soft: #2c1610;  --coral-line: #59291a;

    --teal: #2fbfa1;  --teal-strong: #4ed6ba;  --teal-hover: #6ee2ca;
    --teal-soft: #0d2a26;  --teal-line: #1a4f44;

    --royal: #4c82e0;  --royal-strong: #7aa6ee;  --royal-hover: #96baf3;
    --royal-soft: #11244a;  --royal-line: #2a4d8f;

    --amber: #d99a3c;  --amber-strong: #e8b464;  --amber-hover: #f0c684;
    --amber-soft: #2b2010;  --amber-line: #57411d;

    --crimson: #e0553c;  --crimson-strong: #ef7a63;  --crimson-hover: #f59583;
    --crimson-soft: #2d1512;  --crimson-line: #5a2a22;

    --plum: #a97ce8;  --plum-strong: #bb99f0;  --plum-hover: #ccb0f5;
    --plum-soft: #1e1a33;  --plum-line: #3a3160;

    --slate: #8496ae;  --slate-strong: #a2b1c5;  --slate-hover: #b8c4d4;
    --slate-soft: #172a4a;  --slate-line: #2a3f61;

    --paper:          #061933;
    --surface:        #0d2a50;
    --surface-alt:    #11315b;
    --surface-sunken: #08203f;
    --rule:           #1d4170;
    --rule-strong:    #2a5288;

    /* --chrome-* is deliberately NOT redefined here: the rail, tooltips and
       auth screens are chrome, not canvas, and stay the one navy in both
       themes (see :root for the values). */

    --on-strong: #061933;   /* the inverted half of the pair, see :root */

    --shadow-raise:  0 1px 2px rgb(0 0 0 / 0.3),  0 4px 16px rgb(0 0 0 / 0.35);
    --shadow-float:  0 2px 4px rgb(0 0 0 / 0.35), 0 12px 32px rgb(0 0 0 / 0.5);
    --shadow-drawer: -12px 0 48px rgb(0 0 0 / 0.5);
    --focus-ring:    0 0 0 2px var(--surface), 0 0 0 4px var(--royal);

    --ink-rgb:     238, 243, 249;
    --coral-rgb:   255, 107, 65;
    --teal-rgb:    47, 191, 161;
    --royal-rgb:   76, 130, 224;
    --amber-rgb:   217, 154, 60;
    --crimson-rgb: 224, 85, 60;
    --plum-rgb:    169, 124, 232;
    --slate-rgb:   132, 150, 174;
}

/* Resolved per page from the chrome that is actually rendered:
   unauthenticated and print layouts have no topbar, and the environment
   banner only exists off production. */
body:has(.topbar) {
    --sticky-offset: calc(var(--topbar-h) + var(--space-4));
}
body:has(.environment-banner):has(.topbar) {
    --sticky-offset: calc(var(--env-banner-h) + var(--topbar-h) + var(--space-4));
}

/* ===============================================================
   THE UTILITY LAYER (§7).  Five classes carry the whole language.
   =============================================================== */

/* THE caps label — the system's signature and its connective tissue.
   Section headers, panel headers, stat labels, FORM labels, table
   column headers, eyebrows, badge text, chart tooltip titles. */
.label {
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
}

/* Any number that sits above or beside another number. */
.tabular {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* A vertical hairline for dividing stat columns. */
.rule-y { border-inline-start: 1px solid var(--rule); }

/* A hairline that fades out — closes every section header. It says
   "this section starts here" without drawing a box. */
.rule-fade {
    background: linear-gradient(90deg, var(--rule) 0%, transparent 100%);
    height: 1px;
}

/* Texture. One place only: over the navy sign-in panel, at ~40%. */
.grain {
    background-image: radial-gradient(circle at 1px 1px,
        color-mix(in srgb, var(--ink) 6%, transparent) 1px, transparent 0);
    background-size: 4px 4px;
}

/* The navy rail gets its own scrollbar treatment — a paper-coloured
   thumb inside the rail would look like a bug. */
.scroll-chrome::-webkit-scrollbar-thumb {
    background: var(--chrome-rule);
    border-color: var(--chrome);
}


/* ===============================================================
   TONE UTILITIES (§4.4)
   Semantics never reference a colour directly: they reference a
   tone, and this one table turns a tone into utilities. It is what
   stops a badge, a delta arrow and a progress fill ever disagreeing
   about what "amber" means.

     text-<tone>-strong   any word a human reads      (Law 2)
     bg-<tone>-soft       the tint ground
     border-<tone>-line   the tint's border
     bg-<tone>            the base tone, for GRAPHICS only
   =============================================================== */
.text-coral-strong { color: var(--coral-strong); }
.text-teal-strong { color: var(--teal-strong); }
.text-royal-strong { color: var(--royal-strong); }
.text-amber-strong { color: var(--amber-strong); }
.text-crimson-strong { color: var(--crimson-strong); }
.text-plum-strong { color: var(--plum-strong); }
.text-slate-strong { color: var(--slate-strong); }
.text-ink { color: var(--ink); }
.text-ink-soft { color: var(--ink-soft); }
.text-ink-mute { color: var(--ink-mute); }
.text-ink-faint { color: var(--ink-faint); }

.bg-coral-soft { background-color: var(--coral-soft); }
.bg-teal-soft { background-color: var(--teal-soft); }
.bg-royal-soft { background-color: var(--royal-soft); }
.bg-amber-soft { background-color: var(--amber-soft); }
.bg-crimson-soft { background-color: var(--crimson-soft); }
.bg-plum-soft { background-color: var(--plum-soft); }
.bg-slate-soft { background-color: var(--slate-soft); }
.bg-surface-alt { background-color: var(--surface-alt); }
.bg-surface-sunken { background-color: var(--surface-sunken); }

.border-coral-line { border-color: var(--coral-line); }
.border-teal-line { border-color: var(--teal-line); }
.border-royal-line { border-color: var(--royal-line); }
.border-amber-line { border-color: var(--amber-line); }
.border-crimson-line { border-color: var(--crimson-line); }
.border-plum-line { border-color: var(--plum-line); }
.border-slate-line { border-color: var(--slate-line); }
.border-ink-line { border-color: var(--ink-line); }
.border-rule { border-color: var(--rule); }
.border-rule-strong { border-color: var(--rule-strong); }

.bg-coral { background-color: var(--coral); }
.bg-teal { background-color: var(--teal); }
.bg-royal { background-color: var(--royal); }
.bg-amber { background-color: var(--amber); }
.bg-crimson { background-color: var(--crimson); }
.bg-plum { background-color: var(--plum); }
.bg-slate { background-color: var(--slate); }
.bg-ink { background-color: var(--ink); }

/* The tone's own text colour for an SVG stroke or fill, so a chart
   or an illustration paints through the same token as its label. */
.fill-coral { fill: var(--coral); }  .stroke-coral { stroke: var(--coral); }
.fill-teal { fill: var(--teal); }  .stroke-teal { stroke: var(--teal); }
.fill-royal { fill: var(--royal); }  .stroke-royal { stroke: var(--royal); }
.fill-amber { fill: var(--amber); }  .stroke-amber { stroke: var(--amber); }
.fill-crimson { fill: var(--crimson); }  .stroke-crimson { stroke: var(--crimson); }
.fill-plum { fill: var(--plum); }  .stroke-plum { stroke: var(--plum); }
.fill-slate { fill: var(--slate); }  .stroke-slate { stroke: var(--slate); }


/* ===== KEYFRAMES (§7) ===== */
@keyframes rise {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}
@keyframes fade     { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide-in { from { transform: translateX(100%); } to { transform: none; } }
@keyframes shimmer  {
    from { background-position: -200% 0; }
    to   { background-position:  200% 0; }
}

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

/* ===== BASE LAYER (§7 @layer base) ===== */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    /* --rule is the single most-used token in the system, so `border` alone
       is always correct and `border-color` is only written for emphasis. */
    border-color: var(--rule);
}

html {
    font-size: 16px;              /* the rem base; the product's body is 14px */
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overflow-y: scroll;           /* always show, so pages never shift sideways */
}

body {
    font-family: var(--font-sans);
    font-size: var(--text-body);          /* 14px — a density choice (§5.2) */
    font-weight: var(--font-normal);
    line-height: var(--text-body-lh);
    color: var(--ink);
    background-color: var(--paper);
    min-height: 100vh;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* `overflow-x: hidden` on <body> silently disabled EVERY position:sticky in the
   app. `hidden` on one axis computes the other axis to `auto`, which makes the
   body a scroll container -- and a sticky element sticks to its nearest scroll
   container, not the viewport. Body's scrollport never scrolls (the viewport
   does), so nothing ever stuck: the sticky topbar, the mobile header,
   `.sticky-top`, the form-builder sidebar and every `.detail-side` summary rail
   scrolled straight off the screen. Measured in Chromium: a rail pinned at
   `top: 16px` sat at -848px after an 1600px scroll.

   `clip` clamps the same overflow WITHOUT creating a scroll container, so
   sticky resolves against the viewport again. Kept behind @supports so a
   browser that does not know `clip` (Safari < 16) keeps the old clamp rather
   than gaining a horizontal scrollbar. */
@supports (overflow: clip) {
    body { overflow-x: clip; }
}

/* One global focus ring. A double ring reads on any background, light or
   dark, without any per-component work (§15). */
:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    border-radius: var(--radius-sm);
}

/* A branded selection colour: two lines nobody makes, felt immediately. */
::selection {
    background-color: var(--coral);
    color: #fff;
}

/* The 3px --paper border is what turns a 10px scrollbar into a thin
   floating pill without any custom scrollbar library. */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: var(--rule-strong);
    border: 3px solid var(--paper);
    border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); }

* {
    scrollbar-width: thin;
    scrollbar-color: var(--rule-strong) transparent;
}

/* ===== TYPOGRAPHY (§5) ===== */
/* Serif for figures and titles. Sans for interface. Mono for machine
   strings. A transitional serif does the talking; three families, three
   jobs, no overlap (Law 5). */
h1, h2, h3, h4, h5, h6 {
    font-weight: var(--font-semibold);
    line-height: 1.3;
    color: var(--ink);
    margin-bottom: var(--space-3);
}

h1, h2, h3, h4 { text-wrap: balance; }   /* headlines never orphan a word */
p             { text-wrap: pretty; }

h1 {
    font-family: var(--font-serif);
    font-size: var(--text-h1);
    line-height: var(--text-h1-lh);
    letter-spacing: var(--text-h1-ls);
    font-weight: var(--font-medium);
}
h2 {
    font-family: var(--font-serif);
    font-size: var(--text-h2);
    line-height: var(--text-h2-lh);
    letter-spacing: var(--text-h2-ls);
    font-weight: var(--font-medium);
}
h3 { font-size: var(--text-h3); line-height: var(--text-h3-lh);
     letter-spacing: var(--text-h3-ls); }
h4 { font-size: var(--text-body); }
h5 { font-size: var(--text-body); font-weight: var(--font-medium); }
h6 { font-size: var(--text-sm);   font-weight: var(--font-medium); }

p {
    margin-bottom: var(--space-4);
    color: var(--ink-soft);
}

code, kbd, samp, pre { font-family: var(--font-mono); }

a {
    color: var(--royal-strong);
    text-decoration: none;
    transition: color var(--dur-colour) var(--ease-out-editorial);
}

a:hover { color: var(--royal-hover); }


/* ===== LAYOUT ===== */
.app-layout {
    display: flex;
    min-height: 100vh;
    background: var(--gray-50);
}

/* The shell (§8.1): navy rail -> sticky topbar -> paper canvas. The rail is
   fixed, so the content column is offset by exactly the rail's width and
   follows it when it collapses. */
.main-content {
    flex: 1;
    min-width: 0;
    min-height: 100vh;
    margin-left: var(--rail-w);
}

.rail-hydrated .main-content {
    transition: margin-left 200ms var(--ease-out-editorial);
}

html.sidebar-collapsed .main-content {
    margin-left: var(--rail-w-collapsed);
}

html.no-sidebar .main-content {
    margin-left: 0;
}

@media (max-width: 1024px) {
    .main-content { margin-left: 0; }
    html.sidebar-collapsed .main-content { margin-left: 0; }
}

/* The canvas. 1600px is wide enough for a dense table and narrow enough that
   a paragraph inside it still has to declare its own measure (§5.5). */
.container {
    width: 100%;
    max-width: var(--canvas-max);
    margin: 0 auto;
    padding: var(--space-7) var(--space-6);
}

@media (min-width: 1024px) {
    .container { padding-inline: var(--space-8); }
}

/* ===== PAGE HEADER (§8.4) ========================================
   Four moves, in this order: caps eyebrow naming the AREA, a serif h1
   naming the page, one line of meta context, and right-aligned actions with
   the PRIMARY ACTION LAST — because the eye finishes there.

   Aligned on the baseline (`align-items: flex-end`), not the top, so the
   action row sits on the same line as the title rather than floating above
   the meta line.
   ================================================================= */
.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    column-gap: var(--space-6);
    row-gap: var(--space-4);
    margin-bottom: var(--space-7);
    padding-bottom: var(--space-5);
    border-bottom: 1px solid var(--rule);
}

.page-header-content {
    flex: 1;
    min-width: 200px;
}

/* The eyebrow names the AREA — OVERVIEW, RECRUITMENT, REVENUE — not the
   page. It is the caps label, like everything else at this level. */
.page-eyebrow {
    display: block;
    margin-bottom: var(--space-2);
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink-mute);
}

/* The page title is SERIF. A serif headline over a sans interface is what
   makes a screen read as a document rather than as a dashboard (§5.1). */
.page-title {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: 0;
    font-family: var(--font-serif);
    font-size: var(--text-h1);
    line-height: var(--text-h1-lh);
    letter-spacing: var(--text-h1-ls);
    font-weight: var(--font-medium);
    color: var(--ink);
}

.page-title i,
.page-title svg {
    font-size: 20px;
    width: 22px;
    height: 22px;
    color: var(--ink-mute);
}

/* One line of context — a date, a count, an owner — with faint separators. */
.page-subtitle {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0 var(--space-2);
    max-width: 68ch;
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    color: var(--ink-soft);
}

.page-subtitle .sep { color: var(--ink-faint); }

.page-header-actions {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    gap: var(--space-2);
}

/* ===== SECTION (§8.5) ============================================
   The caps label, a rule that fades to transparent on the right, and an
   optional action parked at the end of it.

   The fading rule is a small thing doing a lot of work: it says "this
   section starts here" without drawing a box, and it is the single reason
   section headers read as editorial rather than administrative.
   ================================================================= */
.section-head {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

.section-title {
    flex-shrink: 0;
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink);
}

.section-rule {
    flex: 1;
    min-width: 24px;
    height: 1px;
    background: linear-gradient(90deg, var(--rule) 0%, transparent 100%);
}

.section-actions {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    gap: 6px;
}

.section-description {
    max-width: 68ch;
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    color: var(--ink-mute);
}

.section-body { margin-top: var(--space-4); }

/* Between top-level page sections: gap-8 on a dashboard, gap-7 on a list. */
.page-sections {
    display: flex;
    flex-direction: column;
    gap: var(--space-7);
    padding-bottom: var(--space-4);
}

.page-sections.is-dashboard { gap: var(--space-8); }

/* ===== SURFACES: .panel and .card are ONE component (§9.3) =========
   `.panel` is the canonical name (see UI_STANDARD.md); `.card` and
   `.content-card` are the historical spellings used across ~200 templates.

   They used to be defined separately and had drifted: .card-body padded
   space-6 while .panel-body padded space-5, and .card-title was gray-800 while
   .panel-title was gray-900. Put an admissions page next to a students page
   and the boxes did not line up. Defining them together is the fix, and
   core/test_design_system.py pins it.

   The retrofit changes three things about all three names at once:

   * NO SHADOW AT REST. Structure is carried by a 1px `--rule` hairline;
     elevation exists only on hover, drag and overlay (Law 3). A resting
     shadow is what turns an editorial page into a dashboard template.
   * Radius is `lg` (10px) — the panel/dialog/menu role in the one-radius-
     per-role ladder (§6.2). It was 16px, which is the command-palette-only
     step.
   * PANEL PADDING IS FIXED and is not to be improvised: header
     `16px 16px 12px`-equivalent (px-4 py-3), body px-4 py-4, footer
     px-4 py-2.5. The footer sits on `--surface-alt`, a small recession that
     closes the panel.

   The panel header title is THE CAPS LABEL, not a heading font. The serif is
   reserved for page and dialog level (§5.1); a serif panel title competes
   with the page title and flattens the hierarchy.
   ================================================================= */
.panel,
.card,
.content-card {
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    margin-bottom: var(--space-5);
    overflow: hidden;
}

/* The interactive variant — a panel that is itself a link or a target.
   This is the ONLY place a panel gains elevation. */
.panel.is-interactive,
.card.is-interactive,
a.panel, a.card {
    transition: box-shadow var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial);
}
.panel.is-interactive:hover,
.card.is-interactive:hover,
a.panel:hover, a.card:hover {
    border-color: var(--rule-strong);
    box-shadow: var(--shadow-raise);
}

.panel-header,
.card-header,
.content-card-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--rule);
    background: var(--surface);
}

.panel-title,
.card-title,
.content-card-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink);
}

.panel-title i,
.panel-title svg,
.card-title i,
.card-title svg,
.content-card-title i,
.content-card-title svg {
    color: var(--ink-mute);
    font-size: 12px;
    width: 14px;
    height: 14px;
}

.panel-subtitle,
.card-subtitle {
    margin: var(--space-1) 0 0;
    font-size: var(--text-xs);
    line-height: var(--text-xs-lh);
    color: var(--ink-mute);
    text-transform: none;
    letter-spacing: normal;
    font-weight: var(--font-normal);
}

.panel-body,
.card-body,
.content-card-body {
    padding: var(--space-4);
}

.panel-footer,
.card-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 10px var(--space-4);
    border-top: 1px solid var(--rule);
    background: var(--surface-alt);
    font-size: var(--text-xs);
    color: var(--ink-mute);
}

.panel-actions,
.card-actions,
.content-card-actions {
    display: flex;
    flex-shrink: 0;
    gap: 6px;
    align-items: center;
}

.panel.compact .panel-body,
.card.compact .card-body {
    padding: var(--space-3);
}

.panel.full-width {
    margin-top: var(--space-5);
}

/* A count beside a panel title: tabular, because it sits in a column of
   other counts, and a tint rather than a solid fill for the same reason a
   badge is a tint. */
.panel-count {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    min-width: 20px;
    padding: 1px 6px;
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--ink-mute);
    font-size: var(--text-label);
    letter-spacing: var(--text-label-ls);
    font-weight: var(--font-semibold);
    text-align: center;
}

.panel-body.no-padding,
.card-body.no-padding {
    padding: 0;
}

/* ===== THE BOXLESS KPI BAND (§9.4) ==============================
   The single most distinctive component in the system, and the one most
   often lost when this language is copied badly.

   A stat is: a 10px letterspaced caps label, a large SERIF TABULAR numeral,
   and a delta line. That is all. No card, no background, no border, no
   shadow, no icon medallion. Neighbours are separated by vertical hairlines
   — the way a newspaper separates columns — and only from `md` up, because
   a hairline between stacked cells reads as a table rather than as columns
   (§8.3).

   `.metrics-grid` / `.stats-row` / `.stats-overview` are the band;
   `.metric-card` / `.stat-card` are one stat inside it. The names are kept
   because ~200 templates are written against them; what changed is that
   they stopped being cards.

   Two details that make it work:
   * `first:padding-left:0` / `last:padding-right:0` — the band is flush
     with the content column at both ends. Without this the row looks inset
     and wrong.
   * The serif numeral is doing half the work of the "annual report, not
     dashboard" feeling. Do not swap it for the sans (§5.6).
   ================================================================= */
.metrics-grid,
.stats-overview,
.stats-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-6);
    row-gap: var(--space-7);
    margin-bottom: var(--space-7);
}

.metrics-grid > *,
.stats-overview > *,
.stats-row > * { min-width: 0; }

/* The `cols-*` modifiers used to set an explicit track count. Above `md` the
   band is a flex row and divides itself, so they only steer the stacked
   layout below it. */
.metrics-grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.metrics-grid.cols-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.metrics-grid.cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.metrics-grid.cols-5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.metrics-grid.cols-6 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* One stat. */
.metric-card,
.stat-card {
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    text-decoration: none;
    color: inherit;
}

.metric-card.large,
.stat-card.large { padding: 0; }

@media (min-width: 768px) {
    .metrics-grid,
    .stats-overview,
    .stats-row {
        display: flex;
        gap: 0;
    }

    .metrics-grid > *,
    .stats-overview > *,
    .stats-row > * {
        flex: 1 1 0;
        padding-inline: var(--space-6);
        /* The vertical hairline that separates columns. */
        border-inline-start: 1px solid var(--rule);
    }

    .metrics-grid > :first-child,
    .stats-overview > :first-child,
    .stats-row > :first-child {
        padding-inline-start: 0;
        border-inline-start: 0;
    }

    .metrics-grid > :last-child,
    .stats-overview > :last-child,
    .stats-row > :last-child {
        padding-inline-end: 0;
    }
}


/* A stat that needs to flag itself does it with its own label, not by
   turning into a tinted card. */
.metric-card.highlight-warning .metric-label,
.metric-card.highlight-warning .stat-label { color: var(--amber-strong); }

/* Any metric tile that draws a visible box on itself (the highlight
   variants — admissions tints them) must also carry inner padding: the
   metrics row keeps its first column flush at inline-start 0, which is
   correct for unboxed figures but puts a BOXED tile's number straight on
   its own border. */
.metric-card.highlight-warning,
.metric-card[class*="highlight-"] {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
}

/* The icon. The system's stat has none — the label already names the thing.
   Rather than delete an icon from ~200 templates, it degrades from a 44px
   filled medallion to a 13px tone glyph riding with the caps label, which
   keeps whatever meaning it carried without reintroducing a box. */
.metric-icon,
.stat-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 13px;
    height: 13px;
    margin-inline-end: 6px;
    font-size: 12px;
    background: none;
    border-radius: 0;
    color: var(--ink-mute);
    vertical-align: -1px;
}

.metric-icon.primary, .stat-icon.primary { color: var(--coral-strong); }
.metric-icon.success, .stat-icon.success { color: var(--teal-strong); }
.metric-icon.warning, .stat-icon.warning { color: var(--amber-strong); }
.metric-icon.danger,  .stat-icon.danger  { color: var(--crimson-strong); }
.metric-icon.info,    .stat-icon.info    { color: var(--royal-strong); }
.metric-icon.gray, .metric-icon.secondary, .metric-icon.neutral,
.stat-icon.gray, .stat-icon.secondary, .stat-icon.neutral { color: var(--ink-mute); }

.metric-content,
.stat-content {
    display: block;
    min-width: 0;
}

/* The figure: serif, tabular, 34px. Any number that sits above or beside
   another number is tabular, or the column jitters as values change. */
.metric-value,
.stat-value {
    display: block;
    margin-top: var(--space-2);
    font-family: var(--font-serif);
    font-size: var(--text-figure);
    line-height: var(--text-figure-lh);
    letter-spacing: var(--text-figure-ls);
    font-weight: var(--font-medium);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    color: var(--ink);
}

/* The label: THE caps label. This is the connective tissue (§5.3). */
.metric-label,
.stat-label {
    display: block;
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink-mute);
}

/* Order matters: the label comes first in the reading order even where the
   markup puts the value first. */
.metric-content .metric-label,
.stat-content .stat-label { order: -1; }
.metric-content, .stat-content { display: flex; flex-direction: column; }
.metric-content .metric-value,
.stat-content .stat-value { order: 0; }

/* The delta line: an arrow, a tabular figure in the delta's tone, and a
   caption. Direction and meaning are separate inputs — override the tone
   when up is bad (cost per lead rising, days-to-offer growing). */
.stat-delta {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: var(--space-2);
    font-size: var(--text-xs);
    line-height: var(--text-xs-lh);
    color: var(--ink-mute);
}

.stat-delta-value {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    font-weight: var(--font-medium);
}

.stat-delta svg { width: 14px; height: 14px; flex-shrink: 0; }

.stat-sublabel {
    font-size: var(--text-xs);
    color: var(--ink-mute);
}

/* A count that flags itself beside a stat — kept as a tint, never a solid. */
.metric-badge {
    display: inline-flex;
    align-items: center;
    margin-inline-start: 6px;
    padding: 1px 6px;
    border: 1px solid var(--amber-line);
    border-radius: var(--radius-sm);
    background: var(--amber-soft);
    color: var(--amber-strong);
    font-size: var(--text-label);
    letter-spacing: var(--text-label-ls);
    font-weight: var(--font-semibold);
    white-space: nowrap;
}

/* ===== FILTER BAR (§9.23) =====
   Filters sit in a RULED STRIP between the page header and the content,
   like a masthead rule. The `border-y` band is the visual signature: as a
   floating white card it read as one more panel competing with the content
   below it, which is exactly the thing this language avoids.

   The behavioural half of §9.23 — filters living in the query string, so a
   narrowed list is a link someone can paste — is a proposal (P1), not part
   of this visual pass. */
.filter-bar {
    padding: var(--space-3) 0;
    margin-bottom: var(--space-6);
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    background: none;
    border-radius: 0;
    box-shadow: none;
}

.filter-form {
    display: flex;
    align-items: flex-end;
    gap: var(--space-4);
    flex-wrap: wrap;
}

.filter-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    /* flex:1 made a lone filter grow to the whole 1120px bar -- a 4-digit
       year select given ~900px on 20 single-filter pages. Fields that
       genuinely want the space opt in with .flex-grow (enhancements.css). */
    flex: 0 1 auto;
    min-width: 200px;
    max-width: 360px;
}

/* Search inputs deserve more room than a narrow select. */
.filter-group input[type="search"],
.filter-group input[type="text"],
.filter-group input[name="search"],
.filter-group input[name="q"] {
    min-width: 260px;
}

/* A filter's label is THE caps label, like every other label in the
   product — 10px at 0.09em, not 12px at 0.05em. Two tracking values half a
   screen apart is exactly how a system stops reading as one. */
.filter-group label {
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink-mute);
}

.filter-actions {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    /* Without wrapping, a two- or three-button action group stayed on one line
       and pushed the filter bar past the viewport on narrow screens. */
    flex-wrap: wrap;
}

/* Filter Tabs — THE filter chip. One selected state, in one direction.
   ---------------------------------------------------------------------------
   FILLED = SELECTED. OUTLINED = AVAILABLE. There is no third reading.

   Pages that hand-rolled chips out of `.btn` variants got this backwards:
   students/welfare/grievance_list and wellbeing_list paint their "Overdue SLA"
   / "Escalated Only" chip a permanently filled btn-danger / btn-warning while
   the chip that IS selected sits there as a quiet btn-outline, so the filter
   that is off looks like the filter that is on. `.btn` cannot express this
   safely because its variants encode SEVERITY, not state -- and a page has to
   choose a variant per chip, which is the choice that goes wrong.

   INTENDED MARKUP (state is the single class `active`; nothing else varies):

     <div class="filter-bar">
       <div class="filter-tabs">
         <a href="?" class="filter-tab{% if not f %} active{% endif %}"
            {% if not f %}aria-current="true"{% endif %}>
           All <span class="tab-count">128</span>   {# count optional #}
         </a>
         <a href="?filter=overdue"
            class="filter-tab{% if f == 'overdue' %} active{% endif %}"
            {% if f == 'overdue' %}aria-current="true"{% endif %}>Overdue</a>
       </div>
     </div>

   Do NOT swap `btn-primary`/`btn-outline` per chip, and do NOT colour a chip
   by what it filters -- severity belongs on the rows, not on the control.
   --------------------------------------------------------------------------- */
.filter-tabs {
    display: flex;
    gap: var(--space-1);
    padding: var(--space-3) var(--space-5);
    margin-bottom: var(--space-4);
    flex-wrap: wrap;
}

.filter-tab {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 6px 14px;
    /* Chips are hit targets, not text links. */
    min-height: 32px;
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    color: var(--gray-500);
    background: transparent;
    /* A visible hairline is what makes "filled" read as ON rather than as
       "this one exists and the others do not". */
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-full);
    text-decoration: none;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                box-shadow var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
    cursor: pointer;
}

.filter-tab:hover {
    color: var(--gray-700);
    background: var(--gray-100);
    border-color: var(--gray-300);
}

/* The selected state is deliberately over-specific. `.filter-tab.active` at
   (0,2,0) was being silently reversed downstream -- admissions.css re-declares
   it as navy text, which lands navy-on-navy once this rule fills the chip, and
   per-app sheets load after this one. Element-qualified and container-scoped
   selectors put the shared answer above anything an app sheet can say without
   deliberately reaching for it. */
.filter-tab.active,
a.filter-tab.active,
button.filter-tab.active,
.filter-tabs .filter-tab.active,
.filter-tab[aria-current],
a.filter-tab[aria-current] {
    background: var(--primary);
    color: var(--white);
    border-color: var(--primary);
    font-weight: var(--font-semibold);
}

.tab-count {
    font-size: var(--text-xs);
    font-weight: var(--font-semibold);
    padding: 1px 6px;
    border-radius: var(--radius-full);
    /* Resting chips sit on white, so a white-alpha pill is invisible there. */
    background: var(--gray-100);
    color: var(--gray-600);
}

.filter-tab.active .tab-count,
a.filter-tab.active .tab-count,
.filter-tabs .filter-tab.active .tab-count,
.filter-tab.active .tab-badge,
a.filter-tab.active .tab-badge,
.filter-tabs .filter-tab.active .tab-badge {
    background: rgba(255, 255, 255, 0.25);
    color: var(--white);
}

/* ===== DASHBOARD CONTENT LAYOUT ===== */
.dashboard-content {
    display: grid;
    grid-template-columns: 1.5fr 1fr;
    gap: var(--space-5);
    margin-bottom: var(--space-5);
}

.dashboard-primary,
.dashboard-secondary {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    /* Grid children default to min-width:auto, so a wide panel inside either
       column refused to shrink and pushed .dashboard-content past the viewport
       (609px on a 390px screen). */
    min-width: 0;
}

@media (max-width: 900px) {
    /* Two columns cannot carry a dashboard on a phone; stack them. */
    .dashboard-content {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ===== ATTENTION LIST ===== */
.attention-list {
    display: flex;
    flex-direction: column;
}

.attention-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--gray-50);
    transition: background var(--dur-colour) var(--ease-out-editorial);
}

.attention-item:last-child {
    border-bottom: none;
}

.attention-item:hover {
    background: var(--gray-50);
}

.attention-avatar {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: var(--font-semibold);
    font-size: var(--text-xs);
    text-transform: uppercase;
    flex-shrink: 0;
    background: var(--primary-50);
    color: var(--primary);
}

.attention-avatar.warning { background: var(--warning-light); color: var(--warning-dark); }
.attention-avatar.info { background: var(--info-light); color: var(--info-dark); }
.attention-avatar.success { background: var(--success-light); color: var(--success); }
.attention-avatar.danger { background: var(--danger-light); color: var(--danger); }

.attention-info {
    flex: 1;
    min-width: 0;
}

.attention-header {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: 2px;
}

.attention-name {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: var(--font-semibold);
    color: var(--gray-900);
}

.attention-name a {
    color: inherit;
    text-decoration: none;
}

.attention-name a:hover {
    color: var(--primary);
}

.attention-course {
    font-size: var(--text-sm);
    color: var(--gray-600);
    margin: 0 0 var(--space-1);
}

.attention-meta {
    display: flex;
    gap: var(--space-3);
    font-size: var(--text-xs);
    color: var(--gray-400);
}

.attention-meta i {
    margin-right: 3px;
}

.attention-actions {
    display: flex;
    gap: var(--space-1);
    flex-shrink: 0;
}

/* ===== PROGRESS AND SCORE BARS (§9.14) ===========================
   The track is the deepest recess in the surface ramp; the fill is the
   BASE tone, because a bar is a graphic rather than text.

   Every one needs role="progressbar" with aria-valuenow/min/max and a
   label — a bar with no accessible value tells a screen-reader user
   nothing at all (§15).

   Band scores by THRESHOLD, not by gradient. The thresholds live once, in
   core.vocabulary.score_tone / attendance_tone, and are imported wherever
   they are needed rather than re-inlined.
   ================================================================= */
.progress,
.progress-track {
    height: 6px;
    overflow: hidden;
    border-radius: var(--radius-full);
    background: var(--surface-sunken);
}

.progress-bar {
    display: block;
    height: 100%;
    border-radius: var(--radius-full);
    background: var(--coral);
    transition: width var(--dur-fill) var(--ease-out-editorial);
}

/* Tone modifiers. The legacy `bg-*` spellings are kept because ~80
   templates write them; each maps onto the base tone. */
.progress-bar.tone-coral,   .progress-bar.bg-primary { background: var(--coral); }
.progress-bar.tone-teal,    .progress-bar.bg-success { background: var(--teal); }
.progress-bar.tone-royal,   .progress-bar.bg-info    { background: var(--royal); }
.progress-bar.tone-amber,   .progress-bar.bg-warning { background: var(--amber); }
.progress-bar.tone-crimson, .progress-bar.bg-danger  { background: var(--crimson); }
.progress-bar.tone-plum                              { background: var(--plum); }
.progress-bar.tone-slate,   .progress-bar.bg-secondary { background: var(--slate); }

/* A progress ROW: label left, `current / goal` right, both baseline
   aligned, with the count in medium ink and the divider slash faint. */
.progress-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: 6px;
    font-size: var(--text-xs);
    color: var(--ink-mute);
}

.progress-row-value {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    font-weight: var(--font-medium);
    color: var(--ink);
}

.progress-row-value .sep { color: var(--ink-faint); font-weight: var(--font-normal); }

/* The compact variant: a fixed label column, the track, and a
   right-aligned tabular figure in the band's tone. */
.score-bar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.score-bar-label {
    width: 112px;
    flex-shrink: 0;
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink-mute);
}

.score-bar .progress,
.score-bar .progress-track { flex: 1; }

.score-bar-value {
    width: 36px;
    flex-shrink: 0;
    text-align: right;
    font-size: var(--text-xs);
    font-weight: var(--font-medium);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* ===== QUICK LINKS ===== */
.quick-links {
    display: flex;
    flex-direction: column;
}

.quick-link {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    color: var(--gray-700);
    text-decoration: none;
    border-bottom: 1px solid var(--gray-50);
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                box-shadow var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
}

.quick-link:last-child {
    border-bottom: none;
}

.quick-link:hover {
    background: var(--primary-50);
    color: var(--primary);
    padding-left: var(--space-5);
}

.quick-link i {
    width: 20px;
    text-align: center;
    color: var(--gray-400);
    transition: color var(--dur-colour) var(--ease-out-editorial);
}

.quick-link:hover i {
    color: var(--primary);
}

/* ===== BUTTONS (§9.1) =====
   Five variants, two sizes.

   Three details that are easy to get subtly wrong and are encoded here:

   1. Solid fills use the `-strong` variant, never the base. `--coral-strong`
      carries white type at 5.12:1; `--coral` would not (Law 2).
   2. The transition names its properties explicitly. `transition: all` animates a button's WIDTH when a spinner swaps in for its label var(--dur-colour) var(--ease-out-editorial),
                and animates layout on anything that moves (§20). 3. Disabled is 45% and pointer-events:none var(--dur-colour) var(--ease-out-editorial),
                so a disabled control is neither clickable nor mistakable for a quiet one. Nothing lifts on hover. `transform: translateY(-1px)` plus a growing shadow is the dashboard-template tell var(--dur-colour) var(--ease-out-editorial); the hairline and a single
   `shadow-raise` do the work instead (Law 3).

   Usage: ONE `primary` per view. `secondary` (teal) is a positive parallel
   action — confirm, publish, approve. `ghost` is the default for anything
   else. `danger` only for irreversible destruction. */
.btn {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    height: 36px;
    padding: 0 14px;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    user-select: none;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                box-shadow var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
}

/* Buttons size their own glyphs rather than each call site passing a size. */
.btn > svg { width: 16px; height: 16px; flex-shrink: 0; }
.btn > i { font-size: 14px; flex-shrink: 0; }

.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: 0.45;
    pointer-events: none;
}

/* Loading keeps the label in place and overlays a spinner, so the button
   never changes width mid-action. */
.btn.is-loading { color: transparent; pointer-events: none; }
.btn.is-loading::after {
    content: '';
    position: absolute;
    width: 14px;
    height: 14px;
    border: 1.5px solid currentColor;
    border-top-color: transparent;
    border-radius: var(--radius-full);
    color: var(--surface);
    animation: btn-spin 0.7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

.btn-sm {
    height: 28px;
    padding: 0 10px;
    gap: 6px;
    font-size: var(--text-xs);
    border-radius: var(--radius-sm);
    /* Floor kept from the pre-retrofit sheet: without it an icon-only
       .btn-sm collapsed under the 24x24 WCAG 2.2 SC 2.5.8 target size, and
       these sit packed side by side in table action columns where the
       standard's spacing exception does not rescue them. 28px is the
       system's `sm` height and clears the floor.

       No min-width here. A flex item's default min-width:auto is what stops
       it shrinking below its own content; replacing that with an explicit
       value let the "Search" button shrink and clip its own label. */
    min-height: 28px;
}
.btn-sm > svg { width: 14px; height: 14px; }
.btn-sm > i { font-size: 12px; }

.btn-lg {
    height: 40px;
    padding: 0 18px;
    font-size: var(--text-body);
    border-radius: var(--radius-md);
}

/* --- primary: the brand. One per view. --- */
.btn-primary,
.btn-accent {
    background: var(--coral-strong);
    border-color: transparent;
    color: var(--on-strong);
}
.btn-primary:hover, .btn-accent:hover,
.btn-primary:active, .btn-accent:active {
    background: var(--coral-hover);
    color: var(--on-strong);
    box-shadow: var(--shadow-raise);
}

/* --- secondary: a positive parallel action. --- */
.btn-success {
    background: var(--teal-strong);
    border-color: transparent;
    color: var(--on-strong);
}
.btn-success:hover, .btn-success:active {
    background: var(--teal-hover);
    color: var(--on-strong);
    box-shadow: var(--shadow-raise);
}

/* --- ghost: the default for everything else. --- */
.btn-secondary,
.btn-outline,
.btn-outline-secondary,
.btn-outline-primary,
.btn-info {
    background: var(--surface);
    border-color: var(--rule);
    color: var(--ink);
}
.btn-secondary:hover,
.btn-outline:hover,
.btn-outline-secondary:hover,
.btn-outline-primary:hover,
.btn-info:hover {
    background: var(--surface-alt);
    border-color: var(--rule-strong);
    color: var(--ink);
    box-shadow: var(--shadow-raise);
}

/* --- quiet: icon buttons inside dense chrome. --- */
.btn-ghost {
    background: transparent;
    border-color: transparent;
    color: var(--ink-soft);
}
.btn-ghost:hover {
    background: var(--surface-alt);
    color: var(--ink);
}

/* --- danger: irreversible destruction only. --- */
.btn-danger {
    background: var(--crimson-strong);
    border-color: transparent;
    color: var(--on-strong);
}
.btn-danger:hover, .btn-danger:active {
    background: var(--crimson-hover);
    color: var(--on-strong);
    box-shadow: var(--shadow-raise);
}

.btn-outline-danger {
    background: var(--surface);
    border-color: var(--crimson-line);
    color: var(--crimson-strong);
}
.btn-outline-danger:hover {
    background: var(--crimson-soft);
    border-color: var(--crimson);
    color: var(--crimson-strong);
}

.btn-warning {
    background: var(--amber-strong);
    border-color: transparent;
    color: var(--on-strong);
}
.btn-warning:hover {
    background: var(--amber-hover);
    color: var(--on-strong);
    box-shadow: var(--shadow-raise);
}

/* Icon-only buttons are square, and every one of them needs an aria-label:
   there is no text for a screen reader to fall back on (§15). */
.btn-icon {
    width: 36px;
    height: 36px;
    padding: 0;
    border-radius: var(--radius-md);
}

.btn-icon.btn-sm {
    width: 28px;
    height: 28px;
    border-radius: var(--radius-sm);
}

/* ===== FORMS (§9.7) ==============================================
   One shared control recipe, four controls built on it.

   FORM LABELS ARE THE CAPS LABEL, not sentence case. This is unusual and it
   is a big part of the look — a form reads like a printed record card
   (§9.7). It is also the same `.label` recipe as section headers, panel
   headers, stat labels and table column headers, which is what makes twenty
   unrelated screens feel like one product (§5.3).

   The focus ring is the global one from the base layer — a double ring that
   reads on any background in either theme — so no control declares its own.

   Invalid state keys off `aria-invalid`, not only off a class, so a field
   that is announced as invalid to a screen reader is always also shown as
   invalid (§15).
   ================================================================= */
.form-group {
    margin-bottom: var(--space-4);
}

.form-label {
    display: block;
    margin-bottom: 6px;
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink-mute);
}

.form-label.required::after {
    content: '*';
    color: var(--coral);
    margin-left: 4px;
}

/* The other required-marker spelling used across templates:
   <label class="form-label">File <span class="required">*</span></label> */
.form-label .required {
    color: var(--coral);
}

.form-control,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="tel"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="url"],
input[type="search"],
select,
textarea {
    width: 100%;
    height: 36px;
    padding: 0 10px;
    font-family: var(--font-sans);
    font-size: var(--text-body);
    line-height: 1.5;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius-md);
    appearance: none;
    transition: border-color var(--dur-colour) var(--ease-out-editorial),
                background-color var(--dur-colour) var(--ease-out-editorial);
}

.form-control:hover,
input:hover,
select:hover,
textarea:hover {
    border-color: var(--rule-strong);
}

/* No per-control focus style: :focus-visible in the base layer paints the
   one global ring. A control that draws its own ends up with two. */
.form-control:focus,
input:focus,
select:focus,
textarea:focus {
    outline: none;
    border-color: var(--rule-strong);
}

.form-control:disabled,
input:disabled,
select:disabled,
textarea:disabled {
    background: var(--surface-alt);
    color: var(--ink-mute);
    cursor: not-allowed;
}

/* Placeholders use --ink-mute (5.08:1 on paper), not --ink-faint. The faint
   step is for disabled glyphs and separator dots; placeholder text is text
   a sighted user is expected to read, so it clears 4.5:1. */
.form-control::placeholder,
input::placeholder,
textarea::placeholder {
    color: var(--ink-mute);
    opacity: 1;
}

.form-control.is-invalid,
input.is-invalid,
select.is-invalid,
[aria-invalid="true"] {
    border-color: var(--crimson);
    background: var(--crimson-soft);
}

/* The chevron is drawn in --ink-mute. It is the one data: URI in the sheet;
   the stroke is written as the token's own value because a data: URI cannot
   reference a custom property, and it is a control affordance rather than a
   brand paint. */
select {
    height: 36px;
    padding: 0 32px 0 10px;
    cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%23586d89' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
    background-position: right 10px center;
    background-repeat: no-repeat;
    background-size: 16px;
}

textarea {
    min-height: 92px;
    height: auto;
    padding: 8px 10px;
    resize: vertical;
}

.form-text,
.form-hint,
.field-help {
    display: block;
    margin-top: 4px;
    font-size: var(--text-xs);
    line-height: var(--text-xs-lh);
    color: var(--ink-mute);
}

.invalid-feedback,
.field-error {
    margin-top: 4px;
    font-size: var(--text-xs);
    color: var(--crimson-strong);
}

/* Form row layout */
.form-row {
    display: flex;
    gap: var(--space-4);
    margin-bottom: var(--space-4);
}

.form-row .form-group {
    flex: 1;
    min-width: 0;
    margin-bottom: 0;
}

/* Password eye toggle */
.password-input-wrap {
    position: relative;
}
.password-input-wrap .form-control {
    padding-right: 40px;
}
.pw-eye {
    position: absolute;
    right: 1px;
    top: 1px;
    bottom: 1px;
    width: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    background: transparent;
    color: var(--ink-faint);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    transition: color var(--dur-colour) var(--ease-out-editorial);
}
.pw-eye:hover { color: var(--ink); }
.pw-eye.active { color: var(--coral-strong); }

/* Input group */
.input-group {
    display: flex;
    align-items: stretch;
}

.input-group .form-control,
.input-group input {
    border-radius: var(--radius-md) 0 0 var(--radius-md);
}

.input-group-append {
    display: flex;
}

.input-group-append .btn {
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    border-left: none;
}

/* A submit button placed straight after the last field used to hug it —
   every message/reply form had its Send pressed against the textarea. Any
   button that directly follows a form-group gets the standard field gap. */
form .form-group + .btn,
form .form-group + button[type="submit"] {
    margin-top: var(--space-3);
}

/* Native file inputs: the browser's default "Choose file" button looked like
   a foreign control inside our forms. Style the file-selector button as a
   small outline button so file fields match every other field. */
input[type="file"].form-control {
    padding: 6px 10px;
    line-height: 1.4;
}
input[type="file"].form-control::file-selector-button {
    margin-right: 10px;
    padding: 4px 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--ink);
    font: inherit;
    font-size: var(--text-xs);
    font-weight: var(--font-medium);
    cursor: pointer;
    transition: background var(--dur-colour) var(--ease-out-editorial);
}
input[type="file"].form-control::file-selector-button:hover {
    background: var(--surface);
}

/* Checkbox and radio: the brand fill when checked, so a checked box is the
   one place the base coral appears as a small solid — it is a graphic
   carrying a white tick, not coloured text. */
.form-check {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: 6px;
}

.form-check-input {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    cursor: pointer;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial);
}

.form-check-input[type="radio"] {
    border-radius: var(--radius-full);
}

.form-check-input:checked {
    background: var(--coral);
    border-color: var(--coral);
}

.form-check-label {
    font-size: var(--text-sm);
    color: var(--ink-soft);
    cursor: pointer;
}

/* A switch: on = teal (positive), off = the neutral recess. */
.form-switch {
    position: relative;
    width: 36px;
    height: 20px;
    flex-shrink: 0;
    border: 1px solid var(--rule-strong);
    border-radius: var(--radius-full);
    background: var(--surface-alt);
    cursor: pointer;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial);
}

.form-switch::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 2px;
    width: 14px;
    height: 14px;
    margin-top: -7px;
    border-radius: var(--radius-full);
    background: var(--surface);
    box-shadow: var(--shadow-raise);
    transition: transform var(--dur-colour) var(--ease-out-editorial);
}

.form-switch[aria-checked="true"],
.form-switch:checked {
    background: var(--teal);
    border-color: var(--teal);
}

.form-switch[aria-checked="true"]::after,
.form-switch:checked::after { transform: translateX(16px); }

/* Form sections */
.form-section {
    margin-bottom: var(--space-8);
}

/* A section heading has to out-rank the fields it introduces. It did not:
   measured on subject_create, the gap above a heading was ~43px against ~29px
   between two fields, so the heading read as just another row in the stack.
   The interval above a heading is now the section's own bottom margin PLUS
   equal padding -- roughly double -- with a hairline at the join so the break
   survives even when a section is only one field tall.

   Padding rather than margin-top on purpose: margins collapse against the
   previous section inside a block parent but not inside a flex one, which
   would make the interval inconsistent between forms.

   `:not(.panel):not(.card)` exempts the forms that use one panel per section
   (agents/application_form writes `class="panel form-section"`) -- there the
   panel border already is the separator and this would draw a second line. */
.form-section + .form-section:not(.panel):not(.card) {
    padding-top: var(--space-8);
    border-top: 1px solid var(--rule);
}

/* Without a subtitle the fields sat 4px under the heading -- that 4px exists
   to keep a subtitle glued to its title, not to separate a heading from a
   field. */
.form-section-title + .form-grid,
.form-section-title + .form-row,
.form-section-title + .form-group,
.form-section-title + .form-row-2 {
    margin-top: var(--space-4);
}

/* A section header inside a form is the caps label with the fading rule —
   the same Section pattern the page uses (§8.5), one level down. */
.form-section-title {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin: 0 0 4px;
    font-family: var(--font-sans);
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink);
}

.form-section-title::after {
    content: '';
    flex: 1;
    min-width: 24px;
    height: 1px;
    background: linear-gradient(90deg, var(--rule) 0%, transparent 100%);
}

.form-section-subtitle {
    max-width: 68ch;
    margin: 0 0 var(--space-4);
    font-size: var(--text-xs);
    color: var(--ink-mute);
}

/* Form inline / horizontal */
.form-inline {
    display: flex;
    align-items: flex-end;
    gap: var(--space-3);
    flex-wrap: wrap;
}

.form-inline .form-group {
    margin-bottom: 0;
}

/* The form actions row. Cancel (ghost) then confirm (primary), right
   aligned — the primary action goes LAST, because the eye finishes there. */
.form-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    padding-top: var(--space-4);
    margin-top: var(--space-5);
    border-top: 1px solid var(--rule);
}

/* ===== TABLES (§9.5) =====
   The header is the caps label over a `border-b-2` double-weight rule. That
   rule is what separates header from body WITHOUT a fill — the old grey
   header band is a dashboard tell, and it fights the paper canvas.

   Zebra striping is not used. The hairline is enough.

   Numeric columns are right-aligned on both `th` and `td`, and tabular on
   the cell: any number that sits above another number has to be tabular or
   the column jitters as values change (§5.4).

   The page body never scrolls horizontally — a wide table scrolls inside
   its own container (§8.3). That is what `.table-container` is for. */
.table-container,
.table-wrapper,
.table-responsive {
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.table,
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
    line-height: var(--text-sm-lh);
}

.table th,
.data-table th {
    padding: 10px var(--space-3);
    text-align: left;
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
    color: var(--ink-mute);
    background: var(--surface);
    border-bottom: 2px solid var(--rule);
    white-space: nowrap;
    position: sticky;
    top: 0;
    z-index: 1;
}

.table th:first-child,
.data-table th:first-child { padding-left: var(--space-4); }
.table th:last-child,
.data-table th:last-child { padding-right: var(--space-4); }

.table td,
.data-table td {
    padding: 10px var(--space-3);
    border-bottom: 1px solid var(--rule);
    color: var(--ink-soft);
    vertical-align: middle;
}

.table td:first-child,
.data-table td:first-child { padding-left: var(--space-4); }
.table td:last-child,
.data-table td:last-child { padding-right: var(--space-4); }

/* Numeric columns. Mark the cell `.num` (or the column `.text-right`) and
   the figure lines up with the one above it. */
.table td.num, .data-table td.num,
.table th.num, .data-table th.num,
.table td.text-right, .data-table td.text-right,
.table th.text-right, .data-table th.text-right {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

.table tbody tr,
.data-table tbody tr {
    transition: background-color var(--dur-colour) var(--ease-out-editorial);
}

.table tbody tr:hover,
.data-table tbody tr:hover {
    background: var(--surface-alt);
}

/* A selected row is royal — selection is informational, not brand (§4.4). */
.table tbody tr[aria-selected="true"],
.data-table tbody tr[aria-selected="true"],
.table tbody tr.is-selected,
.data-table tbody tr.is-selected {
    background: var(--royal-soft);
}

.table tbody tr:last-child td,
.data-table tbody tr:last-child td {
    border-bottom: none;
}

.data-table tfoot td {
    padding: 10px var(--space-3);
    font-weight: var(--font-semibold);
    background: var(--surface-alt);
    border-top: 2px solid var(--rule);
    color: var(--ink);
}

/* Sortable headers are a <button> inside the <th>, with aria-sort on the
   th. The glyph is 40% opacity when unsorted and takes the brand tone when
   active, so the sorted column is findable at a glance. */
.table th[aria-sort] button,
.data-table th[aria-sort] button {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
}

.table th[aria-sort] svg,
.data-table th[aria-sort] svg,
.table th[aria-sort] i,
.data-table th[aria-sort] i { opacity: 0.4; }

.table th[aria-sort="ascending"] svg,
.table th[aria-sort="descending"] svg,
.data-table th[aria-sort="ascending"] svg,
.data-table th[aria-sort="descending"] svg,
.table th[aria-sort="ascending"] i,
.table th[aria-sort="descending"] i,
.data-table th[aria-sort="ascending"] i,
.data-table th[aria-sort="descending"] i {
    opacity: 1;
    color: var(--coral);
}

/* On a numeric column the sort control reverses so the glyph sits left of
   the right-aligned label. */
.table th.num[aria-sort] button,
.data-table th.num[aria-sort] button { flex-direction: row-reverse; }

.table-actions,
.action-buttons {
    display: flex;
    gap: 6px;
    justify-content: flex-end;
    align-items: center;
}

/* Action icon buttons in a table row: the `quiet` button variant. Every one
   needs an aria-label — there is no text to fall back on. */
.action-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--ink-faint);
    font-size: var(--text-xs);
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
}

.action-btn > svg { width: 14px; height: 14px; }

.action-btn:hover {
    background: var(--surface-alt);
    border-color: var(--rule);
    color: var(--ink);
}

.action-btn.edit:hover    { background: var(--coral-soft);   border-color: var(--coral-line);   color: var(--coral-strong); }
.action-btn.view:hover    { background: var(--royal-soft);   border-color: var(--royal-line);   color: var(--royal-strong); }
.action-btn.success:hover { background: var(--teal-soft);    border-color: var(--teal-line);    color: var(--teal-strong); }
.action-btn.delete:hover,
.action-btn.danger:hover  { background: var(--crimson-soft); border-color: var(--crimson-line); color: var(--crimson-strong); }
.action-btn.warning:hover { background: var(--amber-soft);   border-color: var(--amber-line);   color: var(--amber-strong); }


/* ===== TABLE CELL PATTERNS =====
   The two shapes a data table needs over and over: a record's identity
   (avatar + primary link + a quieter second line) and a cell that must not
   be allowed to widen the table. Both used to be re-invented per app —
   `.applicant-cell`, `.agent-cell`, `.staff-grid-info` — each with its own
   gap and its own idea of how the second line should look. */
.cell-identity {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

.cell-identity-text {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
}

.cell-identity-text > a {
    color: var(--ink);
    font-weight: var(--font-medium);
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

.cell-identity-text > a:hover { color: var(--coral-strong); }

/* A cell that would otherwise set the table's width. `max-width` on a cell
   only bites once the table is laid out, so the ellipsis lives here rather
   than on the text inside it. */
.cell-truncate {
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}


/* An overlapping avatar stack — "roughly how many, and is anyone I know in
   there" in the width of one table cell. `ring` in --surface so the
   overlap reads as depth rather than as a smudge (§9.13). */
.avatar-stack {
    display: flex;
    align-items: center;
}

.avatar-stack > a,
.avatar-stack > .avatar {
    margin-inline-start: -6px;
    box-shadow: 0 0 0 2px var(--surface);
    border-radius: var(--radius-full);
    line-height: 0;
    transition: transform var(--dur-colour) var(--ease-out-editorial);
}

.avatar-stack > :first-child { margin-inline-start: 0; }

/* The one that is hovered comes forward, so a stack is still browsable. */
.avatar-stack > a:hover { transform: translateY(-2px); z-index: 1; }

.avatar-stack-more {
    margin-inline-start: var(--space-2);
    font-size: var(--text-xs);
    color: var(--ink-mute);
}

/* A leading glyph in a table cell's identity block, where the record has no
   avatar — a department, a course, a classroom. */
.cell-glyph {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    color: var(--ink-faint);
}


/* ===== COUNT LIST (§9.14 ProgressRow) =====
   "Label left, `current / goal` right, both baseline-aligned."

   The shape a breakdown always wants: seven departments and their head
   counts, five roles and theirs. Every one of those was being drawn as a
   grid of boxed tiles, which is the most expensive possible way to show a
   name and a number — a tile per row, a border per tile, and the numbers
   landing in a different place on each line so they cannot be compared
   without hunting. Rows put every count in one column.

   Hairlines between, nothing around: this is a list inside a panel that
   already has a border. */
.count-list {
    display: flex;
    flex-direction: column;
}

.count-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding: 10px var(--space-4);
    border-bottom: 1px solid var(--rule);
    color: var(--ink-soft);
    font-size: var(--text-sm);
    text-decoration: none;
}

.count-row:last-child { border-bottom: none; }

a.count-row {
    transition: background-color var(--dur-colour) var(--ease-out-editorial);
}
a.count-row:hover { background: var(--surface-alt); }

.count-row-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Serif and tabular, because these are numbers a reader compares against
   the number on the line below. */
.count-row-value {
    flex-shrink: 0;
    font-family: var(--font-serif);
    font-size: var(--text-h3);
    font-weight: var(--font-medium);
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}

.count-row.is-zero .count-row-value { color: var(--ink-faint); }

/* ===== BADGES (§9.2) =====
   A badge is always the TINT + TINT-BORDER + STRONG-TEXT triple. Never a
   solid fill: solid badges shout, and in a table of forty rows they turn
   the page into confetti (§20). The optional 5px dot uses the *base* tone
   — it is a graphic, not text.

   Radius is `sm` (4px), not a pill: 4px is the chip/badge role in the one-
   radius-per-role ladder (§6.2). The text is the 10px caps `.label` at the
   small size, which is what makes a table of statuses read as one system.

   Tones come from core.vocabulary via {% badge %} — a template never picks
   a badge colour by hand. */
.badge {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    gap: 6px;
    height: 22px;
    padding: 0 8px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    font-weight: var(--font-medium);
    line-height: 1;
    white-space: nowrap;
    background: var(--surface-alt);
    color: var(--ink-mute);
}

.badge-sm {
    height: 18px;
    padding: 0 6px;
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
}

/* The 5px dot: the base tone, because it is a graphic. */
.badge-dot {
    width: 5px;
    height: 5px;
    flex-shrink: 0;
    border-radius: var(--radius-full);
}

/* One rule per tone. Nothing else in the product decides a badge colour. */
.badge-coral   { background: var(--coral-soft);   border-color: var(--coral-line);   color: var(--coral-strong); }
.badge-teal    { background: var(--teal-soft);    border-color: var(--teal-line);    color: var(--teal-strong); }
.badge-royal   { background: var(--royal-soft);   border-color: var(--royal-line);   color: var(--royal-strong); }
.badge-amber   { background: var(--amber-soft);   border-color: var(--amber-line);   color: var(--amber-strong); }
.badge-crimson { background: var(--crimson-soft); border-color: var(--crimson-line); color: var(--crimson-strong); }
.badge-plum    { background: var(--plum-soft);    border-color: var(--plum-line);    color: var(--plum-strong); }
.badge-slate   { background: var(--slate-soft);   border-color: var(--slate-line);   color: var(--slate-strong); }
.badge-ink     { background: var(--surface-alt);  border-color: var(--ink-line);     color: var(--ink); }

/* The previous semantic names, pointed at their tone. ~300 templates were
   written against these; each one is retired as its template is migrated to
   {% badge %}. The mapping is the conventional one from §4.4. */
.badge-primary   { background: var(--coral-soft);   border-color: var(--coral-line);   color: var(--coral-strong); }
.badge-success   { background: var(--teal-soft);    border-color: var(--teal-line);    color: var(--teal-strong); }
.badge-warning   { background: var(--amber-soft);   border-color: var(--amber-line);   color: var(--amber-strong); }
.badge-danger    { background: var(--crimson-soft); border-color: var(--crimson-line); color: var(--crimson-strong); }
.badge-info      { background: var(--royal-soft);   border-color: var(--royal-line);   color: var(--royal-strong); }
.badge-secondary { background: var(--slate-soft);   border-color: var(--slate-line);   color: var(--slate-strong); }

/* Status badges — a badge that carries its own dot. Identity is never by
   colour alone (§15), so the dot never appears without its label. */
.status-badge {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    gap: 6px;
    height: 22px;
    padding: 0 8px;
    border: 1px solid var(--slate-line);
    border-radius: var(--radius-sm);
    background: var(--slate-soft);
    color: var(--slate-strong);
    font-size: var(--text-xs);
    font-weight: var(--font-medium);
    line-height: 1;
    white-space: nowrap;
}

.status-badge::before {
    content: '';
    width: 5px;
    height: 5px;
    flex-shrink: 0;
    border-radius: var(--radius-full);
    background: currentColor;
}

.status-active,   .status-badge.active   { background: var(--teal-soft);   border-color: var(--teal-line);   color: var(--teal-strong); }
.status-inactive, .status-badge.inactive { background: var(--slate-soft);  border-color: var(--slate-line);  color: var(--slate-strong); }
.status-pending,  .status-badge.pending  { background: var(--amber-soft);  border-color: var(--amber-line);  color: var(--amber-strong); }

/* Status chips — the same triple without the dot, for a value that is
   already unambiguous in context. */
.status-chip {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    height: 22px;
    padding: 0 8px;
    border: 1px solid var(--slate-line);
    border-radius: var(--radius-sm);
    background: var(--slate-soft);
    color: var(--slate-strong);
    font-size: var(--text-xs);
    font-weight: var(--font-medium);
    line-height: 1;
    white-space: nowrap;
}

.status-chip.warning, .status-chip.status-warning, .status-chip.pending,
.status-chip.new, .status-chip.partial, .status-chip.on_leave,
.status-chip.on-leave, .status-chip.expiring {
    background: var(--amber-soft); border-color: var(--amber-line); color: var(--amber-strong);
}
.status-chip.info, .status-chip.status-info, .status-chip.inreview,
.status-chip.submitted, .status-chip.in_review {
    background: var(--royal-soft); border-color: var(--royal-line); color: var(--royal-strong);
}
/* `.approved` and `.completed` lived only in hr.css / students.css while all
   their siblings lived here, so on any page that did not load those sheets the
   positive states rendered neutral grey while the red and amber states worked
   -- "Completed" read as the least significant state on the row. */
.status-chip.success, .status-chip.status-success, .status-chip.accepted,
.status-chip.active, .status-chip.paid, .status-chip.approved,
.status-chip.completed, .status-chip.verified, .status-chip.graduated,
.status-chip.closed {
    background: var(--teal-soft); border-color: var(--teal-line); color: var(--teal-strong);
}
.status-chip.danger, .status-chip.status-danger, .status-chip.rejected,
.status-chip.suspended, .status-chip.overdue {
    background: var(--crimson-soft); border-color: var(--crimson-line); color: var(--crimson-strong);
}
.status-chip.inactive, .status-chip.status-neutral, .status-chip.withdrawn {
    background: var(--slate-soft); border-color: var(--slate-line); color: var(--slate-strong);
}

/* Code badges — machine strings, so mono (Law 5). */
.code-badge {
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 6px;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: var(--font-medium);
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--ink-mute);
    letter-spacing: -0.01em;
}

.code-badge.primary {
    background: var(--coral-soft);
    border-color: var(--coral-line);
    color: var(--coral-strong);
}

/* ===== ALERTS =====
   The same tint + tint-border + strong-text triple as a badge, one size up.
   An alert is a badge that got a sentence: keeping the two on the same
   construction is what stops "warning" meaning one colour in a row and a
   different one in a banner.

   Radius is `md` (6px) — it is a control-sized block inside a page, not a
   panel. */
.alert {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: 10px var(--space-3);
    margin-bottom: var(--space-4);
    border: 1px solid var(--slate-line);
    border-radius: var(--radius-md);
    background: var(--slate-soft);
    color: var(--slate-strong);
    font-size: var(--text-sm);
    line-height: var(--text-sm-lh);
}

.alert i,
.alert svg {
    flex-shrink: 0;
    margin-top: 2px;
    width: 16px;
    height: 16px;
    font-size: 14px;
}

.alert-heading {
    display: block;
    margin-bottom: 2px;
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
}

.alert-success { background: var(--teal-soft);    border-color: var(--teal-line);    color: var(--teal-strong); }
.alert-warning { background: var(--amber-soft);   border-color: var(--amber-line);   color: var(--amber-strong); }
.alert-danger  { background: var(--crimson-soft); border-color: var(--crimson-line); color: var(--crimson-strong); }
.alert-info    { background: var(--royal-soft);   border-color: var(--royal-line);   color: var(--royal-strong); }

/* ===== SEARCH BOX ===== */
.search-box {
    position: relative;
    display: flex;
    align-items: center;
}

.search-box i {
    position: absolute;
    left: var(--space-3);
    color: var(--gray-400);
    font-size: var(--text-sm);
    pointer-events: none;
}

.search-input {
    padding: 8px 12px 8px 36px;
    font-size: var(--text-sm);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
    background: var(--gray-50);
    min-width: 220px;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                box-shadow var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
    height: 38px;
}

.search-input:focus {
    outline: none;
    background: var(--white);
    border-color: var(--primary);
    box-shadow: 0 0 0 3px rgba(30, 58, 95, 0.1);
}

/* ===== MOBILE HEADER ===== */
.mobile-header {
    display: none;
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    background: var(--white);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--gray-200);
    align-items: center;
    gap: var(--space-3);
}

.mobile-menu-btn {
    width: 40px;
    height: 40px;
    border: none;
    background: var(--gray-100);
    border-radius: var(--radius-md);
    color: var(--gray-700);
    font-size: var(--text-lg);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                box-shadow var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
}

.mobile-menu-btn:hover {
    background: var(--gray-200);
}

.mobile-header-brand {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.mobile-header-brand img {
    height: 28px;
    width: auto;
}

.mobile-header-brand span {
    font-weight: var(--font-semibold);
    color: var(--primary);
}

/* ===== EMPTY STATE (§9.15) =======================================
   A hairline illustration, a SERIF h2, ONE sentence, and ONE concrete
   action. If the description needs two sentences the empty state is doing
   too much; if the action is "Learn more" it is not an action.

   The illustrations are hairline DIAGRAMS, not pictures: each is drawn on a
   120x84 field in `currentColor` at 1px, so it inherits the surrounding
   text colour and can never fight the palette. They are rendered by
   {% empty_illustration %} in core/templatetags/design.py.

   The old 80px tinted circle behind a Font Awesome glyph is gone — that
   medallion is the one shape this system reserves for a full-page status
   (§9.24), and using it for "nothing here" made an empty list look like an
   error.
   ================================================================= */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: var(--space-16) var(--space-6);
    text-align: center;
}

.empty-state.compact { padding: var(--space-10) var(--space-6); }

.empty-icon {
    display: block;
    width: 120px;
    height: 84px;
    margin: 0 auto var(--space-6);
    color: var(--ink-faint);
    background: none;
    border-radius: 0;
}

.empty-icon svg { width: 120px; height: 84px; display: block; }

/* Templates that still carry a Font Awesome glyph degrade to a bare
   hairline-weight glyph rather than a filled medallion. */
.empty-icon i {
    display: block;
    line-height: 84px;
    font-size: 34px;
    font-weight: 300;
}

.empty-title {
    margin: 0;
    font-family: var(--font-serif);
    font-size: var(--text-h2);
    line-height: var(--text-h2-lh);
    letter-spacing: var(--text-h2-ls);
    font-weight: var(--font-medium);
    color: var(--ink);
}

.empty-description,
.empty-state-text {
    max-width: 46ch;
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    line-height: var(--text-sm-lh);
    color: var(--ink-mute);
}

.empty-description a,
.empty-state-text a {
    color: var(--royal-strong);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.empty-state .btn,
.empty-state .empty-action { margin-top: var(--space-5); }

/* ===== SKELETON (§9.16) ==========================================
   A loading state must be shaped like the content that is ARRIVING, with
   UNEVEN dimensions. Identical bars read as a loader; uneven ones read as
   data. Never a centred spinner over a blank area (§14).

   Each skeleton is built from the same constants as the real view — the
   same column count, the same row height — so the two cannot drift apart.
   Wrap in role="status" with an aria-label.

   The shimmer is one of only three things in the entire product allowed to
   loop (§11); it stops entirely under prefers-reduced-motion via the
   global override in the base layer.
   ================================================================= */
.skeleton {
    display: block;
    height: 12px;
    border-radius: var(--radius-sm);
    background: linear-gradient(90deg,
        var(--surface-alt) 0%, var(--rule) 45%, var(--surface-alt) 90%);
    background-size: 200% 100%;
    animation: shimmer 1.6s linear infinite;
}

.skeleton-text  { height: 12px; }
.skeleton-line  { height: 10px; margin-top: 8px; }
.skeleton-title { height: 20px; }
.skeleton-figure { height: 34px; }
.skeleton-block { height: 100%; border-radius: var(--radius-md); }

/* The six uneven widths, cycled by (row + column) so no two neighbouring
   cells match. This is the difference between a loader and a preview. */
.skeleton-w1 { width: 75%; }
.skeleton-w2 { width: 50%; }
.skeleton-w3 { width: 66%; }
.skeleton-w4 { width: 83%; }
.skeleton-w5 { width: 33%; }
.skeleton-w6 { width: 60%; }

.skeleton-row {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: 10px var(--space-4);
    border-bottom: 1px solid var(--rule);
}

.skeleton-row > * { flex: 1; }
.skeleton-row:last-child { border-bottom: none; }

.skeleton-avatar {
    width: 32px;
    height: 32px;
    flex: 0 0 32px;
    border-radius: var(--radius-full);
}

/* ===== STATUS MEDALLION (§9.24) ==================================
   The system's one full-page status mark: a 48px tinted circle with a
   `-line` border and a base-tone glyph. Reused by the error, forbidden and
   not-found pages. ALWAYS accompanied by the reference code in mono — that
   is what turns a support conversation from "it broke" into something
   actionable.
   ================================================================= */
.status-page {
    display: flex;
    flex-direction: column;
    align-items: center;
    max-width: 28rem;
    margin: 0 auto;
    padding: var(--space-16) var(--space-6);
    text-align: center;
}

.status-medallion {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    border: 1px solid var(--slate-line);
    border-radius: var(--radius-full);
    background: var(--slate-soft);
    color: var(--slate);
}

.status-medallion svg { width: 20px; height: 20px; }
.status-medallion i { font-size: 18px; }

.status-medallion.tone-crimson { border-color: var(--crimson-line); background: var(--crimson-soft); color: var(--crimson); }
.status-medallion.tone-amber   { border-color: var(--amber-line);   background: var(--amber-soft);   color: var(--amber); }
.status-medallion.tone-royal   { border-color: var(--royal-line);   background: var(--royal-soft);   color: var(--royal); }
.status-medallion.tone-teal    { border-color: var(--teal-line);    background: var(--teal-soft);    color: var(--teal); }

.status-page-title {
    margin: var(--space-5) 0 0;
    font-family: var(--font-serif);
    font-size: var(--text-h2);
    line-height: var(--text-h2-lh);
    letter-spacing: var(--text-h2-ls);
    font-weight: var(--font-medium);
    color: var(--ink);
}

.status-page-body {
    max-width: 46ch;
    margin: var(--space-2) 0 0;
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--ink-soft);
}

.status-reference {
    margin-top: var(--space-4);
    padding: 4px 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--ink-mute);
}

.status-page .btn { margin-top: var(--space-5); }

/* ===== UTILITY CLASSES ===== */
.text-primary { color: var(--primary) !important; }
.text-accent { color: var(--accent) !important; }
.text-success { color: var(--success) !important; }
.text-warning { color: var(--warning) !important; }
.text-danger { color: var(--danger) !important; }
.text-muted { color: var(--gray-500) !important; }

.bg-primary { background-color: var(--primary) !important; }
.bg-accent { background-color: var(--accent) !important; }
.bg-success { background-color: var(--success) !important; }
.bg-warning { background-color: var(--warning) !important; }
.bg-danger { background-color: var(--danger) !important; }

.text-sm { font-size: var(--text-sm) !important; }
.text-xs { font-size: var(--text-xs) !important; }
.text-lg { font-size: var(--text-lg) !important; }

.font-medium { font-weight: var(--font-medium) !important; }
.font-semibold { font-weight: var(--font-semibold) !important; }
.font-bold { font-weight: var(--font-bold) !important; }

.text-center { text-align: center !important; }
.text-right { text-align: right !important; }

.mb-0 { margin-bottom: 0 !important; }
.mb-2 { margin-bottom: var(--space-2) !important; }
.mb-4 { margin-bottom: var(--space-4) !important; }
.mb-6 { margin-bottom: var(--space-6) !important; }
.mb-8 { margin-bottom: var(--space-8) !important; }
.mb-xl { margin-bottom: var(--space-12) !important; }

.mt-0 { margin-top: 0 !important; }
.mt-2 { margin-top: var(--space-2) !important; }
.mt-4 { margin-top: var(--space-4) !important; }

.p-4 { padding: var(--space-4) !important; }
.p-6 { padding: var(--space-6) !important; }

.d-flex { display: flex !important; }
.d-block { display: block !important; }
.d-none { display: none !important; }

.align-items-center { align-items: center !important; }
.justify-content-between { justify-content: space-between !important; }
.gap-2 { gap: var(--space-2) !important; }
.gap-4 { gap: var(--space-4) !important; }

.w-100 { width: 100% !important; }

.grid {
    display: grid;
}

.grid-cols-2 {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-6);
}

.grid-cols-3 {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-6);
}

.grid-cols-4 {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-6);
}

/* ===== PAGINATION =====
   One pager. There were four: `.pagination` here, `.pagination-bar` in
   enhancements, `.pagination-container`/`-link` in hr.css and
   `.pagination-info`/`-controls` in admissions.css. None offered page
   NUMBERS, so every list in the product could only be walked one page at a
   time — and because the old `.pagination span` rule put a border on every
   child, the "Page 1 of 7" caption rendered as a disabled-looking button
   sitting between the arrows.

   The band echoes the filter bar at the other end of the list: a hairline
   rule, the count on the left as a caps label, the controls on the right.
   Nothing floats. */
.pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--rule);
}

.pager-count {
    margin: 0;
    color: var(--ink-mute);
    font-variant-numeric: tabular-nums;
}

.pager-controls {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* The `ghost` button at the small size (§9.1). Square for the arrows;
   page numbers get a little breathing room and a tabular figure so the
   control row does not jitter between 9 and 10. */
.pager-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    height: 28px;
    padding: 0 6px;
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink-soft);
    font-size: var(--text-xs);
    font-variant-numeric: tabular-nums;
    text-decoration: none;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
}

.pager-btn > svg { width: 14px; height: 14px; }

a.pager-btn:hover {
    background: var(--surface-alt);
    border-color: var(--rule-strong);
    color: var(--ink);
}

/* The current page is a TINT, not a solid navy fill: it is a marker in a
   row of controls, not a call to action, and a solid block here pulls the
   eye harder than the records above it (§9.2). */
.pager-btn.is-current {
    background: var(--coral-soft);
    border-color: var(--coral-line);
    color: var(--coral-strong);
    font-weight: var(--font-semibold);
}

/* Disabled ends stay in place rather than disappearing, so the control row
   does not change width as you walk through the pages. */
.pager-btn.is-disabled {
    opacity: 0.45;
    pointer-events: none;
}

.pager-gap {
    padding: 0 2px;
    color: var(--ink-faint);
    font-size: var(--text-xs);
}

@media (max-width: 640px) {
    /* On a phone the numbered pages do not fit beside the count. The arrows
       and the current page are enough to keep moving. */
    .pager-page:not(.is-current) { display: none; }
    .pager-gap { display: none; }
}

/* ===== DIALOG (§9.8) =============================================
   The backdrop is NAVY at 35% with a 2px blur — not black at 50%. It keeps
   the overlay inside the brand family; a black scrim on a warm paper canvas
   reads as dirty for the same reason a black shadow does (§4.2).

   The dialog title is SERIF: page level and dialog level are the two places
   the serif speaks (§5.1). The footer sits on the recessed `--surface-alt`
   band with cancel (ghost) then confirm (primary) right-aligned — the
   primary action last, because the eye finishes there.

   Modals replace context; drawers preserve it. A DETAIL RECORD belongs in
   the drawer below, not here (§9.9).
   ================================================================= */
.modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal-backdrop);
    background: rgb(var(--ink-rgb) / 0.35);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur-fade) var(--ease-out-editorial),
                visibility var(--dur-fade) var(--ease-out-editorial);
}

.modal-backdrop.active {
    opacity: 1;
    visibility: visible;
}

.modal {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: var(--z-modal);
    display: flex;
    flex-direction: column;
    width: 90%;
    max-width: 520px;
    max-height: 90vh;
    overflow: hidden;
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-float);
    opacity: 0;
    visibility: hidden;
    transform: translate(-50%, -50%) translateY(6px);
    transition: opacity var(--dur-rise) var(--ease-out-editorial),
                transform var(--dur-rise) var(--ease-out-editorial),
                visibility var(--dur-rise) var(--ease-out-editorial);
}

.modal.active {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, -50%) translateY(0);
}

/* The three dialog widths (§8.1). */
.modal.modal-sm { max-width: 400px; }
.modal.modal-lg { max-width: 720px; }

.modal-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-5) var(--space-5) var(--space-3);
    border-bottom: 0;
}

.modal-title {
    margin: 0;
    font-family: var(--font-serif);
    font-size: var(--text-h2);
    line-height: var(--text-h2-lh);
    letter-spacing: var(--text-h2-ls);
    font-weight: var(--font-medium);
    color: var(--ink);
}

.modal-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    margin: -4px -4px 0 0;
    flex-shrink: 0;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--ink-faint);
    cursor: pointer;
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
}

.modal-close:hover {
    background: var(--surface-alt);
    color: var(--ink);
}

.modal-body {
    flex: 1;
    min-height: 0;
    max-height: 60vh;
    overflow-y: auto;
    padding: 4px var(--space-5);
    font-size: var(--text-body);
    color: var(--ink-soft);
}

.modal-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-4);
    padding: var(--space-3) var(--space-5);
    border-top: 1px solid var(--rule);
    background: var(--surface-alt);
}

/* ===== DRAWER (§9.9) =============================================
   Detail without losing your place. A drawer is rendered OVER the list,
   which stays behind it, and is dismissable with Escape or a click on the
   backdrop. Below `sm` it is full width; above it, capped.

   This is the component half of the pattern. The routing half — a detail
   record deep-linkable at its own route — is a behavioural change and is
   raised as proposal P2 in the audit rather than implemented here.
   ================================================================= */
.drawer {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-modal);
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 520px;
    border-left: 1px solid var(--rule);
    background: var(--surface);
    box-shadow: var(--shadow-drawer);
    transform: translateX(100%);
    visibility: hidden;
    transition: transform var(--dur-slide) var(--ease-out-editorial),
                visibility var(--dur-slide) var(--ease-out-editorial);
}

.drawer.active {
    transform: translateX(0);
    visibility: visible;
}

.drawer.drawer-lg { max-width: 720px; }

.drawer-header {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--rule);
}

.drawer-title {
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-serif);
    font-size: var(--text-h2);
    line-height: var(--text-h2-lh);
    letter-spacing: var(--text-h2-ls);
    font-weight: var(--font-medium);
    color: var(--ink);
}

.drawer-subtitle {
    margin: 4px 0 0;
    font-size: var(--text-sm);
    color: var(--ink-mute);
}

.drawer-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--space-4) var(--space-5);
}

.drawer-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-5);
    border-top: 1px solid var(--rule);
    background: var(--surface-alt);
}

/* ===== TOOLTIP (§9.12) ===========================================
   NAVY IN BOTH THEMES — a tooltip is chrome, not canvas, for the same
   reason the chart read-out chip is. Not a popover: no interactive
   content, no click-to-open.
   ================================================================= */
.tooltip {
    position: fixed;
    z-index: var(--z-tooltip);
    max-width: 240px;
    padding: 4px 8px;
    border-radius: var(--radius-md);
    background: var(--chrome);
    color: var(--chrome-text-strong);
    font-size: var(--text-xs);
    line-height: var(--text-xs-lh);
    box-shadow: var(--shadow-float);
    pointer-events: none;
    animation: fade var(--dur-fade) var(--ease-out-editorial) both;
}

/* ===== TABS (§9.6) ===============================================
   The active marker is a 2px coral underline. The reference implementation
   slides ONE measured element between tabs so the travel reads as a single
   continuous movement; without a client runtime measuring tab positions,
   the per-tab border is the honest static equivalent, and it is called out
   in the Phase 2 report rather than pretended away.
   ================================================================= */
.tabs {
    position: relative;
    display: flex;
    align-items: flex-end;
    gap: var(--space-6);
    overflow-x: auto;
    border-bottom: 1px solid var(--rule);
    margin-bottom: var(--space-6);
}

.tab {
    position: relative;
    display: flex;
    flex-shrink: 0;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: -1px;
    padding: 4px 0 10px;
    border: none;
    border-bottom: 2px solid transparent;
    background: transparent;
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    white-space: nowrap;
    color: var(--ink-mute);
    cursor: pointer;
    text-decoration: none;
    transition: color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial);
}

.tab > svg { width: 16px; height: 16px; flex-shrink: 0; }

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

.tab.active,
.tab[aria-selected="true"] {
    color: var(--ink);
    border-bottom-color: var(--coral);
}

/* The count beside a tab label: tabular, and tinted only when active. */
.tab-count {
    padding: 0 4px;
    border-radius: var(--radius-sm);
    background: var(--surface-alt);
    color: var(--ink-mute);
    font-size: var(--text-label);
    letter-spacing: var(--text-label-ls);
    font-weight: var(--font-semibold);
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

.tab.active .tab-count,
.tab[aria-selected="true"] .tab-count {
    background: var(--coral-soft);
    color: var(--coral-strong);
}

/* ===== DROPDOWN ===== */
.dropdown {
    position: relative;
}

.dropdown-menu {
    position: absolute;
    top: 100%;
    right: 0;
    min-width: 180px;
    background: var(--white);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    z-index: var(--z-dropdown);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                box-shadow var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
    padding: var(--space-2);
}

.dropdown.active .dropdown-menu,
.dropdown:focus-within .dropdown-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(4px);
}

.dropdown-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm);
    color: var(--gray-700);
    text-decoration: none;
    border-radius: var(--radius-md);
    transition: background-color var(--dur-colour) var(--ease-out-editorial),
                border-color var(--dur-colour) var(--ease-out-editorial),
                box-shadow var(--dur-colour) var(--ease-out-editorial),
                color var(--dur-colour) var(--ease-out-editorial);
}

.dropdown-item:hover {
    background: var(--gray-100);
}

.dropdown-item i {
    width: 16px;
    color: var(--gray-500);
}

.dropdown-divider {
    height: 1px;
    background: var(--gray-100);
    margin: var(--space-2) 0;
}

/* ===== LOADING SPINNER ===== */
.spinner {
    width: 24px;
    height: 24px;
    border: 2px solid var(--gray-200);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

.spinner-sm {
    width: 16px;
    height: 16px;
}

.spinner-lg {
    width: 32px;
    height: 32px;
    border-width: 3px;
}

/* ===== AVATAR (§9.13) ============================================
   Generated monograms on a deterministic hue. The hue is derived from the
   stable id, not the name, so a rename does not change someone's colour
   (core.vocabulary.avatar_tone). The monogram is a TINT avatar — soft
   ground, line border, strong text — consistent with badges.

   The topbar's own avatar is the one exception: a solid fill with white
   text, because it sits in chrome.
   ================================================================= */
.avatar {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    overflow: hidden;
    border: 1px solid var(--rule);
    border-radius: var(--radius-full);
    background: var(--surface-alt);
    color: var(--ink-mute);
    font-size: var(--text-xs);
    font-weight: var(--font-semibold);
    line-height: 1;
    user-select: none;
}

.avatar img,
.avatar-photo {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.avatar-xs { width: 20px; height: 20px; font-size: var(--text-label); }
.avatar-sm { width: 24px; height: 24px; font-size: var(--text-label); }
.avatar-md { width: 32px; height: 32px; font-size: var(--text-xs); }
.avatar-lg { width: 44px; height: 44px; font-size: var(--text-sm); }

.avatar-tone-coral { background: var(--coral-soft); border-color: var(--coral-line); color: var(--coral-strong); }
.avatar-tone-teal  { background: var(--teal-soft);  border-color: var(--teal-line);  color: var(--teal-strong); }
.avatar-tone-royal { background: var(--royal-soft); border-color: var(--royal-line); color: var(--royal-strong); }
.avatar-tone-plum  { background: var(--plum-soft);  border-color: var(--plum-line);  color: var(--plum-strong); }
.avatar-tone-amber { background: var(--amber-soft); border-color: var(--amber-line); color: var(--amber-strong); }
.avatar-tone-ink   { background: var(--surface-alt); border-color: var(--ink-line);  color: var(--ink); }

/* For overlapping stacks. */
.avatar-stacked { box-shadow: 0 0 0 2px var(--surface); }

/* ===== ANIMATIONS ===== */
@keyframes fadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes slideUp {
    from { 
        opacity: 0;
        transform: translateY(10px);
    }
    to { 
        opacity: 1;
        transform: translateY(0);
    }
}

.fade-in {
    animation: fadeIn 0.3s ease;
}

.slide-up {
    animation: slideUp 0.3s ease;
}

/* ===== BOOTSTRAP-COMPAT UTILITIES ===== */
/* Margin-end (right) & margin-start (left) */
.me-1 { margin-right: 0.25rem !important; }
.me-2 { margin-right: 0.5rem !important; }
.me-3 { margin-right: 1rem !important; }
.ms-1 { margin-left: 0.25rem !important; }
.ms-2 { margin-left: 0.5rem !important; }

/* Additional margin */
.mb-1 { margin-bottom: 0.25rem !important; }
.mb-3 { margin-bottom: 1rem !important; }
.mt-1 { margin-top: 0.25rem !important; }
.mt-3 { margin-top: 1rem !important; }

/* Padding shortcuts */
.p-0 { padding: 0 !important; }
.p-3 { padding: 1rem !important; }
.py-3 { padding-top: 1rem !important; padding-bottom: 1rem !important; }
.py-4 { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; }
.py-5 { padding-top: 3rem !important; padding-bottom: 3rem !important; }

/* Font weight aliases */
.fw-bold { font-weight: 700 !important; }
.fw-medium { font-weight: 500 !important; }

/* Font size shortcuts */
.fs-4 { font-size: 1.5rem !important; }
.fs-5 { font-size: 1.25rem !important; }
.fs-6 { font-size: 1rem !important; }

/* Text alignment */
.text-end { text-align: right !important; }

/* Display */
.d-inline { display: inline !important; }
.d-inline-block { display: inline-block !important; }

/* Flex */
.flex-grow-1 { flex-grow: 1 !important; }
.flex-wrap { flex-wrap: wrap !important; }

/* Border */
.border-top { border-top: 1px solid var(--border) !important; }

/* ===== BOOTSTRAP-COMPAT GRID ===== */
.row {
    display: flex;
    flex-wrap: wrap;
    margin-left: -0.5rem;
    margin-right: -0.5rem;
}

.row.g-3 {
    gap: 1rem;
    margin-left: 0;
    margin-right: 0;
}

[class*="col-"] {
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    flex-shrink: 0;
}

.col-auto { width: auto; flex: 0 0 auto; }
.col { flex: 1 1 0%; }
.col-md-2 { width: 16.666%; }
.col-md-3 { width: 25%; }
.col-md-4 { width: 33.333%; }
.col-md-6 { width: 50%; }
.col-lg-4 { width: 33.333%; }
.col-lg-6 { width: 50%; }
.col-lg-8 { width: 66.666%; }

/* ===== BOOTSTRAP-COMPAT COMPONENTS ===== */

/* The Bootstrap-compat block used to re-declare .form-label, .form-check,
   .form-check-input, .form-check-label and .form-text here — a second,
   sentence-case copy of the whole form family, sitting AFTER the FORMS
   section, so it silently beat every §9.7 recipe above it. The form labels
   in this product were never actually the caps label; they only looked
   like it on the pages where a THIRD definition in enhancements.css
   happened to win. One definition each now, in the FORMS section. */

/* Selects carry their own chevron because `appearance: none` removes the
   native one. This lived only in advance_standing.css, so a <select> looked
   correct on Advance Standing screens and like a bare box everywhere else. */
.form-select {
    appearance: none;
    -webkit-appearance: none;
    width: 100%;
    padding: var(--space-2) var(--space-3);
    padding-right: var(--space-8);
    font-size: var(--text-sm);
    color: var(--gray-800);
    background-color: var(--white);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236b7280' stroke-width='1.5'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--space-3) center;
    background-size: 16px 16px;
    border: 1px solid var(--gray-300);
    border-radius: var(--radius-md);
    transition: border-color var(--dur-colour) var(--ease-out-editorial),
                box-shadow var(--dur-colour) var(--ease-out-editorial);
}

.form-select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-50);
}

/* Alerts */
.alert {
    padding: 1rem 1.25rem;
    border-radius: var(--radius);
    border: 1px solid;
    margin-bottom: 1rem;
    font-size: 0.875rem;
}

.alert-info {
    background: var(--info-light);
    border-color: rgba(30, 58, 95, 0.15);
    color: var(--info-dark);
}

.alert-warning {
    background: var(--warning-light);
    border-color: rgba(202, 138, 4, 0.15);
    color: var(--warning-dark);
}

.alert-danger {
    background: var(--danger-light);
    border-color: rgba(220, 38, 38, 0.15);
    color: var(--danger-dark);
}

.alert-success {
    background: var(--success-light);
    border-color: rgba(22, 163, 74, 0.15);
    color: var(--success-dark);
}

.alert-heading {
    font-weight: 600;
    margin-bottom: 0.5rem;
}

/* Badge bg-* variants (Bootstrap compat) */
.badge.bg-secondary { background: var(--gray-100) !important; color: var(--gray-600) !important; }
.badge.bg-info { background: var(--info-light) !important; color: var(--info-dark) !important; }
.badge.bg-success { background: var(--success-light) !important; color: var(--success-dark) !important; }
.badge.bg-warning { background: var(--warning-light) !important; color: var(--warning-dark) !important; }
.badge.bg-danger { background: var(--danger-light) !important; color: var(--danger-dark) !important; }
.badge.bg-primary { background: var(--primary-50) !important; color: var(--primary) !important; }
.badge.bg-dark { background: var(--gray-700) !important; color: white !important; }
.badge.bg-light { background: var(--gray-100) !important; color: var(--gray-600) !important; }
.text-dark { color: var(--gray-800) !important; }

/* text-white was never defined here, so any element combining a solid
   bg-* utility with text-white rendered its text in the ink colour — navy
   on navy (the Schedule Preview card header, avatars, etc.). Define it,
   and force headings/icons inside a solid-primary band to white too:
   .card-title and its icon colours would otherwise out-specific it. */
.text-white,
.text-white .card-title,
.text-white .panel-title,
.text-white :is(h1, h2, h3, h4, h5, h6),
.text-white .card-title i,
.text-white .card-title svg { color: white !important; }
.card-header.bg-primary,
.card-header.bg-primary .card-title,
.card-header.bg-primary .card-title i,
.card-header.bg-primary .card-title svg,
.card-header.bg-dark,
.card-header.bg-dark .card-title { color: white !important; }

/* The hidden attribute must always win. Any component that sets its own
   display (flex alerts, grid rows) silently overrides the UA's
   [hidden]{display:none} — which is how an empty amber .alert band showed
   on the subject-selection page before its JS had anything to say. */
[hidden] { display: none !important; }

/* bg-*-subtle (Bootstrap 5.3 compat) */
.bg-primary-subtle { background: var(--primary-50) !important; }
.bg-success-subtle { background: var(--success-light) !important; }
.bg-warning-subtle { background: var(--warning-light) !important; }
.bg-info-subtle { background: var(--info-light) !important; }
.bg-secondary-subtle { background: var(--gray-100) !important; }
.bg-light { background: var(--gray-50) !important; }
.bg-opacity-10 { --bg-opacity: 0.1; }
.bg-warning.bg-opacity-10 { background: rgba(202, 138, 4, 0.1) !important; }
.bg-success.bg-opacity-10 { background: rgba(22, 163, 74, 0.1) !important; }

/* Progress bars */
.progress {
    height: 8px;
    background: var(--gray-100);
    border-radius: var(--radius);
    overflow: hidden;
}

/* Height modifiers. Four templates carried an inline style="height:…" on
   .progress purely because these did not exist. */
.progress.small { height: 5px; }
.progress.large { height: 12px; }

.progress-bar {
    height: 100%;
    border-radius: var(--radius);
    transition: width var(--dur-fill) var(--ease-out-editorial);
}

.progress-bar.bg-success { background: var(--success); }
.progress-bar.bg-warning { background: var(--warning); }
.progress-bar.bg-danger { background: var(--danger); }
.progress-bar.bg-info { background: var(--info); }

/* List group */
.list-group {
    list-style: none;
    padding: 0;
    margin: 0;
}

.list-group-flush .list-group-item {
    border-left: 0;
    border-right: 0;
    border-radius: 0;
}

.list-group-item {
    padding: 0.75rem 1.25rem;
    border-bottom: 1px solid var(--border);
}

.list-group-item:last-child {
    border-bottom: 0;
}

/* Button group */
.btn-group {
    display: inline-flex;
    gap: 0;
}

.btn-group .btn {
    border-radius: 0;
}

.btn-group .btn:first-child {
    border-radius: var(--radius) 0 0 var(--radius);
}

.btn-group .btn:last-child {
    border-radius: 0 var(--radius) var(--radius) 0;
}

.btn-group-sm .btn {
    padding: 0.25rem 0.5rem;
    font-size: 0.8125rem;
}

/* Additional button outline variants */
.btn-outline-info {
    color: var(--info);
    border: 1px solid var(--gray-200);
    background: transparent;
}

.btn-outline-info:hover {
    background: var(--info-light);
    color: var(--info-dark);
    border-color: var(--info);
}

.btn-outline-success {
    color: var(--success);
    border: 1px solid var(--gray-200);
    background: transparent;
}

.btn-outline-success:hover {
    background: var(--success-light);
    color: var(--success-dark);
    border-color: var(--success);
}

.btn-outline-warning {
    color: var(--warning-dark);
    border: 1px solid var(--gray-200);
    background: transparent;
}

.btn-outline-warning:hover {
    background: var(--warning-light);
    color: var(--warning-dark);
    border-color: var(--warning);
}

/* Additional utilities */
.visually-hidden {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

.stretched-link::after {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1;
    content: "";
}

.cursor-pointer { cursor: pointer; }
.rounded-circle { border-radius: 50% !important; }
.rounded { border-radius: var(--radius) !important; }
.border { border: 1px solid var(--border) !important; }
.h-100 { height: 100% !important; }

.avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 0.875rem;
}

.avatar-sm {
    width: 32px;
    height: 32px;
    font-size: 0.75rem;
}

.avatar-lg {
    width: 48px;
    height: 48px;
    font-size: 1rem;
}

.text-white { color: white !important; }
.text-info { color: var(--info) !important; }
.text-decoration-none { text-decoration: none !important; }

.sticky-top {
    position: sticky;
    /* Five templates still carry an inline `style="top: 1rem"` that beats this
       and parks the card behind the topbar; the inline value should go. */
    top: var(--sticky-offset);
}

/* Definition list styling */
dl {
    margin: 0;
}

dt {
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--gray-500);
    margin-bottom: 0.125rem;
}

dd {
    font-size: 0.9375rem;
    color: var(--gray-800);
    margin-bottom: 0.75rem;
    margin-left: 0;
}

/* Pagination compat */
.page-item {
    list-style: none;
}

.page-link {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    height: 36px;
    padding: 0 var(--space-3);
    font-size: var(--text-sm);
    border: 1px solid var(--gray-200);
    border-radius: var(--radius);
    color: var(--gray-600);
    background: white;
    text-decoration: none;
}

.page-item.disabled .page-link {
    color: var(--gray-400);
    pointer-events: none;
}

.page-link:hover {
    background: var(--gray-50);
    border-color: var(--gray-300);
}

.pagination.justify-content-center {
    justify-content: center;
}

/* Modal base styling */
.modal-content {
    border-radius: var(--radius-xl);
    border: 1px solid var(--gray-200);
    box-shadow: var(--shadow-xl);
}

.modal-header {
    padding: var(--space-4) var(--space-6);
    border-bottom: 1px solid var(--gray-100);
}

.modal-body {
    padding: var(--space-6);
}

.modal-footer {
    padding: var(--space-4) var(--space-6);
    border-top: 1px solid var(--gray-100);
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
}

.modal-title {
    font-size: var(--text-lg);
    font-weight: var(--font-semibold);
    color: var(--gray-800);
}

/* ===== RESPONSIVE ===== */
@media (max-width: 1024px) {
    .main-content {
        margin-left: 0;
    }
    
    html.sidebar-collapsed .main-content {
        margin-left: 0;
    }
    
    .mobile-header {
        display: flex;
    }
    
    .container {
        padding: var(--space-4) var(--space-4);
    }
    
    .grid-cols-2,
    .grid-cols-3,
    .grid-cols-4 {
        grid-template-columns: 1fr;
    }
    
    .metrics-grid {
        grid-template-columns: repeat(2, 1fr);
    }
    
    .dashboard-content {
        grid-template-columns: 1fr;
    }
    
    .filter-form {
        flex-direction: column;
        align-items: stretch;
    }
    
    .filter-group {
        min-width: 0;
    }
}

@media (max-width: 768px) {
    .page-header {
        flex-direction: column;
        gap: var(--space-4);
    }
    
    .page-header-actions {
        width: 100%;
        flex-wrap: wrap;
    }
    
    .stats-overview,
    .metrics-grid {
        grid-template-columns: 1fr;
    }
    
    .card-header {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--space-3);
    }
    
    .card-actions {
        width: 100%;
    }
    
    .search-input {
        width: 100%;
    }
    
    .filter-tabs {
        flex-wrap: wrap;
    }
    
    .form-row {
        flex-direction: column;
    }
}

@media (max-width: 480px) {
    html {
        font-size: 14px;
    }
    
    .btn {
        padding: var(--space-2) var(--space-3);
    }
    
    .table th,
    .data-table th,
    .table td,
    .data-table td {
        padding: var(--space-2) var(--space-3);
    }
    
    .panel {
        border-radius: var(--radius-lg);
    }
    
    .metric-card {
        padding: var(--space-4);
    }
}

/* ===== PRINT STYLES ===== */
@media print {
    .sidebar,
    .mobile-header,
    .page-header-actions,
    .btn,
    .no-print {
        display: none !important;
    }
    
    .main-content {
        margin-left: 0 !important;
    }
    
    .card {
        border: 1px solid var(--rule);
        box-shadow: none;
    }
}

/* ===== ENVIRONMENT BANNER ===== */
/* The environment banner is an alert, so it takes the alert construction:
   tint ground + tint border + strong text (§9.2). A full-bleed solid
   crimson bar is the loudest object in the product and it is permanently
   on screen in every non-production environment -- it drowned out the
   actual work. As a tinted band it still reads first and stops shouting.

   It sits above everything, including overlays, because it is a statement
   about the whole environment rather than about the page. */
.environment-banner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: calc(var(--z-tooltip) + 10);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 6px var(--space-4);
    border-bottom: 1px solid var(--crimson-line);
    background-color: var(--crimson-soft);
    color: var(--crimson-strong);
    font-size: var(--text-label);
    line-height: var(--text-label-lh);
    letter-spacing: var(--text-label-ls);
    text-transform: uppercase;
    font-weight: var(--font-semibold);
}

.environment-banner i {
    font-size: 0.875rem;
}

/* Adjust app layout when environment banner is present */
body:has(.environment-banner) .app-layout {
    padding-top: var(--env-banner-h);
}

body:has(.environment-banner) .auth-page {
    padding-top: var(--env-banner-h);
}

body:has(.environment-banner) .sidebar {
    top: var(--env-banner-h);
    height: calc(100vh - var(--env-banner-h));
}

body:has(.environment-banner) .mobile-header {
    top: var(--env-banner-h);
}

/* The topbar has always carried `position: sticky; top: 0`, but nothing stuck
   while <body> was a scroll container, so it never needed the banner offset its
   siblings above already have. It does now: pinned at 0 it parks underneath the
   fixed banner and loses its top third. */
body:has(.environment-banner) .topbar {
    top: var(--env-banner-h);
}

@media print {
    .environment-banner {
        display: none !important;
    }
}

/* The old dark-theme block lived here: a second set of legacy-token
   overrides plus six component rules re-painting body, .card, .topbar,
   .sidebar, .table and the inputs.

   It is gone. Under the paired-token contract the [data-theme="dark"] block
   at the top of this file is the ONLY place the theme changes anything, and
   because -strong lightens instead of darkening, no component needs to know
   which theme it is in (§4.1). Two of these rules had also inverted
   themselves during the re-hue -- `body { background: var(--ink) }` painted
   the dark canvas in the LIGHT ink, which is how the bug was found. */

/* ===== TOAST (§9.11) =============================================
   A floating thing, so `shadow-float` is correct here — one of the few
   places elevation is right at rest (§6.3).

   Tones: success -> teal, error -> crimson, info -> royal, warning ->
   amber. A visually-hidden prefix names the tone for a screen reader,
   because a coloured glyph is not an announcement (§15).

   The region is role="status" aria-live="polite" so a message is read
   without stealing focus.
   ================================================================= */
#toast-container {
    position: fixed;
    /* Was top:20px, which sat on top of the sticky topbar and covered
       search, View As and the account menu every time a message fired.
       Bottom-right can never occlude navigation. */
    bottom: var(--space-5);
    right: var(--space-5);
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: min(360px, calc(100vw - 2.5rem));
    pointer-events: none;
}

.toast {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    overflow: hidden;
    padding: var(--space-3) 14px;
    border: 1px solid var(--rule);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-float);
    pointer-events: auto;
    animation: rise var(--dur-rise) var(--ease-out-editorial) both;
}

.toast-icon {
    flex-shrink: 0;
    margin-top: 1px;
    font-size: var(--text-sm);
    color: var(--ink-mute);
}

.toast-icon svg { width: 16px; height: 16px; }

.toast-body {
    flex: 1;
    min-width: 0;
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    color: var(--ink);
}

.toast-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    /* Dismiss is the only manual way to close a toast, so it has to clear
       the 24px WCAG 2.2 SC 2.5.8 target floor -- it sat at 16x19. Sized with
       width/height rather than min-width: on a flex item min-width would
       replace the min-width:auto that stops a control shrinking under its
       own content. Safe to centre a fixed box because the button holds an
       icon, never a text label. */
    width: 24px;
    min-height: 24px;
    padding: 0;
    border: none;
    background: none;
    color: var(--ink-faint);
    font-size: var(--text-sm);
    line-height: 1;
    cursor: pointer;
    transition: color var(--dur-colour) var(--ease-out-editorial);
}

.toast-close:hover { color: var(--ink); }

/* Severity reads from the icon and a tinted hairline, not a 4px colour bar
   down one edge. The bar was redundant with the icon and is the single most
   recognisable generated-UI tell. */
.toast-success { border-color: var(--teal-line); }
.toast-success .toast-icon { color: var(--teal-strong); }
.toast-warning { border-color: var(--amber-line); }
.toast-warning .toast-icon { color: var(--amber-strong); }
.toast-error,
.toast-danger { border-color: var(--crimson-line); }
.toast-error .toast-icon,
.toast-danger .toast-icon { color: var(--crimson-strong); }
.toast-info { border-color: var(--royal-line); }
.toast-info .toast-icon { color: var(--royal-strong); }

.toast-exit {
    animation: toast-out var(--dur-fade) var(--ease-out-editorial) forwards;
}

@keyframes toast-out {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(6px); }
}

/* ==========================================================================
   SHARED DETAIL / FORM PATTERNS
   --------------------------------------------------------------------------
   Reusable building blocks for record-detail and multi-section form screens
   (send offer, application detail, agent application detail, applicant
   portal). These live in the design system — not in a per-app stylesheet — so
   every module renders these patterns identically and at the same type scale.
   ========================================================================== */

/* --- Two-column detail layout: main content + sticky summary rail --------- */
.detail-layout {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
    gap: var(--space-5);
    align-items: start;
}
.detail-main,
.detail-side {
    min-width: 0; /* let long tables/URLs shrink instead of blowing out the grid */
}
.detail-side {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* --- `.sticky-rail`: the answer for a two-column page whose rail runs out ---
   ~10 two-column pages leave the narrow column stranded once its content ends
   -- up to 8,900px of empty gutter running beside the main column.

   CHOSEN: keep the rail in the gutter and make it travel (position: sticky).
   REJECTED: dropping the rail below the content when it is shorter.
     * The rail holds the summary and the actions for the record you are
       reading. Moving it 8,900px below the thing it describes does not fill
       the gutter, it just puts the answer somewhere you have to go looking
       for -- and on the pages that scroll least it would move for no gain.
     * `.detail-layout` was already sticky. Picking the other approach would
       have meant two different behaviours for the same shape; one behaviour
       is the whole point.
     * Sticky costs nothing where it is not needed: on a page too short to
       scroll it never engages, and below 1024px it is switched off entirely.

   `.sticky-rail` is additive -- put it on the rail element of ANY two-column
   container (`.two-column-layout > .side-column`, `.profile-sidebar`, an
   inline grid) without touching the container:

     <div class="two-column-layout">
       <div class="main-column"> ... </div>
       <div class="side-column sticky-rail"> ... </div>
     </div>

   `align-self: start` is what makes it work on containers that do not set
   `align-items: start` -- a stretched grid item is already the full height of
   the row, so it has nowhere to travel to. */
.sticky-rail,
.detail-side {
    position: sticky;
    /* Clears the sticky topbar (and the environment banner above it). Pinning
       at a bare 16px puts the top of the rail behind the topbar. */
    top: var(--sticky-offset);
    align-self: start;
    /* A rail taller than the viewport sticks by its top edge and its foot
       becomes permanently unreachable. Capping it trades that for an inner
       scrollbar, which only appears on rails that would otherwise be cut off.
       Note the cap makes the rail a scroll container, so do not put a
       dropdown that opens past the rail's edge inside one. */
    max-height: calc(100vh - var(--sticky-offset) - var(--space-4));
    overflow-y: auto;
    overscroll-behavior: contain;
}

@media (max-width: 1024px) {
    .detail-layout { grid-template-columns: 1fr; }
    /* Stacked, the rail is in normal flow: no gutter to strand, and pinning it
       would cover the content it belongs to. */
    .sticky-rail,
    .detail-side {
        position: static;
        max-height: none;
        overflow: visible;
    }
}

/* --- Selectable radio "cards" (used for recipient pickers etc.) ---------- */
.radio-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-3);
}
.radio-card {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-md, 8px);
    background: var(--surface);
    cursor: pointer;
    transition: border-color var(--transition-fast var(--dur-colour) var(--ease-out-editorial),
                ease) var(--dur-fade) var(--ease-out-editorial),
                box-shadow var(--transition-fast var(--dur-colour) var(--ease-out-editorial),
                ease) var(--dur-fade) var(--ease-out-editorial),
                background-color var(--transition-fast var(--dur-colour) var(--ease-out-editorial),
                ease) var(--dur-fade) var(--ease-out-editorial);
}
.radio-card:hover { border-color: var(--primary); }
.radio-card input[type="radio"] {
    margin-top: 3px;
    flex: none;
    accent-color: var(--primary);
}
/* :has() lets the whole card highlight from the native radio with no JS.
   Browsers without :has() simply show the default (unhighlighted) card. */
.radio-card:has(input:checked) {
    border-color: var(--primary);
    background: var(--bg-light);
    box-shadow: 0 0 0 1px var(--primary) inset;
}
.radio-card-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.radio-card-title {
    font-size: var(--text-sm);
    font-weight: var(--font-semibold);
    color: var(--text-primary);
}
.radio-card-meta {
    font-size: var(--text-xs);
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

/* --- Label / value summary rows ----------------------------------------- */
/* Works as a <ul> as well as a <div>: academics rendered it as a list and
   carried its own list reset, so the bullets came back on every other page
   that tried the same markup. */
.summary-list {
    display: flex;
    flex-direction: column;
    list-style: none;
    margin: 0;
    padding: 0;
}
.summary-list .summary-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border-light);
    font-size: var(--text-sm);
}
.summary-list .summary-row:last-child { border-bottom: none; }
.summary-list .summary-row > span { color: var(--text-muted); flex: none; }
.summary-list .summary-row > strong {
    color: var(--text-primary);
    font-weight: var(--font-medium);
    text-align: right;
    overflow-wrap: anywhere;
}

/* --- Condition / requirement lists --------------------------------------- */
.condition-list { list-style: none; margin: 0; padding: 0; }
.condition-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border-light);
    font-size: var(--text-sm);
    color: var(--text-secondary);
}
.condition-list li:last-child { border-bottom: none; }
.condition-list li small { color: var(--text-muted); font-size: var(--text-xs); }

/* --- Form scaffolding shared by every module ----------------------------- */
.form-row-2 {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-4);
}
/* .form-label, .form-hint, .field-error and .form-actions used to be
   redefined here, AFTER the FORMS section, so the later sentence-case
   label silently beat the caps label defined above. They now have one
   definition each, in the FORMS section (§9.7). */

/* ===============================================================
   CHART SYSTEM (§10)
   ===============================================================
   Every paint here resolves through a token, which is why a chart
   recolours itself the instant [data-theme] flips — no JavaScript,
   no re-render, no theme prop threaded through. The previous
   charts baked their colours into the config, which is why they
   were invisible in dark mode.
   =============================================================== */
.chart-host {
    position: relative;
    width: 100%;
    min-height: 240px;
}

.chart-svg { display: block; overflow: visible; }

/* Tick labels are SERIF and tabular — the same family as the KPI figures,
   so a number in a chart and a number in a stat read as the same kind of
   thing (§10.3). */
.chart-tick {
    font-family: var(--font-serif);
    font-size: 10.5px;
    letter-spacing: 0.012em;
    font-variant-numeric: tabular-nums;
    fill: var(--ink-mute);
}

.chart-bar,
.chart-arc { cursor: default; }

.chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    margin-top: var(--space-3);
    font-size: var(--text-xs);
    color: var(--ink-mute);
}

.chart-legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* Squares, not dots: they read as swatches from a printed key and hold the
   tone at a smaller size (§10.3). */
.chart-legend-swatch {
    width: 8px;
    height: 8px;
    border-radius: 1px;
    flex-shrink: 0;
}

/* The empty chart is a 16px hairline and one line of serif text. NEVER a
   broken axis: an axis with no data on it reads as a failure. */
.chart-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    min-height: 200px;
}

.chart-empty-rule {
    display: block;
    width: 16px;
    height: 1px;
    background: var(--rule-strong);
}

.chart-empty-text {
    margin: 0;
    font-family: var(--font-serif);
    font-size: var(--text-xs);
    color: var(--ink-mute);
}

/* The read-out chip: navy in BOTH themes, for the same reason a tooltip is
   (§10.4). Portalled to <body> so an overflow-hidden ancestor cannot clip
   it. */
.chart-chip {
    position: fixed;
    z-index: var(--z-tooltip);
    min-width: 9rem;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--chrome-rule);
    border-radius: var(--radius-md);
    background: var(--chrome);
    box-shadow: var(--shadow-float);
    pointer-events: none;
    animation: fade 120ms var(--ease-out-editorial) both;
}

.chart-chip[hidden] { display: none; }

.chart-chip-title {
    color: var(--chrome-text-mute);
    margin-bottom: 4px;
}

.chart-chip-row {
    display: flex;
    align-items: baseline;
    gap: 6px;
    font-size: var(--text-xs);
    color: var(--chrome-text);
}

.chart-chip-swatch {
    width: 8px;
    height: 8px;
    flex-shrink: 0;
    border-radius: 1px;
    align-self: center;
}

.chart-chip-label { flex: 1; }

.chart-chip-value {
    font-family: var(--font-serif);
    font-size: var(--text-sm);
    font-weight: var(--font-medium);
    color: var(--chrome-text-strong);
}

/* ===== ICONS (§12) =====
   Lucide at stroke-width 1.5, from one self-hosted sprite. Default 16px;
   the size steps are 12px inside chips and sort glyphs, 14px in small
   buttons and dense chrome, 16px default, 20px in a status medallion. */
.icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    display: inline-block;
    vertical-align: -0.15em;
}

.icon-xs { width: 12px; height: 12px; }
.icon-sm { width: 14px; height: 14px; }
.icon-lg { width: 20px; height: 20px; }

/* ===== FLAGS (§9.21) =====
   16x12, one shared rounded clip, and a navy hairline so a white or pale
   flag does not vanish on a white surface. The flag artwork itself is the
   ONE place literal hex is allowed in the whole system — a national flag's
   colours are not the product's to theme. */
.flag {
    width: 16px;
    height: 12px;
    flex-shrink: 0;
    display: inline-block;
    vertical-align: -1px;
}

.country {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
