/*
|==========================================================================
| ICCBI GUIDANCE SYSTEM — DESIGN SYSTEM
|==========================================================================
|
| Immaculate Conception College of Balayan Inc. — Guidance Office.
|
| WHY THIS FILE EXISTS
| --------------------
| Before it, this application had no stylesheet at all. Styles lived inline
| across 162 PHP files, seventeen pages each loaded their own copy of the
| Tailwind CDN with their own private `tailwind.config`, and two different
| typefaces were in play (Inter in the signed-in shell, Poppins on the public
| pages). That is the real reason the product read as a set of assembled
| templates rather than one designed thing: there was no source of truth for
| any visual decision, so every page answered each question again on its own.
|
| Everything below is that source of truth. A token changed here changes the
| whole product.
|
| THE PALETTE IS THE SCHOOL'S OWN
| -------------------------------
| The primary is SKY #00A8E4 and the secondary is NAVY #243090, both sampled
| directly from the college seal at assets/images/icclogo.png — not invented,
| and not the generic indigo (#4f46e5) that used to sit in the CDN config.
| An institution's system should wear the institution's colours.
|
| RED MEANS ONE THING
| -------------------
| This system carries counselling referrals, crisis concerns and safeguarding
| records about real students. Red is therefore reserved strictly for
| destructive actions and error states. A concern that is merely *open* must
| never wear the same colour as a concern that is *urgent*, because a reader
| scanning a queue will act on the colour before they read the label.
|
| CALM IS A REQUIREMENT, NOT A STYLE
| ----------------------------------
| The people using this are counsellors and teachers handling sensitive
| pastoral cases, often while a student is waiting. The interface should
| recede. That rules out decorative gradients, floating card stacks and
| competing accent colours, and it is why hierarchy here is carried by weight
| and spacing rather than by size and colour.
|
| Every foreground/background pair below was chosen to clear WCAG 2.1 AA
| (4.5:1 for body text, 3:1 for large text and UI boundaries).
*/

/*
|==========================================================================
| TOKENS
|==========================================================================
| Plain CSS custom properties. They are mirrored into the Tailwind CDN config
| in includes/header.php so `bg-brand-600` and `var(--color-brand-600)` are
| always the same colour — utilities and hand-written CSS cannot drift apart.
*/
:root {
    /* ---- Type --------------------------------------------------------
       Plus Jakarta Sans: humanist enough to feel warm on a pastoral-care
       product, geometric enough to stay orderly in dense tables. It
       replaces the Inter/Poppins split. The stack degrades to system UI
       fonts so a slow network never leaves the page unstyled. */
    --font-sans: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system,
        'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, 'Cascadia Mono', Consolas, monospace;

    /* Hierarchy is carried by WEIGHT (700-800) and negative tracking, not by
       size. That is what lets the ramp stay compact — so a referral queue
       fits on one screen — while the structure stays obvious. */
    --text-2xs: 0.6875rem;   /* 11px   — table heads, metadata        */
    --text-2xs-lh: 0.875rem;
    --text-xs: 0.75rem;      /* 12px   — labels, helper text, badges  */
    --text-xs-lh: 1rem;
    --text-sm: 0.78125rem;   /* 12.5px — TABLE BODY, dense lists      */
    --text-sm-lh: 1.125rem;
    --text-base: 0.84375rem; /* 13.5px — BASE body                    */
    --text-base-lh: 1.375rem;
    --text-md: 0.90625rem;   /* 14.5px — inputs, primary reading      */
    --text-md-lh: 1.4375rem;
    --text-lg: 0.9375rem;    /* 15px   — card titles (h4)             */
    --text-lg-lh: 1.375rem;
    --text-xl: 1.03125rem;   /* 16.5px — section titles (h3)          */
    --text-xl-lh: 1.5rem;
    --text-2xl: 1.25rem;     /* 20px   — h2                           */
    --text-2xl-lh: 1.75rem;
    --text-3xl: 1.375rem;    /* 22px   — page titles                  */
    --text-3xl-lh: 2rem;
    --text-4xl: 1.625rem;    /* 26px   — stat figures                 */
    --text-4xl-lh: 2.375rem;
    --text-5xl: 2.75rem;     /* 44px   — landing hero                 */
    --text-5xl-lh: 3rem;

    /* ---- Radii -------------------------------------------------------
       One family, four steps. The old UI mixed 4px, 6px, 8px, 12px, 16px,
       20px and full-round arbitrarily, which is a large part of why
       components did not look related. */
    --radius-xs: 4px;
    --radius-sm: 6px;
    --radius-md: 8px;
    --radius-lg: 12px;
    --radius-xl: 16px;
    --radius-2xl: 20px;

    /* ---- Brand: ICC seal sky -----------------------------------------
       #00A8E4 is the dominant colour of the college seal. The ramp is
       anchored at 500 = the seal value; 600-900 all clear 4.5:1 on white,
       so text and primary buttons use 600+ while 500 is for large fills
       and 50-100 for tinted grounds. */
    /* Light royal blue. #4361EE at 600 clears 4.5:1 on white, so the same
       value carries buttons, links and the active nav item — no second blue
       is needed for text. 300 is the light step for accents on dark grounds. */
    --color-brand-50:  #eef1fe;
    --color-brand-100: #e0e6fd;
    --color-brand-200: #c6d0fb;
    --color-brand-300: #a3b3f8;
    --color-brand-400: #7b90f4;
    --color-brand-500: #5c76f0;
    --color-brand-600: #4361ee;   /* LIGHT ROYAL BLUE — primary, 5.02:1  */
    --color-brand-700: #3451d1;   /* hover / pressed — 6.47:1            */
    --color-brand-800: #2d47bd;
    --color-brand-900: #273c9c;
    --color-brand-950: #1b2765;

    /* ---- Navy — the seal's field -------------------------------------
       The primary partner to the sky. Carries the signed-in rail's brand
       mark and section headings, and anchors the landing page, so the
       product has weight without reaching for black. */
    --color-navy-50:  #eef0f8;
    --color-navy-100: #dfe3f3;
    --color-navy-200: #c2c9e8;
    --color-navy-300: #97a2d6;
    --color-navy-400: #6675bd;
    --color-navy-500: #4453a3;
    --color-navy-600: #243090;   /* THE SEAL NAVY                     */
    --color-navy-700: #1f2875;
    --color-navy-800: #1a2160;
    --color-navy-900: #141942;
    --color-navy-950: #0b0f28;

    /* ---- Neutrals: one family, faint cool cast -----------------------
       A slight blue cast so the greys sit with the sky and navy rather
       than fighting them. Still unmistakably neutral. ink-50 is the app
       canvas; the old UI used pure #fff everywhere, which is why content
       and chrome had nothing separating them. */
    --color-ink-25:  #fbfcfd;
    --color-ink-50:  #f6f8fa;
    --color-ink-100: #eef1f5;
    --color-ink-200: #e0e5ec;
    --color-ink-300: #c8d1dc;
    --color-ink-400: #8b96a5;
    --color-ink-500: #667283;
    --color-ink-600: #525d6d;
    --color-ink-700: #3c4655;
    --color-ink-800: #28303c;
    --color-ink-900: #171d26;

    /* ---- Semantic status ---------------------------------------------
       Paired ALWAYS with an icon or a dot and a text label. Colour is never
       the sole carrier of meaning: these records are read by people who may
       be colour-blind, and printed in monochrome for case files. */
    --color-ok-50:  #f0fdf4;
    --color-ok-100: #dcfce7;
    --color-ok-200: #bbf7d0;
    --color-ok-600: #16a34a;
    --color-ok-700: #15803d;
    --color-ok-800: #166534;

    --color-warn-50:  #fffbeb;
    --color-warn-100: #fef3c7;
    --color-warn-200: #fde68a;
    --color-warn-600: #d97706;
    --color-warn-700: #b45309;
    --color-warn-800: #92400e;

    /* Alert is the step between "needs attention" and "danger" — used for
       escalated concerns that are urgent but not an error. Having it means
       an urgent referral never has to borrow the destructive red. */
    --color-alert-50:  #fff7ed;
    --color-alert-100: #ffedd5;
    --color-alert-200: #fed7aa;
    --color-alert-600: #ea580c;
    --color-alert-700: #c2410c;
    --color-alert-800: #9a3412;

    /* Danger — destructive actions and error states ONLY. Never decorative,
       never a category colour. #E41824 is the seal's red, darkened to clear
       contrast on white. */
    --color-danger-50:  #fef2f2;
    --color-danger-100: #fee2e2;
    --color-danger-200: #fecaca;
    --color-danger-600: #dc2626;
    --color-danger-700: #b91c1c;
    --color-danger-800: #991b1b;

    /* ---- Accents ------------------------------------------------------
       Categorical and decorative ONLY — charts, avatar grounds, calendar
       category dots. Deliberately outside the status ramp so an accent can
       never be mistaken for "approved" or "something is wrong". Teal and
       violet sit either side of the brand sky on the wheel, so a multi-series
       chart separates cleanly without introducing a second warm colour that
       would compete with warn/alert. */
    /* The seal's sky. It was the primary until the palette moved to royal
       blue; it stays here as the first categorical accent because it is
       genuinely the school's colour and it reads clearly against the new
       brand without competing with it. */
    --color-sky-50:  #eef2ff;
    --color-sky-100: #e0e7ff;
    --color-sky-500: #4a63ef;
    --color-sky-600: #2f47e0;
    --color-sky-700: #1f3fbf;

    --color-teal-100: #ccfbf1;
    --color-teal-500: #14b8a6;
    --color-teal-700: #0f766e;

    --color-violet-100: #ede9fe;
    --color-violet-500: #8b5cf6;
    --color-violet-700: #6d28d9;

    /* The seal's gold. Reads as highlight, not warning, because it is
       darkened well past the amber used for `warn`. */
    --color-gold-50:  #fdf8e7;
    --color-gold-100: #faefc4;
    --color-gold-500: #d9ae2b;
    --color-gold-600: #a67c0a;
    --color-gold-700: #856100;

    /* ---- Shadows ------------------------------------------------------
       Hairline ring + soft drop. A card reads as a lifted surface without a
       heavy border, which is what keeps a page of cards calm. The old UI
       used ad-hoc rgba shadows at five different strengths. */
    --shadow-xs:    0 0 0 1px rgb(23 29 38 / 0.04);
    --shadow-card:  0 0 0 1px rgb(23 29 38 / 0.05), 0 1px 3px rgb(23 29 38 / 0.05);
    --shadow-panel: 0 0 0 1px rgb(23 29 38 / 0.04), 0 4px 16px rgb(23 29 38 / 0.07);
    --shadow-lift:  0 0 0 1px rgb(23 29 38 / 0.04), 0 8px 28px rgb(23 29 38 / 0.10);
    --shadow-modal: 0 20px 60px rgb(23 29 38 / 0.22), 0 0 0 1px rgb(23 29 38 / 0.06);
    --shadow-brand: 0 4px 14px rgb(67 97 238 / 0.30);

    /* ---- Layout -------------------------------------------------------
       One shell geometry for the whole signed-in product. */
    --container-content: 87.5rem;       /* 1400px content cap            */
    --container-prose: 45rem;           /* 720px — help, policy, reading */
    --sidebar-w: 15rem;                 /* 240px pinned rail             */
    --sidebar-collapsed-w: 4.5rem;      /* 72px icons-only               */
    --topbar-h: 3.5rem;                 /* 56px                          */
    --page-pad-x: 1.5rem;               /* 24px desktop gutter           */
    --page-pad-x-sm: 1rem;              /* 16px phone gutter             */

    /* ---- Motion -------------------------------------------------------
       A named vocabulary so no timing in this system is a magic number.
       RESPONSE is how fast a value reaches its target — short for anything
       under the pointer, longer only for large surfaces that would feel
       yanked. --ease-out decelerates into rest, which is what a thing
       arriving should do. */
    --response-instant: 0.1s;   /* press feedback — must beat perception */
    --response-fast: 0.15s;     /* hover, focus ring, colour             */
    --response-base: 0.24s;     /* panels, dialogs, disclosure           */
    --response-slow: 0.36s;     /* full-height surfaces: drawer, sheet   */

    --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
    --ease-in:  cubic-bezier(0.64, 0, 0.78, 0.39);
    --ease-spring: cubic-bezier(0.22, 1.2, 0.36, 1);

    /* ---- Z-index scale -------------------------------------------------
       Named, so no one has to guess whether 9999 is enough. The old UI had
       z-50, z-100, z-999 and z-9999 all in play. */
    --z-sticky: 20;
    --z-rail: 40;
    --z-topbar: 45;
    --z-overlay: 60;
    --z-modal: 70;
    --z-toast: 90;
    --z-tooltip: 100;
}

/*
|==========================================================================
| BASE
|==========================================================================
*/
html {
    -webkit-text-size-adjust: 100%;
}

body {
    background-color: var(--color-ink-50);
    color: var(--color-ink-800);
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: 1.5;
    /* Slightly tightened tracking is what makes dense text read as
       deliberate rather than merely small. */
    letter-spacing: -0.006em;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Notched devices. */
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
}

/*
 | Visible focus ring on every keyboard-reachable element, defined once and
 | globally so no component can ship without it. WCAG 2.1 AA (2.4.7).
 */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--color-brand-600);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Never remove an outline without replacing it. */
:where(a, button, input, select, textarea):focus:not(:focus-visible) {
    outline: none;
}

/* Headings inherit the compact ramp rather than browser defaults. */
h1, h2, h3, h4, h5, h6 {
    color: var(--color-ink-900);
    text-wrap: balance;
    margin: 0;
}
h1 { font-size: var(--text-3xl); line-height: var(--text-3xl-lh); font-weight: 800; letter-spacing: -0.03em; }
h2 { font-size: var(--text-2xl); line-height: var(--text-2xl-lh); font-weight: 800; letter-spacing: -0.025em; }
h3 { font-size: var(--text-xl);  line-height: var(--text-xl-lh);  font-weight: 700; letter-spacing: -0.02em; }
h4 { font-size: var(--text-lg);  line-height: var(--text-lg-lh);  font-weight: 700; letter-spacing: -0.015em; }
h5, h6 { font-size: var(--text-base); line-height: var(--text-base-lh); font-weight: 700; letter-spacing: -0.01em; }

/* Consistent native control accent. */
:where(input[type='checkbox'], input[type='radio'], progress) {
    accent-color: var(--color-brand-600);
}

/* Long words — student emails, case reference numbers, uploaded filenames —
   must never force the page to scroll sideways. */
:where(p, td, th, dd, li) {
    overflow-wrap: break-word;
}

[hidden] { display: none !important; }

/* Honour the OS setting globally. Anything that animates below opts out
   here rather than each rule remembering to. */
@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;
    }
}

/* Thin, unobtrusive scrollbars. */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: var(--color-ink-300);
    border-radius: 999px;
    border: 3px solid transparent;
    background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background-color: var(--color-ink-400); background-clip: content-box; }

/* Screen-reader-only text that becomes visible on focus — the skip link. */
.sr-only-focusable:not(:focus):not(:focus-within) {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border-width: 0;
}

.skip-link {
    position: fixed;
    top: 0.5rem;
    left: 0.5rem;
    z-index: var(--z-tooltip);
    padding: 0.5rem 0.875rem;
    background: var(--color-navy-600);
    color: #fff;
    font-size: var(--text-sm);
    font-weight: 700;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lift);
}

/*
|==========================================================================
| LAYOUT SHELL
|==========================================================================
| NOTE ON THE EXISTING MARKUP
| ---------------------------
| The ~56 signed-in pages all open with
|     <div class="flex h-screen overflow-hidden">
|         <?php require .../sidebar.php ?>
|         <div class="flex-1 ..."> <?php require .../topbar.php ?> ... </div>
|     </div>
| i.e. the rail is a STATIC flex child, not a fixed overlay. The rules below
| therefore keep the rail in flow on desktop rather than switching it to
| `position: fixed`, which would have required editing the opening wrapper of
| every one of those pages and would have left the content underlapping the
| rail on any page that was missed.
|
| `.app-shell` / `.app-main` remain available for pages written fresh against
| the new system; both layouts produce the same geometry.
| One geometry for every signed-in page: a pinned rail, a sticky topbar and
| a capped content column. Previously each page invented its own padding and
| max-width, which is why sections never lined up between pages.
*/
/*
 | The shell is a flex row: rail, then the main column. It is flex rather than
 | a fixed rail plus a margin so that BOTH page shapes in this codebase land on
 | the same geometry — the legacy `<div class="flex h-screen overflow-hidden">`
 | wrapper and the newer `.app-shell`. A fixed rail would have needed a
 | matching margin on the main column, and any page that had one and not the
 | other would either gap or overlap.
 */
.app-shell {
    display: flex;
    min-height: 100vh;
    max-height: 100vh;
    overflow: hidden;
    background: var(--color-ink-50);
}

/* The main column owns the vertical scroll, so the rail and the topbar stay
   put while the content moves under them. */
.app-main {
    flex: 1;
    min-width: 0;
    height: 100vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

@media (max-width: 1023px) {
    /* The rail leaves the flow and becomes a drawer, so the column is free to
       take the whole width. */
    .app-shell { display: block; max-height: none; overflow: visible; }
    .app-main  { height: auto; min-height: 100vh; overflow-y: visible; }
}

/* The page's own padded, capped column. Every page body uses this, which is
   what makes headings align vertically as you move between them. */
.page {
    width: 100%;
    max-width: var(--container-content);
    margin: 0 auto;
    padding: 1.125rem var(--page-pad-x) 2.5rem;
    flex: 1;
}
@media (max-width: 767px) {
    .page { padding: 1rem var(--page-pad-x-sm) 2.5rem; }
}

/* A narrower column for pages that are mostly prose — help, policy, a single
   form. Reading line length stays comfortable instead of stretching to
   1400px. */
.page-prose { max-width: var(--container-prose); }

/* Vertical rhythm between major blocks on a page. Using one class instead of
   per-page margins is what removes the "every section has different spacing"
   problem. */
.stack     > * + * { margin-top: 1rem; }
.stack-sm  > * + * { margin-top: 0.5rem; }
.stack-lg  > * + * { margin-top: 1.5rem; }
.stack-xl  > * + * { margin-top: 2rem; }

/*
|==========================================================================
| PAGE HEADER
|==========================================================================
| Title, optional description, optional actions. One component so that the
| distance from the topbar to the first heading is identical on all 56 pages.
*/
.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.page-header__title {
    font-size: var(--text-3xl);
    line-height: var(--text-3xl-lh);
    font-weight: 800;
    letter-spacing: -0.03em;
    color: var(--color-ink-900);
}
.page-header__desc {
    margin-top: 0.25rem;
    font-size: var(--text-md);
    line-height: var(--text-md-lh);
    color: var(--color-ink-500);
    max-width: 62ch;
}
.page-header__actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
    flex-wrap: wrap;
}

/* Breadcrumb — answers "where am I" on detail pages. */
.breadcrumb {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--color-ink-400);
    margin-bottom: 0.5rem;
    flex-wrap: wrap;
}
.breadcrumb a { color: var(--color-ink-500); }
.breadcrumb a:hover { color: var(--color-brand-700); text-decoration: underline; }
.breadcrumb__sep { color: var(--color-ink-300); }

/* A section divider inside a page. */
.section-title {
    font-size: var(--text-2xs);
    line-height: var(--text-2xs-lh);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-ink-400);
    margin-bottom: 0.625rem;
}

/*
|==========================================================================
| CARD
|==========================================================================
| The single surface primitive. One radius, one shadow, one border — so a
| page of cards reads as one material rather than a pile of unrelated boxes.
*/
.card {
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
}
.card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6875rem 1rem;
    border-bottom: 1px solid var(--color-ink-100);
}
.card__title {
    font-size: var(--text-lg);
    line-height: var(--text-lg-lh);
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--color-ink-900);
}
.card__desc {
    font-size: var(--text-xs);
    color: var(--color-ink-500);
    margin-top: 0.125rem;
}
.card__body { padding: 0.875rem 1rem; }
.card__body--flush { padding: 0; }
.card__footer {
    padding: 0.75rem 1.125rem;
    border-top: 1px solid var(--color-ink-100);
    background: var(--color-ink-25);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* Only for cards that are genuinely a link to somewhere. A hover state on a
   card that does nothing teaches people the interface is unreliable. */
a.card, .card--interactive {
    transition: box-shadow var(--response-fast) var(--ease-out),
                transform var(--response-fast) var(--ease-out);
    display: block;
}
a.card:hover, .card--interactive:hover {
    box-shadow: var(--shadow-panel);
    transform: translateY(-1px);
}
a.card:active, .card--interactive:active { transform: translateY(0); }

/*
|==========================================================================
| STAT TILE
|==========================================================================
| Dashboard metrics. The figure is the loudest thing in the tile and the
| label is quiet above it — the reverse of the old dashboard, where the label,
| the icon and the number all competed at similar weight.
*/
/*
 | The tile is a GRID, not a flex row: label and figure in the first column,
 | icon pinned to the top-right of the second.
 |
 | As a flex row it relied on every tile's content being the same height, and
 | it is not — "Total teachers" carries three status dots, "Total students"
 | carries one line — so the icon fell to the bottom of the taller tiles and
 | left a gap in the shorter ones. A row of tiles looked misaligned and broken.
 | Pinning the icon to its own grid cell makes the icons line up across the row
 | whatever the content does.
 */
.stat {
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    padding: 0.8125rem 1rem;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto;
    align-items: start;
    gap: 0 0.75rem;
}
/* Everything that is not the icon stacks in the first column. */
.stat > *:not(.stat__icon) { grid-column: 1; }
.stat__icon { grid-column: 2; grid-row: 1 / span 99; align-self: start; }
.stat__label {
    font-size: var(--text-2xs);
    line-height: var(--text-2xs-lh);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-ink-400);
}
.stat__value {
    margin-top: 0.25rem;
    font-size: var(--text-4xl);
    line-height: var(--text-4xl-lh);
    font-weight: 800;
    letter-spacing: -0.035em;
    color: var(--color-ink-900);
    font-variant-numeric: tabular-nums;
}
.stat__meta {
    margin-top: 0.25rem;
    font-size: var(--text-xs);
    color: var(--color-ink-500);
}
/* The icon is a quiet locator, not the point of the tile. */
.stat__icon {
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--radius-md);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.9375rem;
    flex-shrink: 0;
    background: var(--color-brand-50);
    color: var(--color-brand-700);
}
/* A metric row reads faster when each tile owns a hue — the eye returns to
   "the green one" rather than re-reading five identical labels. These are
   CATEGORICAL, not status: they say which metric, never whether it is good. */
.stat__icon--navy   { background: var(--color-navy-50);   color: var(--color-navy-600); }
.stat__icon--sky    { background: var(--color-sky-50);    color: var(--color-sky-700); }
.stat__icon--teal   { background: var(--color-teal-100);  color: var(--color-teal-700); }
.stat__icon--violet { background: var(--color-violet-100);color: var(--color-violet-700); }
.stat__icon--gold   { background: var(--color-gold-50);   color: var(--color-gold-700); }
.stat__icon--ok     { background: var(--color-ok-50);     color: var(--color-ok-700); }
.stat__icon--warn   { background: var(--color-warn-50);   color: var(--color-warn-700); }
.stat__icon--alert  { background: var(--color-alert-50);  color: var(--color-alert-700); }
.stat__icon--danger { background: var(--color-danger-50); color: var(--color-danger-700); }

/* Delta indicators. Shape (the arrow) carries the direction, colour only
   reinforces it. */
.stat__delta {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: var(--text-xs);
    font-weight: 700;
}
.stat__delta--up   { color: var(--color-ok-700); }
.stat__delta--down { color: var(--color-danger-700); }
.stat__delta--flat { color: var(--color-ink-400); }

/*
|==========================================================================
| BUTTONS
|==========================================================================
| Four variants and three sizes — and that is the whole set. The old UI had
| more than a dozen near-identical button treatments, which is a large part of
| why nothing felt like one system.
*/
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4375rem;
    height: 2.25rem;
    padding: 0 0.875rem;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
    transition: background var(--response-fast) var(--ease-out),
                border-color var(--response-fast) var(--ease-out),
                color var(--response-fast) var(--ease-out),
                box-shadow var(--response-fast) var(--ease-out),
                transform var(--response-instant) var(--ease-out);
}
/* A 1px dip on press. Enough to feel mechanical, too small to shift layout. */
.btn:active { transform: translateY(1px); }

.btn:disabled, .btn[aria-disabled='true'] {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
}

/* Primary — the one action the page most wants. At most one per view. */
.btn-primary {
    background: var(--color-brand-600);
    color: #fff;
    box-shadow: var(--shadow-brand);
}
.btn-primary:hover:not(:disabled) { background: var(--color-brand-700); }

/* Secondary — the common case: everything that is not the primary action. */
.btn-secondary {
    background: #fff;
    color: var(--color-ink-700);
    border-color: var(--color-ink-200);
    box-shadow: var(--shadow-xs);
}
.btn-secondary:hover:not(:disabled) {
    background: var(--color-ink-50);
    border-color: var(--color-ink-300);
    color: var(--color-ink-900);
}

/* Ghost — toolbar and in-table actions, where a border per control would
   turn a row of actions into a fence. */
.btn-ghost {
    background: transparent;
    color: var(--color-ink-600);
}
.btn-ghost:hover:not(:disabled) {
    background: var(--color-ink-100);
    color: var(--color-ink-900);
}

/* Danger — destructive only. Deleting a student record, dropping an account. */
.btn-danger {
    background: var(--color-danger-600);
    color: #fff;
    box-shadow: 0 4px 14px rgb(220 38 38 / 0.24);
}
.btn-danger:hover:not(:disabled) { background: var(--color-danger-700); }

/* A quieter destructive control for table rows, where a solid red button in
   every row would make the whole table look like an emergency. */
.btn-danger-ghost {
    background: transparent;
    color: var(--color-danger-700);
}
.btn-danger-ghost:hover:not(:disabled) { background: var(--color-danger-50); }

.btn-sm { height: 1.875rem; padding: 0 0.625rem; font-size: var(--text-xs); border-radius: var(--radius-sm); }
.btn-lg { height: 2.625rem; padding: 0 1.25rem; font-size: var(--text-md); }

/* Icon-only. Kept square, and given a real touch target on coarse pointers
   so a 30px icon button is not finger-hostile on a phone. */
.btn-icon { width: 2.25rem; padding: 0; }
.btn-icon.btn-sm { width: 1.875rem; }
.btn-icon.btn-lg { width: 2.625rem; }
@media (pointer: coarse) {
    .btn-icon { min-width: 2.75rem; min-height: 2.75rem; }
}

.btn-block { width: 100%; }

/*
|==========================================================================
| FORMS
|==========================================================================
| Label above input, help text below, error replacing help text. Predictable
| everywhere, so a user who has filled one form in this product knows how the
| next one behaves.
*/
.field { display: block; }
.field + .field { margin-top: 1rem; }

.label {
    display: block;
    font-size: var(--text-xs);
    line-height: var(--text-xs-lh);
    font-weight: 700;
    letter-spacing: -0.005em;
    color: var(--color-ink-700);
    margin-bottom: 0.375rem;
}
/* The asterisk is reinforcement; the word "required" is in the label's
   aria-label or the help text, because an asterisk alone is not an
   accessible way to say a field is mandatory. */
.label__req { color: var(--color-danger-600); margin-left: 0.125rem; }
.label__opt {
    color: var(--color-ink-400);
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
    margin-left: 0.25rem;
}

.input,
.select,
.textarea {
    display: block;
    width: 100%;
    min-height: 2.375rem;
    padding: 0.4375rem 0.6875rem;
    background: #fff;
    border: 1px solid var(--color-ink-200);
    border-radius: var(--radius-md);
    font-family: inherit;
    font-size: var(--text-md);
    line-height: 1.4;
    color: var(--color-ink-900);
    box-shadow: var(--shadow-xs);
    transition: border-color var(--response-fast) var(--ease-out),
                box-shadow var(--response-fast) var(--ease-out);
}
.input::placeholder, .textarea::placeholder { color: var(--color-ink-400); }

.input:hover:not(:disabled), .select:hover:not(:disabled), .textarea:hover:not(:disabled) {
    border-color: var(--color-ink-300);
}
.input:focus, .select:focus, .textarea:focus {
    outline: none;
    border-color: var(--color-brand-500);
    box-shadow: 0 0 0 3px rgb(67 97 238 / 0.18);
}
.input:disabled, .select:disabled, .textarea:disabled {
    background: var(--color-ink-50);
    color: var(--color-ink-400);
    cursor: not-allowed;
}
.input[readonly], .textarea[readonly] {
    background: var(--color-ink-50);
    color: var(--color-ink-600);
}

.textarea { min-height: 5.5rem; resize: vertical; line-height: 1.5; }

/* Native select needs its own chevron once the default appearance is removed. */
.select {
    appearance: none;
    padding-right: 2rem;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23667283' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.625rem center;
    background-size: 1rem;
}

/* Error state. The red border is paired with an error message carrying an
   icon — colour alone never reports a validation failure. */
.input--error, .select--error, .textarea--error,
.input[aria-invalid='true'], .select[aria-invalid='true'], .textarea[aria-invalid='true'] {
    border-color: var(--color-danger-600);
}
.input--error:focus, .textarea--error:focus, .select--error:focus,
.input[aria-invalid='true']:focus {
    border-color: var(--color-danger-600);
    box-shadow: 0 0 0 3px rgb(220 38 38 / 0.14);
}

.field__help {
    margin-top: 0.375rem;
    font-size: var(--text-xs);
    line-height: 1.4;
    color: var(--color-ink-500);
}
.field__error {
    display: flex;
    align-items: flex-start;
    gap: 0.3125rem;
    margin-top: 0.375rem;
    font-size: var(--text-xs);
    line-height: 1.4;
    font-weight: 600;
    color: var(--color-danger-700);
}

/* Input with a leading icon — search boxes, currency, prefixes. */
.input-group { position: relative; }
.input-group__icon {
    position: absolute;
    left: 0.6875rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--color-ink-400);
    font-size: 0.8125rem;
    pointer-events: none;
}
.input-group .input { padding-left: 2.125rem; }

/* Checkbox / radio rows. The whole row is the label, so the target is the
   text as well as the box. */
.check-row {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    cursor: pointer;
    font-size: var(--text-sm);
    color: var(--color-ink-700);
    line-height: 1.4;
}
.check-row input[type='checkbox'],
.check-row input[type='radio'] {
    width: 1rem;
    height: 1rem;
    margin-top: 0.125rem;
    flex-shrink: 0;
    cursor: pointer;
}

/* Grouping inside a long form — a fieldset that reads as a section. */
.fieldset { border: 0; padding: 0; margin: 0; }
.fieldset + .fieldset { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--color-ink-100); }
.fieldset__legend {
    font-size: var(--text-base);
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--color-ink-900);
    margin-bottom: 0.125rem;
    padding: 0;
}
.fieldset__hint {
    font-size: var(--text-xs);
    color: var(--color-ink-500);
    margin-bottom: 0.875rem;
    max-width: 62ch;
}

/* Two-column form grid that collapses on small screens. */
.form-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
}
.form-grid__full { grid-column: 1 / -1; }
@media (max-width: 767px) {
    .form-grid { grid-template-columns: minmax(0, 1fr); }
}

/* Sticky action bar, so Save is never a forty-field scroll away. */
.form-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
    padding-top: 1rem;
    margin-top: 1.25rem;
    border-top: 1px solid var(--color-ink-100);
}
.form-actions--sticky {
    position: sticky;
    bottom: 0;
    z-index: var(--z-sticky);
    background: color-mix(in srgb, #fff 92%, transparent);
    backdrop-filter: blur(8px);
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
    margin-top: 0;
}
/* On a phone the actions go full width and the primary comes first in the
   visual order, since the thumb rests at the bottom of the screen. */
@media (max-width: 639px) {
    .form-actions { flex-direction: column-reverse; align-items: stretch; }
    .form-actions .btn { width: 100%; }
}

/*
|==========================================================================
| DATA TABLE
|==========================================================================
| 13px body on 9px rows. A referral queue that used to show eight rows per
| screen now shows around twenty-five. Rows do NOT wrap by default: a queue is
| scanned down a column, and one wrapping cell breaks that rhythm for every
| row beside it. Cells that genuinely hold prose opt in with .cell-wrap.
*/
.table-wrap {
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

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

.data-table thead tr {
    border-bottom: 1.5px solid var(--color-ink-200);
}
.data-table thead th {
    padding: 0.4375rem 0.75rem;
    font-size: var(--text-2xs);
    line-height: var(--text-2xs-lh);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-ink-500);
    text-align: left;
    white-space: nowrap;
    user-select: none;
    background: var(--color-ink-25);
}
.data-table thead th:first-child,
.data-table tbody td:first-child { padding-left: 1.125rem; }
.data-table thead th:last-child,
.data-table tbody td:last-child { padding-right: 1.125rem; }

/* A sticky header keeps the column names in view on long student lists. */
.data-table--sticky thead th {
    position: sticky;
    top: 0;
    z-index: 2;
}

.data-table tbody tr {
    border-bottom: 1px solid color-mix(in srgb, var(--color-ink-200) 65%, transparent);
    transition: background var(--response-instant) var(--ease-out);
}
.data-table tbody tr:last-child { border-bottom: none; }
.data-table tbody tr:hover {
    background: color-mix(in srgb, var(--color-brand-500) 6%, transparent);
}
.data-table tbody tr[aria-selected='true'],
.data-table tbody tr.is-selected {
    background: var(--color-brand-50);
    box-shadow: inset 2px 0 0 var(--color-brand-600);
}

.data-table tbody td {
    padding: 0.4375rem 0.75rem;
    font-weight: 500;
    color: var(--color-ink-800);
    vertical-align: middle;
    line-height: 1.4;
    white-space: nowrap;
}
/* Opt back into wrapping for notes, reasons and addresses. */
.data-table td.cell-wrap { white-space: normal; min-width: 16rem; }

/* The identifying cell in a row — a student's name — carries the weight, so
   the eye can run down that one column. */
.data-table .cell-primary {
    font-weight: 700;
    color: var(--color-ink-900);
    letter-spacing: -0.01em;
}
.data-table .cell-muted { color: var(--color-ink-500); }
.data-table .cell-num { text-align: right; font-variant-numeric: tabular-nums; }
/* Actions column: right-aligned, and the controls only reach full opacity
   when the row is hovered or something inside has focus — so a long table is
   not a wall of buttons, while remaining fully keyboard-reachable. */
.data-table .cell-actions { text-align: right; white-space: nowrap; }
.data-table .cell-actions .btn { opacity: 0.65; transition: opacity var(--response-fast) var(--ease-out); }
.data-table tbody tr:hover .cell-actions .btn,
.data-table .cell-actions .btn:focus-visible { opacity: 1; }
@media (pointer: coarse) {
    /* No hover on touch, so the actions must always be visible. */
    .data-table .cell-actions .btn { opacity: 1; }
}

/* Sortable header. The caret is always present but faint, so the column
   advertises that it can be sorted before you hover it. */
.th-sort {
    display: inline-flex;
    align-items: center;
    gap: 0.3125rem;
    cursor: pointer;
    color: inherit;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    background: none;
    border: 0;
    padding: 0;
}
.th-sort:hover { color: var(--color-ink-800); }
.th-sort__caret { opacity: 0.35; font-size: 0.625rem; transition: opacity var(--response-fast); }
.th-sort[aria-sort='ascending'] .th-sort__caret,
.th-sort[aria-sort='descending'] .th-sort__caret { opacity: 1; color: var(--color-brand-600); }

/* The bar above a table: search, filters, bulk actions. */
.table-toolbar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    padding: 0.625rem 1rem;
    border-bottom: 1px solid var(--color-ink-100);
}
.table-toolbar__search { flex: 1 1 16rem; min-width: 12rem; max-width: 24rem; }
.table-toolbar__spacer { flex: 1 1 auto; }

/* Bulk-selection bar, shown once rows are ticked. */
.table-bulkbar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
    padding: 0.625rem 1.125rem;
    background: var(--color-brand-50);
    border-bottom: 1px solid var(--color-brand-100);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--color-brand-900);
}

/* Pagination. */
.pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
    padding: 0.75rem 1.125rem;
    border-top: 1px solid var(--color-ink-100);
    font-size: var(--text-xs);
    color: var(--color-ink-500);
}
.pagination__pages { display: flex; align-items: center; gap: 0.25rem; }
.pagination__page {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.875rem;
    height: 1.875rem;
    padding: 0 0.375rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--color-ink-600);
    font-variant-numeric: tabular-nums;
    transition: background var(--response-fast) var(--ease-out);
}
.pagination__page:hover { background: var(--color-ink-100); color: var(--color-ink-900); }
.pagination__page[aria-current='page'] {
    background: var(--color-brand-600);
    color: #fff;
}
.pagination__page[aria-disabled='true'] { opacity: 0.4; pointer-events: none; }

/*
 | Responsive table -> stacked cards under md.
 |
 | The markup stays a real <table>, so it is still a table for assistive
 | technology and for print, and each <td> carries data-label which becomes
 | the field name on phones. A guidance officer checking a referral on their
 | phone should not have to scroll a data grid sideways.
 */
@media (max-width: 767px) {
    .table-stack thead {
        position: absolute;
        width: 1px; height: 1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
    }
    .table-stack, .table-stack tbody, .table-stack tr, .table-stack td {
        display: block;
        width: 100%;
    }
    .table-stack tr {
        border: 1px solid var(--color-ink-200);
        border-radius: var(--radius-lg);
        background: #fff;
        box-shadow: var(--shadow-card);
        margin-bottom: 0.625rem;
        padding: 0.75rem 0.875rem;
    }
    .table-stack tr:hover { background: #fff; }
    .table-stack td {
        border: 0;
        padding: 0.25rem 0;
        display: flex;
        gap: 0.75rem;
        align-items: baseline;
        justify-content: space-between;
        white-space: normal;
        text-align: left;
    }
    .table-stack td::before {
        content: attr(data-label);
        flex: 0 0 auto;
        font-size: var(--text-xs);
        font-weight: 700;
        color: var(--color-ink-500);
    }
    /* A cell marked data-label="" is the card's headline row. */
    .table-stack td[data-label='']::before { content: none; }
    .table-stack td[data-label=''] { justify-content: flex-start; }
    .table-stack .cell-actions { justify-content: flex-end; padding-top: 0.5rem; }
}

/*
|==========================================================================
| STATUS PILL
|==========================================================================
| The 6px dot is a SHAPE signal, so a status survives a monochrome printout
| and colour-vision deficiency. It is always paired with a text label — the
| dot reinforces, it never carries the meaning alone. This matters more here
| than in most products: these pills report whether a student's case is open,
| escalated or closed.
*/
.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.125rem 0.5625rem;
    border-radius: 9999px;
    font-size: var(--text-xs);
    font-weight: 700;
    line-height: 1.125rem;
    white-space: nowrap;
    letter-spacing: -0.005em;
}
/* Read-only by definition: a pill reports what a record IS, it is never a
   control, so it gets no hover and no press state. */
.pill::before {
    content: '';
    width: 6px; height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
    opacity: 0.85;
}
.pill--neutral { background: var(--color-ink-100);   color: var(--color-ink-600); }
.pill--brand   { background: var(--color-brand-50);  color: var(--color-brand-800); }
.pill--info    { background: var(--color-navy-50);   color: var(--color-navy-700); }
.pill--ok      { background: var(--color-ok-50);     color: var(--color-ok-800); }
.pill--warn    { background: var(--color-warn-50);   color: var(--color-warn-800); }
.pill--alert   { background: var(--color-alert-50);  color: var(--color-alert-800); }
.pill--danger  { background: var(--color-danger-50); color: var(--color-danger-800); }

/* A pill with no dot, for counts and plain tags where there is no status
   being reported. */
.tag {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.0625rem 0.4375rem;
    border-radius: var(--radius-sm);
    background: var(--color-ink-100);
    color: var(--color-ink-600);
    font-size: var(--text-2xs);
    font-weight: 700;
    line-height: 1rem;
    white-space: nowrap;
}

/*
|==========================================================================
| AVATAR
|==========================================================================
*/
.avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 9999px;
    background: var(--color-brand-100);
    color: var(--color-brand-800);
    font-weight: 700;
    letter-spacing: -0.02em;
    overflow: hidden;
    flex-shrink: 0;
    object-fit: cover;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar-xs { width: 1.5rem;  height: 1.5rem;  font-size: 0.625rem; }
.avatar-sm { width: 2rem;    height: 2rem;    font-size: var(--text-2xs); }
.avatar-md { width: 2.5rem;  height: 2.5rem;  font-size: var(--text-sm); }
.avatar-lg { width: 3rem;    height: 3rem;    font-size: var(--text-md); }
.avatar-xl { width: 4.5rem;  height: 4.5rem;  font-size: var(--text-2xl); }

/* Name + secondary line, the commonest table cell in this product. */
.identity { display: flex; align-items: center; gap: 0.625rem; min-width: 0; }
.identity__body { min-width: 0; }
.identity__name {
    font-weight: 700;
    color: var(--color-ink-900);
    letter-spacing: -0.01em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.identity__meta {
    font-size: var(--text-xs);
    color: var(--color-ink-500);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
|==========================================================================
| NAVIGATION RAIL
|==========================================================================
| A white rail, 240px. White rather than dark so attention stays on the
| casework rather than on the furniture, and so the rail does not visually
| shout on a product whose content is often distressing.
*/
.rail {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: var(--sidebar-w);
    z-index: var(--z-rail);
    background: #fff;
    border-right: 1px solid var(--color-ink-200);
    display: flex;
    flex-direction: column;
    transition: width var(--response-base) var(--ease-out),
                transform var(--response-base) var(--ease-out);
}
.app-shell.is-collapsed .rail { width: var(--sidebar-collapsed-w); }

.rail__brand {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    height: var(--topbar-h);
    padding: 0 0.875rem;
    border-bottom: 1px solid var(--color-ink-100);
    flex-shrink: 0;
}
.rail__logo {
    width: 2rem; height: 2rem;
    border-radius: var(--radius-md);
    object-fit: contain;
    flex-shrink: 0;
    background: #fff;
}
.rail__wordmark { min-width: 0; }
.rail__wordmark-title {
    font-size: var(--text-sm);
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--color-navy-700);
    line-height: 1.1;
    white-space: nowrap;
}
.rail__wordmark-sub {
    font-size: var(--text-2xs);
    font-weight: 600;
    color: var(--color-ink-400);
    line-height: 1.2;
    white-space: nowrap;
}

.rail__scroll {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 0.75rem 0.625rem 1rem;
    scrollbar-width: thin;
    scrollbar-color: var(--color-ink-300) transparent;
}

/* Section label above a group of links. Grouping is what makes a
   twenty-item rail comprehensible instead of a list to be read every time. */
.rail__section {
    padding: 0 0.625rem;
    margin: 1rem 0 0.375rem;
    font-size: var(--text-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-ink-400);
    white-space: nowrap;
}
.rail__scroll > .rail__section:first-child { margin-top: 0; }

.rail-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.625rem;
    height: 2.125rem;
    padding: 0 0.625rem;
    margin-bottom: 0.125rem;
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--color-ink-600);
    text-decoration: none;
    transition: background var(--response-instant) var(--ease-out),
                color var(--response-fast) var(--ease-out);
}
.rail-item:hover { background: var(--color-ink-100); color: var(--color-ink-900); }

/* A rail item is pressed to GO somewhere, and navigation is the one
   interaction here with a visible wait behind it. Deepening the tint on
   pointer-down means the rail has already answered before the next page
   paints, so nobody is left wondering whether the click registered.
   Tint only, no scale: shrinking one of twenty aligned rows would break the
   column edge the eye uses to scan the list. */
.rail-item:active { background: var(--color-ink-200); }

/* Active: a filled pill. The left bar underneath is a SHAPE signal so
   "you are here" is not carried by colour alone. */
/* Active: a filled gradient pill with a brand glow. The gradient gives the
   current item real presence in a column of otherwise flat rows — it is the
   one place in this UI where a gradient earns its keep. */
.rail-item--active {
    background: var(--color-brand-600);
    color: #fff;
}
/* Hovering the current item must not wash out its fill: `.rail-item:hover` is
   the same specificity, so the gradient is restated rather than the base hover
   being weakened for every other row. */
.rail-item--active:hover {
    background: var(--color-brand-700);
    color: #fff;
}
.rail-item--active:active { background: var(--color-brand-800); }
/* The icon turns white so it reads against the fill, whatever its tone. */
.rail-item--active .rail-item__icon,
.rail-item--active .rail-item__chevron { color: #fff; }
.rail-item--active .rail-item__count {
    background: rgb(255 255 255 / 0.24);
    color: #fff;
}
/* In forced-colours mode the fill is dropped, so keep an explicit border. */
@media (forced-colors: active) {
    .rail-item--active { border: 1px solid; }
}

/*
 | Each nav icon carries its OWN colour, set per item in the sidebar partial.
 | A twenty-item rail is then scannable by hue as well as by position — the eye
 | goes back to "the amber one" instead of re-reading the list. Crucially there
 | is no tinted box behind the icon: boxes made the rail look like a grid of
 | buttons and were the single noisiest thing in the old design.
 |
 | These hues are CATEGORICAL. They say which section an item belongs to, never
 | whether something is wrong — that is what the count badge and the status
 | pills are for.
 */
/* The icon is an inline stroke SVG on a 20x20 grid (see gs_icon() in
   includes/sidebar.php), not a glyph font. It inherits currentColor, so the
   per-item tone below and the white of the active pill both apply without a
   second rule. */
.rail-item__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.125rem;
    height: 1.125rem;
    flex-shrink: 0;
    color: var(--color-ink-400);
    transition: color var(--response-fast) var(--ease-out);
}
.rail-item__icon svg { width: 1.125rem; height: 1.125rem; display: block; }
.rail-item__icon--brand  { color: var(--color-brand-600); }
.rail-item__icon--navy   { color: var(--color-navy-600); }
.rail-item__icon--sky    { color: var(--color-sky-600); }
.rail-item__icon--teal   { color: var(--color-teal-700); }
.rail-item__icon--violet { color: var(--color-violet-700); }
.rail-item__icon--gold   { color: var(--color-gold-600); }
.rail-item__icon--ok     { color: var(--color-ok-600); }
.rail-item__icon--alert  { color: var(--color-alert-600); }
.rail-item__icon--ink    { color: var(--color-ink-500); }
/* Only an untoned icon warms on hover; a toned one already has its colour. */
.rail-item:hover .rail-item__icon:not([class*='rail-item__icon--']) { color: var(--color-ink-600); }

.rail-item__label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Live work counts — the number of referrals waiting. Reads as a count, not
   a decoration. */
.rail-item__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.125rem;
    padding: 0 0.3125rem;
    border-radius: 9999px;
    background: var(--color-ink-100);
    color: var(--color-ink-600);
    font-size: var(--text-2xs);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    flex-shrink: 0;
}
/* A count that needs action uses alert, never danger — a queue of five
   referrals is work to do, not an error. */
/* Solid, not tinted. A pale chip read as decoration; work waiting for you
   should look like a number someone put there on purpose. */
.rail-item__count {
    background: var(--color-brand-600);
    color: #fff;
}
.rail-item__count--attention {
    background: var(--color-alert-600);
    color: #fff;
}

/* Footer of the rail: the account block. */
.rail__foot {
    border-top: 1px solid var(--color-ink-100);
    padding: 0.625rem;
    flex-shrink: 0;
}

/* ---- Collapsed rail -------------------------------------------------
   Icons only. Labels are hidden rather than removed so screen readers and
   the tooltip both still have the text. */
.app-shell.is-collapsed .rail__wordmark,
.app-shell.is-collapsed .rail-item__label,
.app-shell.is-collapsed .rail-item__count,
.app-shell.is-collapsed .rail__section {
    display: none;
}
.app-shell.is-collapsed .rail__brand { justify-content: center; padding: 0; }
.app-shell.is-collapsed .rail-item { justify-content: center; padding: 0; }
.app-shell.is-collapsed .rail-item__icon,
#sidebar.sidebar-collapsed .rail-item__icon { width: 1.25rem; height: 1.25rem; }
.app-shell.is-collapsed .rail-item__icon svg,
#sidebar.sidebar-collapsed .rail-item__icon svg { width: 1.25rem; height: 1.25rem; }
/* A hairline between groups replaces the hidden section labels, so the
   grouping survives collapse. */
.app-shell.is-collapsed .rail__section + .rail-item {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--color-ink-100);
    border-radius: 0 0 var(--radius-md) var(--radius-md);
}

/* Tooltip for the collapsed rail. Fixed positioning so it escapes the rail's
   overflow rather than being clipped by it. */
.rail-tip {
    position: absolute;
    left: calc(100% + 0.5rem);
    top: 50%;
    transform: translateY(-50%);
    padding: 0.3125rem 0.5625rem;
    background: var(--color-ink-900);
    color: #fff;
    font-size: var(--text-xs);
    font-weight: 600;
    border-radius: var(--radius-sm);
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--response-fast) var(--ease-out);
    z-index: var(--z-tooltip);
    box-shadow: var(--shadow-lift);
}
.app-shell.is-collapsed .rail-item:hover .rail-tip,
.app-shell.is-collapsed .rail-item:focus-visible .rail-tip { opacity: 1; }
.app-shell:not(.is-collapsed) .rail-tip { display: none; }

/* ---- Mobile rail ----------------------------------------------------
   Below lg the rail slides in over the content as a drawer. */
@media (max-width: 1023px) {
    .rail {
        transform: translateX(-100%);
        box-shadow: none;
        width: var(--sidebar-w);
    }
    .app-shell.is-rail-open .rail {
        transform: translateX(0);
        box-shadow: var(--shadow-modal);
    }
    /* Collapse is a desktop affordance only; on mobile the drawer is either
       open or shut, so the collapsed rules must not apply. */
    .app-shell.is-collapsed .rail { width: var(--sidebar-w); }
    .app-shell.is-collapsed .rail__wordmark,
    .app-shell.is-collapsed .rail-item__label,
    .app-shell.is-collapsed .rail-item__count,
    .app-shell.is-collapsed .rail__section { display: revert; }
    .app-shell.is-collapsed .rail__brand { justify-content: flex-start; padding: 0 0.875rem; }
    .app-shell.is-collapsed .rail-item { justify-content: flex-start; padding: 0 0.625rem; }
    .app-shell.is-collapsed .rail__section + .rail-item { border-top: 0; margin-top: 0.125rem; padding-top: 0; }
}

.rail-scrim {
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-rail) - 1);
    background: rgb(23 29 38 / 0.45);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--response-base) var(--ease-out);
}
@media (max-width: 1023px) {
    .app-shell.is-rail-open .rail-scrim { opacity: 1; pointer-events: auto; }
}

/*
|==========================================================================
| TOPBAR
|==========================================================================
*/
.topbar {
    position: sticky;
    top: 0;
    z-index: var(--z-topbar);
    height: var(--topbar-h);
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0 var(--page-pad-x);
    background: color-mix(in srgb, #fff 88%, transparent);
    backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--color-ink-200);
    flex-shrink: 0;
}
@media (max-width: 767px) {
    .topbar { padding: 0 var(--page-pad-x-sm); }
}
.topbar__spacer { flex: 1; }

/* Global search. */
.topbar__search { flex: 0 1 22rem; min-width: 0; }
@media (max-width: 767px) { .topbar__search { display: none; } }

/* A small circular indicator on the notifications bell. */
.icon-btn-dot { position: relative; }
.icon-btn-dot::after {
    content: '';
    position: absolute;
    top: 0.4375rem;
    right: 0.5rem;
    width: 0.4375rem;
    height: 0.4375rem;
    border-radius: 50%;
    background: var(--color-alert-600);
    box-shadow: 0 0 0 2px #fff;
}

/*
|==========================================================================
| DROPDOWN MENU
|==========================================================================
*/
.menu {
    position: absolute;
    min-width: 11rem;
    padding: 0.3125rem;
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lift);
    z-index: var(--z-overlay);
}
.menu__item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.4375rem 0.5625rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--color-ink-700);
    text-align: left;
    text-decoration: none;
    background: none;
    border: 0;
    cursor: pointer;
    transition: background var(--response-instant) var(--ease-out);
}
.menu__item:hover { background: var(--color-ink-100); color: var(--color-ink-900); }
.menu__item--danger { color: var(--color-danger-700); }
.menu__item--danger:hover { background: var(--color-danger-50); }
.menu__item i, .menu__item svg { width: 1rem; text-align: center; color: var(--color-ink-400); flex-shrink: 0; }
.menu__item--danger i { color: inherit; }
.menu__sep { height: 1px; margin: 0.3125rem 0; background: var(--color-ink-100); }
.menu__label {
    padding: 0.375rem 0.5625rem 0.1875rem;
    font-size: var(--text-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-ink-400);
}

/*
|==========================================================================
| TABS
|==========================================================================
*/
.tabs {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    border-bottom: 1px solid var(--color-ink-200);
    overflow-x: auto;
    scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.4375rem;
    padding: 0.5625rem 0.75rem;
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--color-ink-500);
    white-space: nowrap;
    text-decoration: none;
    background: none;
    border: 0;
    cursor: pointer;
    transition: color var(--response-fast) var(--ease-out);
}
.tab:hover { color: var(--color-ink-800); }
/* The underline sits on the container's border so the active tab reads as
   joined to the panel below it. */
.tab--active { color: var(--color-brand-700); }
.tab--active::after {
    content: '';
    position: absolute;
    left: 0.375rem; right: 0.375rem;
    bottom: -1px;
    height: 2px;
    border-radius: 2px 2px 0 0;
    background: var(--color-brand-600);
}
.tab__count {
    font-size: var(--text-2xs);
    font-weight: 800;
    padding: 0 0.3125rem;
    border-radius: 9999px;
    background: var(--color-ink-100);
    color: var(--color-ink-600);
    font-variant-numeric: tabular-nums;
}
.tab--active .tab__count { background: var(--color-brand-100); color: var(--color-brand-800); }

/* Segmented control — a compact alternative for two or three exclusive
   options inside a toolbar. */
.segmented {
    display: inline-flex;
    padding: 0.1875rem;
    background: var(--color-ink-100);
    border-radius: var(--radius-md);
    gap: 0.125rem;
}
.segmented__item {
    padding: 0.3125rem 0.625rem;
    border-radius: var(--radius-sm);
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--color-ink-500);
    background: none;
    border: 0;
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--response-fast) var(--ease-out),
                color var(--response-fast) var(--ease-out);
}
.segmented__item:hover { color: var(--color-ink-800); }
.segmented__item--active {
    background: #fff;
    color: var(--color-ink-900);
    box-shadow: var(--shadow-xs);
}

/*
|==========================================================================
| ALERTS
|==========================================================================
| Inline, in-page messages. Always icon + text, never colour alone.
*/
.alert {
    display: flex;
    align-items: flex-start;
    gap: 0.625rem;
    padding: 0.75rem 0.875rem;
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    line-height: 1.45;
    border: 1px solid transparent;
}
.alert__icon { flex-shrink: 0; margin-top: 0.0625rem; font-size: 0.875rem; }
.alert__title { font-weight: 700; letter-spacing: -0.01em; }
.alert__body { min-width: 0; }
.alert--info   { background: var(--color-brand-50);  border-color: var(--color-brand-100);  color: var(--color-brand-900); }
.alert--ok     { background: var(--color-ok-50);     border-color: var(--color-ok-200);     color: var(--color-ok-800); }
.alert--warn   { background: var(--color-warn-50);   border-color: var(--color-warn-200);   color: var(--color-warn-800); }
.alert--alert  { background: var(--color-alert-50);  border-color: var(--color-alert-200);  color: var(--color-alert-800); }
.alert--danger { background: var(--color-danger-50); border-color: var(--color-danger-200); color: var(--color-danger-800); }

/*
|==========================================================================
| TOASTS
|==========================================================================
| Transient confirmations, bottom-right on desktop and top on mobile where
| the bottom of the screen is thumb territory.
*/
.toast-region {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    pointer-events: none;
    max-width: calc(100vw - 2rem);
}
@media (max-width: 639px) {
    .toast-region { right: 0.75rem; left: 0.75rem; bottom: auto; top: calc(var(--topbar-h) + 0.5rem); }
}
.toast {
    display: flex;
    align-items: flex-start;
    gap: 0.625rem;
    padding: 0.6875rem 0.875rem;
    min-width: 16rem;
    background: var(--color-ink-900);
    color: #fff;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lift);
    font-size: var(--text-sm);
    font-weight: 600;
    pointer-events: auto;
    animation: toast-in var(--response-base) var(--ease-out);
}
@keyframes toast-in {
    from { opacity: 0; transform: translateY(0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
}
.toast--ok     .toast__icon { color: #4ade80; }
.toast--danger .toast__icon { color: #f87171; }
.toast--warn   .toast__icon { color: #fbbf24; }
.toast__close { margin-left: auto; opacity: 0.6; background: none; border: 0; color: inherit; cursor: pointer; }
.toast__close:hover { opacity: 1; }

/*
|==========================================================================
| MODAL / DRAWER
|==========================================================================
| One width scale, one padding rhythm, one header/footer structure. The old
| UI had modals ranging from 380px to full-bleed with no pattern.
*/
.overlay {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgb(23 29 38 / 0.5);
    backdrop-filter: blur(2px);
    overflow-y: auto;
}

/*
 | Click-outside-to-dismiss. The overlay itself is the scrim, so this element
 | carries no appearance at all — it exists purely as a hit target that sits
 | behind the dialog, because the existing scripts bind their dismiss handlers
 | to a dedicated backdrop element by id. Without it, a click listener on
 | `.overlay` would also fire for clicks inside the dialog.
 */
.overlay__backdrop { position: absolute; inset: 0; }

/*
 | Stacking for a dialog opened from within another dialog — a confirmation
 | raised over an editor, or a picker raised over that. Two named steps rather
 | than the z-[60] / z-[70] arbitrary values these replaced.
 */
.overlay--above { z-index: calc(var(--z-modal) + 1); }
.overlay--top   { z-index: calc(var(--z-modal) + 2); }

.modal {
    position: relative;
    width: 100%;
    max-width: 32rem;
    max-height: calc(100vh - 2rem);
    display: flex;
    flex-direction: column;
    background: #fff;
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-modal);
    animation: modal-in var(--response-base) var(--ease-out);
}
@keyframes modal-in {
    from { opacity: 0; transform: translateY(0.75rem) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-sm { max-width: 26rem; }
.modal-lg { max-width: 44rem; }
.modal-xl { max-width: 60rem; }

.modal__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.125rem 1.25rem 0.875rem;
    border-bottom: 1px solid var(--color-ink-100);
    flex-shrink: 0;
}
.modal__title {
    font-size: var(--text-xl);
    line-height: var(--text-xl-lh);
    font-weight: 800;
    letter-spacing: -0.025em;
    color: var(--color-ink-900);
}
.modal__desc { margin-top: 0.1875rem; font-size: var(--text-sm); color: var(--color-ink-500); }
.modal__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.875rem; height: 1.875rem;
    margin: -0.25rem -0.375rem 0 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-ink-400);
    cursor: pointer;
    flex-shrink: 0;
    transition: background var(--response-fast) var(--ease-out), color var(--response-fast) var(--ease-out);
}
.modal__close:hover { background: var(--color-ink-100); color: var(--color-ink-800); }
.modal__body { padding: 1.25rem; overflow-y: auto; flex: 1; }
.modal__footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.875rem 1.25rem;
    border-top: 1px solid var(--color-ink-100);
    background: var(--color-ink-25);
    border-radius: 0 0 var(--radius-xl) var(--radius-xl);
    flex-shrink: 0;
}
@media (max-width: 639px) {
    .overlay { padding: 0; align-items: flex-end; }
    /* A sheet from the bottom is the right shape on a phone: it is where the
       thumb is, and it keeps the page visible behind it. */
    .modal {
        max-width: none;
        max-height: 92vh;
        border-radius: var(--radius-xl) var(--radius-xl) 0 0;
        animation: sheet-in var(--response-slow) var(--ease-out);
    }
    @keyframes sheet-in {
        from { transform: translateY(100%); }
        to   { transform: translateY(0); }
    }
    .modal__footer {
        border-radius: 0;
        flex-direction: column-reverse;
        padding-bottom: max(0.875rem, env(safe-area-inset-bottom));
    }
    .modal__footer .btn { width: 100%; }
}

/* Side drawer, for record detail alongside a list. */
.drawer {
    position: fixed;
    top: 0; right: 0; bottom: 0;
    width: min(32rem, 100vw);
    z-index: var(--z-modal);
    background: #fff;
    box-shadow: var(--shadow-modal);
    display: flex;
    flex-direction: column;
    animation: drawer-in var(--response-base) var(--ease-out);
}
@keyframes drawer-in {
    from { transform: translateX(100%); }
    to   { transform: translateX(0); }
}

/* Destructive confirmation. The icon plus the word, so the consequence is
   legible before the button is read. */
.confirm-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem; height: 2.5rem;
    border-radius: 9999px;
    background: var(--color-danger-50);
    color: var(--color-danger-600);
    flex-shrink: 0;
}
/* Not every confirmation is destructive. Deactivating a student, archiving a
   form or cancelling an appointment are all reversible, and dressing a
   reversible action in red is exactly what teaches people to click through
   red dialogs without reading them. */
.confirm-icon--warn  { background: var(--color-warn-50);  color: var(--color-warn-700); }
.confirm-icon--alert { background: var(--color-alert-50); color: var(--color-alert-700); }
.confirm-icon--brand { background: var(--color-brand-50); color: var(--color-brand-700); }

/* The icon beside the question, rather than a centred column: the eye reaches
   the sentence in one move instead of travelling down past the icon. */
.confirm-row { display: flex; align-items: flex-start; gap: 0.875rem; }

/*
|==========================================================================
| EMPTY / LOADING / ERROR STATES
|==========================================================================
| These are part of the product, not an afterthought. An empty queue should
| tell a counsellor that there is genuinely nothing waiting — not look like
| the page failed to load.
*/
.empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 3rem 1.5rem;
}
.empty__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem; height: 3rem;
    border-radius: 9999px;
    background: var(--color-ink-100);
    color: var(--color-ink-400);
    font-size: 1.125rem;
    margin-bottom: 0.875rem;
}
.empty__title {
    font-size: var(--text-lg);
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--color-ink-800);
}
.empty__desc {
    margin-top: 0.3125rem;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--color-ink-500);
    max-width: 38ch;
}
.empty__actions { margin-top: 1.125rem; display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; }
/* A compact variant for inside a card or a table body. */
.empty--sm { padding: 2rem 1rem; }
.empty--sm .empty__icon { width: 2.25rem; height: 2.25rem; font-size: 0.875rem; margin-bottom: 0.625rem; }
.empty--sm .empty__title { font-size: var(--text-base); }

/* Skeletons. A slow page that shows nothing reads as broken. */
.skeleton {
    background: linear-gradient(90deg,
        var(--color-ink-100) 25%,
        var(--color-ink-200) 37%,
        var(--color-ink-100) 63%);
    background-size: 400% 100%;
    border-radius: var(--radius-sm);
    animation: skeleton-sweep 1.4s ease-in-out infinite;
}
@keyframes skeleton-sweep {
    from { background-position: 100% 50%; }
    to   { background-position: 0 50%; }
}
.skeleton-text { height: 0.75rem; border-radius: 9999px; }
.skeleton-title { height: 1.125rem; width: 40%; border-radius: 9999px; }
.skeleton-circle { border-radius: 9999px; }

/* Spinner for in-place loading — a button that is submitting, a panel that
   is fetching. */
.spinner {
    display: inline-block;
    width: 1rem; height: 1rem;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: spin 0.6s linear infinite;
    opacity: 0.75;
}
@keyframes spin { to { transform: rotate(360deg); } }
.spinner-lg { width: 1.75rem; height: 1.75rem; border-width: 2.5px; }

/* Thin progress bar at the top of the viewport during navigation. */
.nav-progress {
    position: fixed;
    top: 0; left: 0;
    z-index: var(--z-tooltip);
    width: 100%;
    height: 2.5px;
    transform: scaleX(0);
    transform-origin: 0 50%;
    background: var(--color-brand-500);
    opacity: 0;
    pointer-events: none;
    transition: transform 0.2s var(--ease-out), opacity 0.2s var(--ease-out);
}
.nav-progress.is-active { opacity: 1; }
.nav-progress.is-done {
    transition: transform 0.12s var(--ease-out), opacity 0.3s ease-in 0.1s;
    opacity: 0;
}

/*
|==========================================================================
| MISC UTILITIES
|==========================================================================
*/
/* A definition row — the commonest shape on a record detail page. */
.dl { display: grid; grid-template-columns: minmax(8rem, 12rem) 1fr; gap: 0.5rem 1rem; }
.dl dt {
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--color-ink-500);
    letter-spacing: -0.005em;
}
.dl dd { margin: 0; font-size: var(--text-sm); color: var(--color-ink-800); font-weight: 500; }
@media (max-width: 639px) {
    .dl { grid-template-columns: minmax(0, 1fr); gap: 0.125rem; }
    .dl dd { margin-bottom: 0.625rem; }
}

/* A horizontal rule that matches the system rather than the browser. */
.rule { height: 1px; background: var(--color-ink-100); border: 0; margin: 1.25rem 0; }

/* Timeline, for activity feeds and case history. */
.timeline { position: relative; padding-left: 1.5rem; }
.timeline::before {
    content: '';
    position: absolute;
    left: 0.3125rem; top: 0.375rem; bottom: 0.375rem;
    width: 1.5px;
    background: var(--color-ink-200);
}
.timeline__item { position: relative; padding-bottom: 1rem; }
.timeline__item:last-child { padding-bottom: 0; }
.timeline__dot {
    position: absolute;
    left: -1.5rem;
    top: 0.3125rem;
    width: 0.6875rem; height: 0.6875rem;
    border-radius: 50%;
    background: var(--color-ink-300);
    box-shadow: 0 0 0 3px var(--color-ink-50);
}
.timeline__dot--brand  { background: var(--color-brand-500); }
.timeline__dot--ok     { background: var(--color-ok-600); }
.timeline__dot--alert  { background: var(--color-alert-600); }
.timeline__dot--danger { background: var(--color-danger-600); }
.timeline__time { font-size: var(--text-2xs); color: var(--color-ink-400); font-weight: 600; }
.timeline__body { font-size: var(--text-sm); color: var(--color-ink-700); line-height: 1.45; }

/* Responsive grid helpers, so dashboards do not each invent a breakpoint. */
.grid-metrics {
    display: grid;
    gap: 0.75rem;
    /* `min()` rather than a bare 13rem: on a 390px phone, 13rem (208px) plus
       gutters and the gap overflows one column, and `auto-fit` cannot shrink a
       track below its minimum — which pushed the tiles off the right edge.
       Capping the minimum at 100% lets the track collapse to the available
       width instead. */
    grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
}
.grid-2 { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { display: grid; gap: 1rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
/* The dashboard's main split: work on the left, context on the right. */
.grid-main { display: grid; gap: 1rem; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
@media (max-width: 1023px) {
    .grid-3, .grid-main { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .grid-main { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 767px) {
    .grid-2, .grid-3 { grid-template-columns: minmax(0, 1fr); }
}

/* Print: drop the furniture, keep the record. Case files get printed. */
@media print {
    .rail, .topbar, .rail-scrim, .toast-region, .no-print { display: none !important; }
    .app-main { margin-left: 0 !important; }
    .page { max-width: none; padding: 0; }
    .card { box-shadow: none; border: 1px solid var(--color-ink-300); break-inside: avoid; }
    body { background: #fff; font-size: 11pt; }
    a[href]::after { content: ''; }
}

/*
|==========================================================================
| LIGHT MODE IS THE ONLY MODE
|==========================================================================
| A belt-and-braces guard. includes/footer.php already removes the `dark`
| class and clears any stored dark preference, so the ~6,700 inert `dark:`
| variants left in the views never activate.
|
| This block is the second line of defence: if any page re-adds the class
| (an inline script, a cached bundle, a browser extension), these rules keep
| the app on its designed surfaces rather than dropping into the abandoned
| dark theme. Colour is forced back at the root; the `dark:` utilities are
| Tailwind-generated and already neutralised by the missing class, so there
| is nothing further to override here.
*/
html.dark,
html.dark body {
    background-color: var(--color-ink-50);
    color: var(--color-ink-800);
    color-scheme: light;
}

/* Tell the browser to render native controls (scrollbars, form widgets,
   date pickers) in their light styling regardless of the OS setting. Without
   this, a user on a dark OS gets dark scrollbars and dark date pickers
   against the light UI — one of the specific inconsistencies that made the
   old theme feel unfinished. */
:root {
    color-scheme: light;
}


/*
|==========================================================================
| ADMIN PAGE COMPONENTS
|==========================================================================
| Three shapes that every admin page was building for itself, each with its
| own padding, icon size and hover: a shortcut link, an activity/queue row,
| and the small figures under a stat. They are defined once here so the
| dashboard's "Recent Activity", the student page's sidebar and the user
| page's login history are visibly the same component.
*/

/* ---- Shortcut link -------------------------------------------------
   A card-sized link to elsewhere in the product. Only ever a real
   destination, never a button in disguise. */
.qa-link {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 0.875rem;
    border-radius: var(--radius-lg);
    background: #fff;
    box-shadow: var(--shadow-card);
    text-decoration: none;
    transition: box-shadow var(--response-fast) var(--ease-out),
                transform var(--response-fast) var(--ease-out);
}
.qa-link:hover { box-shadow: var(--shadow-panel); transform: translateY(-1px); }
.qa-link:active { transform: translateY(0); }
.qa-icon {
    width: 2.25rem; height: 2.25rem;
    border-radius: var(--radius-md);
    background: var(--color-brand-50);
    color: var(--color-brand-700);
    display: flex; align-items: center; justify-content: center;
    font-size: 0.8125rem;
    flex-shrink: 0;
}
.qa-title { font-size: var(--text-sm); font-weight: 700; color: var(--color-ink-900); letter-spacing: -0.01em; }
.qa-sub   { font-size: var(--text-xs); color: var(--color-ink-500); margin-top: 0.0625rem; }
.qa-arrow {
    margin-left: auto;
    color: var(--color-ink-300);
    font-size: 0.625rem;
    transition: color var(--response-fast) var(--ease-out),
                transform var(--response-fast) var(--ease-out);
}
.qa-link:hover .qa-arrow { color: var(--color-brand-600); transform: translateX(2px); }

/* ---- Feed row ------------------------------------------------------
   One row shape for activity logs, queues, appointment lists and login
   history. `.feed--scroll` caps the height so a long list does not push the
   rest of the page down. */
.feed { padding: 0.375rem 1.125rem 0.75rem; }
.feed--scroll { overflow-y: auto; max-height: 21rem; }
.feed-row {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.625rem 0;
    border-bottom: 1px solid var(--color-ink-100);
}
.feed-row:last-child { border-bottom: none; }
a.feed-row, .feed-row--action {
    cursor: pointer;
    border-radius: var(--radius-md);
    margin: 0 -0.5rem;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    text-decoration: none;
    transition: background var(--response-instant) var(--ease-out);
}
a.feed-row:hover, .feed-row--action:hover { background: var(--color-ink-50); }
.feed-row__icon {
    width: 2rem; height: 2rem;
    border-radius: 9999px;
    display: flex; align-items: center; justify-content: center;
    font-size: 0.6875rem;
    flex-shrink: 0;
    margin-top: 0.0625rem;
    background: var(--color-ink-100);
    color: var(--color-ink-500);
}
.feed-row__body { flex: 1; min-width: 0; }
.feed-row__title {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--color-ink-900);
    line-height: 1.4;
    display: flex; align-items: center; gap: 0.375rem; flex-wrap: wrap;
}
.feed-row__text { font-size: var(--text-sm); color: var(--color-ink-800); font-weight: 500; line-height: 1.45; }
.feed-row__excerpt {
    font-size: var(--text-xs);
    color: var(--color-ink-500);
    margin-top: 0.125rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.feed-row__meta {
    display: flex; align-items: center; gap: 0.375rem; flex-wrap: wrap;
    margin-top: 0.1875rem;
    font-size: var(--text-2xs);
    color: var(--color-ink-400);
}
.feed-row__meta strong { color: var(--color-ink-600); font-weight: 700; }
.feed-row__meta i { font-size: 0.5625rem; }
.feed-row__aside { flex-shrink: 0; }

/* Tone variants. Categorical or semantic, never danger unless the record
   itself is an error or a deletion. */
.feed-row__icon--ok     { background: var(--color-ok-50);     color: var(--color-ok-700); }
.feed-row__icon--brand  { background: var(--color-brand-50);  color: var(--color-brand-700); }
.feed-row__icon--navy   { background: var(--color-navy-50);   color: var(--color-navy-600); }
.feed-row__icon--warn   { background: var(--color-warn-50);   color: var(--color-warn-700); }
.feed-row__icon--alert  { background: var(--color-alert-50);  color: var(--color-alert-700); }
.feed-row__icon--danger { background: var(--color-danger-50); color: var(--color-danger-700); }
.feed-row__icon--teal   { background: var(--color-teal-100);  color: var(--color-teal-700); }
.feed-row__icon--violet { background: var(--color-violet-100);color: var(--color-violet-700); }

/* A row for something happening today. Brand, and a left bar so the marking
   survives monochrome — a meeting today is a fact, not an alarm. */
.feed-row--today {
    background: var(--color-brand-50);
    box-shadow: inset 2px 0 0 var(--color-brand-600);
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    padding-left: 0.625rem;
}
.feed-row--today:hover { background: var(--color-brand-100); }

/* ---- Stat breakdown ------------------------------------------------
   The small figures beneath a stat's headline number — "Active 12 ·
   Leave 2". They read as a breakdown of the figure above, not as three
   competing values. */
.stat-split { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 0.375rem; }
.stat-split__item {
    display: inline-flex;
    align-items: center;
    gap: 0.3125rem;
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--color-ink-600);
}
.stat-split__item i { font-size: 0.5rem; }
.stat-split__item--ok    i { color: var(--color-ok-600); }
.stat-split__item--warn  i { color: var(--color-warn-600); }
.stat-split__item--muted i { color: var(--color-ink-400); }
.stat-split__item--brand i { color: var(--color-brand-600); }
.stat-split__item--navy  i { color: var(--color-navy-600); }

/* ---- Filter tile ---------------------------------------------------
   A count that is also a filter — the role breakdown on the users page.
   Being a link, it gets a pressed state rather than a coloured border, so
   five equivalent categories do not read as five severities. */
.role-tile {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.75rem 0.875rem;
    border-radius: var(--radius-lg);
    background: var(--color-ink-25);
    box-shadow: inset 0 0 0 1px var(--color-ink-100);
    text-decoration: none;
    transition: background var(--response-fast) var(--ease-out),
                box-shadow var(--response-fast) var(--ease-out);
}
.role-tile:hover { background: #fff; box-shadow: var(--shadow-card); }
.role-tile--active {
    background: var(--color-brand-50);
    box-shadow: inset 0 0 0 1.5px var(--color-brand-600);
}
.role-tile__icon {
    width: 2rem; height: 2rem;
    border-radius: var(--radius-md);
    background: #fff;
    color: var(--color-ink-500);
    display: flex; align-items: center; justify-content: center;
    font-size: 0.75rem;
    flex-shrink: 0;
    box-shadow: var(--shadow-xs);
}
.role-tile--active .role-tile__icon { background: var(--color-brand-600); color: #fff; box-shadow: none; }
.role-tile__label {
    display: block;
    font-size: var(--text-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-ink-400);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.role-tile--active .role-tile__label { color: var(--color-brand-700); }
.role-tile__count {
    display: block;
    margin-top: 0.125rem;
    font-size: var(--text-xl);
    line-height: 1.1;
    font-weight: 800;
    letter-spacing: -0.025em;
    color: var(--color-ink-900);
    font-variant-numeric: tabular-nums;
}
.role-tile--active .role-tile__count { color: var(--color-brand-800); }
.role-tile__check {
    position: absolute;
    top: 0.5rem; right: 0.625rem;
    font-size: 0.5625rem;
    color: var(--color-brand-600);
}
/*
|==========================================================================
| LEGACY COMPATIBILITY
|==========================================================================
| The views were written against an earlier, ad-hoc set of class names. These
| rules map those names onto the design system so ~60 pages pick up the new
| visual language without their markup being rewritten. They are a migration
| aid: as pages are converted to the `.card__header` / `.card__body`
| structure, the corresponding rule here can go.
*/

/* The old `.card` carried its own 20px padding, and most usages still rely on
   it (only some add a `p-4` utility). The design-system `.card` is a bare
   surface whose padding lives on `.card__body`, so without this a converted
   page would keep its padding while an unconverted one would lose it. Bare
   cards therefore keep a default pad, which is dropped as soon as the card
   uses the structured parts or sets its own Tailwind padding. */
.card { padding: 1.125rem; }
.card:has(> .card__header),
.card:has(> .card__body),
.card:has(> .card__footer) { padding: 0; }

/* Browsers without :has() — the rule above is progressive enhancement, so
   give the structured parts their own reset as a fallback. */
.card > .card__header:first-child { margin: -1.125rem -1.125rem 0; }
.card > .card__body { margin: 0 -1.125rem; padding-left: 1.125rem; padding-right: 1.125rem; }
.card > .card__footer:last-child { margin: 0 -1.125rem -1.125rem; }
.card:has(> .card__header) > .card__header:first-child,
.card:has(> .card__body) > .card__body,
.card:has(> .card__footer) > .card__footer:last-child { margin: 0; }

/* `.stat-card` and `.metric` were separate surfaces with their own borders and
   shadows; both are the one card surface now. */
.stat-card, .metric { border: 0; }

/* The old coloured left borders on dashboard tiles. Kept as a token-driven
   accent rather than the raw hex values that were scattered inline. */
.border-blue    { box-shadow: var(--shadow-card), inset 3px 0 0 var(--color-brand-500); }
.border-emerald { box-shadow: var(--shadow-card), inset 3px 0 0 var(--color-ok-600); }
.border-amber   { box-shadow: var(--shadow-card), inset 3px 0 0 var(--color-warn-600); }
.border-pink    { box-shadow: var(--shadow-card), inset 3px 0 0 var(--color-violet-500); }
/* `.border-red` marked a *count* tile, not an error, so it uses alert rather
   than danger — red here would read as "something has gone wrong" on a
   dashboard where nothing has. */
.border-red     { box-shadow: var(--shadow-card), inset 3px 0 0 var(--color-alert-600); }

/* `.info-card` — the record-detail panel used across the student, teacher and
   referral views. */
.info-card {
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    padding: 1.125rem;
}
.info-card-title {
    font-size: var(--text-2xs);
    line-height: var(--text-2xs-lh);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-ink-400);
    margin-bottom: 0.625rem;
}

/* The staggered entrance used on dashboard tiles. Kept because removing it
   would leave the `anim d2..d7` classes in the markup doing nothing, but
   shortened and eased so the dashboard settles quickly instead of animating
   at the user. prefers-reduced-motion is already honoured globally above. */
.anim {
    animation: card-rise var(--response-base) var(--ease-out) backwards;
}
@keyframes card-rise {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
}
.anim.d2 { animation-delay: 0.02s; }
.anim.d3 { animation-delay: 0.04s; }
.anim.d4 { animation-delay: 0.06s; }
.anim.d5 { animation-delay: 0.08s; }
.anim.d6 { animation-delay: 0.10s; }
.anim.d7 { animation-delay: 0.12s; }

/*
|==========================================================================
| ROGUE BRAND COLOUR — #1e64df
|==========================================================================
| A third brand blue, unrelated to both the school seal and the old Tailwind
| config, was hardcoded 957 times across the views as Tailwind arbitrary
| values: `bg-[#1e64df]`, `text-[#1e64df]`, `focus:ring-[#1e64df]/30` and so
| on. Together with #3b82f6 (53) and #2563eb (51) that is over a thousand
| literal colour values, and it is the single largest reason the product read
| as several different applications stitched together.
|
| Arbitrary values are emitted literally by Tailwind and never consult the
| theme, so remapping the token in tailwind.config cannot reach them. They are
| overridden here instead, mapping the rogue blue onto the seal sky.
|
| This is a migration aid, not the destination: as each view is converted to
| the brand-* utilities these selectors become dead and the block shrinks. It
| is deliberately scoped to the exact generated class names so it can never
| affect anything that already uses the design system.
|
| !important is required — these selectors have the same specificity as the
| Tailwind utilities they override, and the utility stylesheet is injected by
| the CDN at runtime, after this file.
*/

/* Solid fills and text */
.bg-\[\#1e64df\]      { background-color: var(--color-brand-600) !important; }
.text-\[\#1e64df\]    { color: var(--color-brand-600) !important; }
.border-\[\#1e64df\]  { border-color: var(--color-brand-600) !important; }
.ring-\[\#1e64df\]    { --tw-ring-color: var(--color-brand-600) !important; }
.fill-\[\#1e64df\]    { fill: var(--color-brand-600) !important; }
.stroke-\[\#1e64df\]  { stroke: var(--color-brand-600) !important; }

/* Tinted grounds. The seal sky is lighter than the blue it replaces, so the
   low-alpha fills are given a touch more presence to stay visible. */
.bg-\[\#1e64df\]\/5   { background-color: rgb(67 97 238 / 0.06) !important; }
.bg-\[\#1e64df\]\/10  { background-color: rgb(67 97 238 / 0.10) !important; }
.bg-\[\#1e64df\]\/20  { background-color: rgb(67 97 238 / 0.16) !important; }
.bg-\[\#1e64df\]\/90  { background-color: var(--color-brand-700) !important; }
.text-\[\#1e64df\]\/60{ color: rgb(52 81 209 / 0.72) !important; }
.border-\[\#1e64df\]\/15 { border-color: rgb(67 97 238 / 0.16) !important; }
.border-\[\#1e64df\]\/20 { border-color: rgb(67 97 238 / 0.22) !important; }
.border-\[\#1e64df\]\/30 { border-color: rgb(67 97 238 / 0.32) !important; }
.border-\[\#1e64df\]\/40 { border-color: rgb(67 97 238 / 0.42) !important; }
.border-\[\#1e64df\]\/50 { border-color: rgb(67 97 238 / 0.52) !important; }

/* Gradient stops. */
.from-\[\#1e64df\] { --tw-gradient-from: var(--color-brand-600) !important;
                     --tw-gradient-to: rgb(67 97 238 / 0) !important;
                     --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important; }
.to-\[\#1e64df\]   { --tw-gradient-to: var(--color-brand-600) !important; }
.via-\[\#1e64df\]  { --tw-gradient-stops: var(--tw-gradient-from), var(--color-brand-600), var(--tw-gradient-to) !important; }

/* Interactive states. */
.hover\:bg-\[\#1e64df\]:hover        { background-color: var(--color-brand-600) !important; }
.hover\:bg-\[\#1e64df\]\/90:hover    { background-color: var(--color-brand-700) !important; }
.hover\:bg-\[\#1e64df\]\/10:hover    { background-color: rgb(67 97 238 / 0.10) !important; }
.hover\:bg-\[\#1e64df\]\/20:hover    { background-color: rgb(67 97 238 / 0.16) !important; }
.hover\:text-\[\#1e64df\]:hover      { color: var(--color-brand-600) !important; }
.hover\:border-\[\#1e64df\]:hover    { border-color: var(--color-brand-600) !important; }
.group:hover .group-hover\:bg-\[\#1e64df\]\/10 { background-color: rgb(67 97 238 / 0.10) !important; }
.group:hover .group-hover\:text-\[\#1e64df\]   { color: var(--color-brand-600) !important; }

/* Focus rings. Every variant collapses to one ring colour — the old file had
   five different ring opacities, so the focus ring changed strength depending
   on which input you tabbed into. */
.focus\:ring-\[\#1e64df\]:focus,
.focus\:ring-\[\#1e64df\]\/20:focus,
.focus\:ring-\[\#1e64df\]\/30:focus,
.focus\:ring-\[\#1e64df\]\/40:focus,
.focus\:ring-\[\#1e64df\]\/50:focus { --tw-ring-color: rgb(67 97 238 / 0.38) !important; }
.focus\:border-\[\#1e64df\]:focus   { border-color: var(--color-brand-500) !important; }

/*
| The other stray blues, same treatment. #3b82f6 and #2563eb are Tailwind's
| own blue-500/blue-600 used as arbitrary values in a handful of views.
*/
.bg-\[\#3b82f6\]     { background-color: var(--color-brand-500) !important; }
.text-\[\#3b82f6\]   { color: var(--color-brand-600) !important; }
.border-\[\#3b82f6\] { border-color: var(--color-brand-500) !important; }
.from-\[\#3b82f6\]   { --tw-gradient-from: var(--color-brand-500) !important;
                       --tw-gradient-to: rgb(0 168 228 / 0) !important;
                       --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important; }
.to-\[\#3b82f6\]     { --tw-gradient-to: var(--color-brand-500) !important; }

.bg-\[\#2563eb\]     { background-color: var(--color-brand-600) !important; }
.text-\[\#2563eb\]   { color: var(--color-brand-700) !important; }
.border-\[\#2563eb\] { border-color: var(--color-brand-600) !important; }
.from-\[\#2563eb\]   { --tw-gradient-from: var(--color-brand-600) !important;
                       --tw-gradient-to: rgb(67 97 238 / 0) !important;
                       --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important; }
.to-\[\#2563eb\]     { --tw-gradient-to: var(--color-brand-600) !important; }

.bg-\[\#4f46e5\]     { background-color: var(--color-brand-600) !important; }
.text-\[\#4f46e5\]   { color: var(--color-brand-600) !important; }
.bg-\[\#6366f1\]     { background-color: var(--color-brand-500) !important; }
.text-\[\#6366f1\]   { color: var(--color-brand-600) !important; }
.bg-\[\#0ea5e9\]     { background-color: var(--color-brand-500) !important; }
.text-\[\#0ea5e9\]   { color: var(--color-brand-600) !important; }

/*
|==========================================================================
| OVERFLOW GUARD
|==========================================================================
| A phone must never scroll sideways. Wide content — data tables, code, long
| unbroken reference numbers — scrolls inside its own container instead, which
| is what `.table-wrap` is for. These are the backstops for anything that slips
| past it.
*/
html, body { max-width: 100%; overflow-x: hidden; }

/* Grid and flex children default to `min-width:auto`, which lets a wide child
   (a long table, a nowrap cell) force its track wider than the viewport. This
   is the single most common cause of horizontal overflow in a dashboard. */
.page > *, .app-main, .grid-metrics > *, .grid-2 > *, .grid-3 > *, .grid-main > * {
    min-width: 0;
}

/* Media should never exceed its column. */
img, svg, video, canvas { max-width: 100%; height: auto; }

/*
|==========================================================================
| QUICK-ACTION LINK
|==========================================================================
| A row that offers a route somewhere: icon, a title with a one-line
| explanation, and a chevron. Used in the side column of the record pages
| ("Students by batch", "View all students", …).
|
| It is a list row rather than a card because these are secondary routes; a
| grid of cards here would compete with the record table that is the actual
| subject of the page.
*/
.qa-link {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem 0.625rem;
    margin: 0 -0.625rem;
    border-radius: var(--radius-md);
    text-decoration: none;
    transition: background var(--response-instant) var(--ease-out);
}
.qa-link + .qa-link { margin-top: 0.125rem; }
.qa-link:hover { background: var(--color-ink-100); }
.qa-link:hover .qa-arrow { color: var(--color-ink-500); transform: translateX(1px); }

.qa-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    background: var(--color-brand-50);
    color: var(--color-brand-700);
    font-size: 0.8125rem;
}
.qa-title {
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--color-ink-800);
    line-height: 1.25;
}
.qa-sub {
    font-size: var(--text-xs);
    color: var(--color-ink-500);
    line-height: 1.3;
}
.qa-arrow {
    margin-left: auto;
    font-size: 0.625rem;
    color: var(--color-ink-300);
    flex-shrink: 0;
    transition: color var(--response-fast) var(--ease-out),
                transform var(--response-fast) var(--ease-out);
}

/*
|==========================================================================
| TABLE ROW ACTIONS
|==========================================================================
| Icon-only controls in a table's last column. They are ghost buttons, not
| filled ones: a solid red delete button repeated down every row makes a
| roster of students look like a list of problems, and it spends the one
| colour this product reserves for genuine danger on a control that is merely
| present. The destructive intent is carried on hover and in the confirmation
| dialog instead.
*/
.row-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-ink-400);
    font-size: 0.75rem;
    cursor: pointer;
    text-decoration: none;
    transition: background var(--response-fast) var(--ease-out),
                color var(--response-fast) var(--ease-out);
}
.row-action:hover { background: var(--color-ink-100); color: var(--color-ink-700); }
.row-action--danger:hover { background: var(--color-danger-50); color: var(--color-danger-700); }
@media (pointer: coarse) {
    .row-action { width: 2.25rem; height: 2.25rem; }
}

/*
| An empty state rendered inside a table cell.
|
| `.data-table tbody td` is `white-space: nowrap`, which is right for data rows
| — a queue is scanned down a column and one wrapping cell breaks that rhythm —
| but wrong for the empty state's sentence of explanatory prose, which was
| running straight off the right edge of the card. The state is centred in the
| cell and allowed to wrap within a comfortable measure.
*/
.data-table td > .empty,
.data-table td > .empty--sm {
    white-space: normal;
    margin-inline: auto;
}
.data-table td:has(> .empty) { white-space: normal; }
.stat__icon--brand { background: var(--color-brand-50); color: var(--color-brand-700); }

/*
|==========================================================================
| ACTION GRID
|==========================================================================
| Quick actions across the full content width.
|
| These were `.qa-link` rows — a list style built for the NARROW side column —
| stretched edge to edge across 1400px. At that width a row is mostly empty
| space with a label stranded on the left, which is why the block read as thin
| and unfinished. Across the full width they become proper cards: bordered,
| evenly sized, capped so four of them do not each become 340px wide.
*/
/*
 | Three to a row, always — not `auto-fit`.
 |
 | With auto-fit the count changed with the viewport: four across on a wide
 | monitor, two on a laptop, and a lonely orphan on the second row whenever the
 | list was not divisible by the column count. A fixed three reads as a
 | deliberate set, and it is what keeps these cards the same size on every page
 | that uses them.
 */
.grid-actions {
    display: grid;
    gap: 0.75rem;
    /* Four across. Fixed rather than auto-fit so the cards are the same width
       on every page that uses them and the row never leaves an orphan. */
    grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 1279px) { .grid-actions { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 639px)  { .grid-actions { grid-template-columns: minmax(0, 1fr); } }

.grid-actions .qa-link { align-items: center; min-height: 3.25rem; }
.grid-actions .qa-link > span:not(.qa-icon) { min-width: 0; flex: 1; }
.grid-actions .qa-title,
.grid-actions .qa-sub {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.grid-actions .qa-link {
    margin: 0;
    padding: 0.875rem 1rem;
    background: #fff;
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
}
.grid-actions .qa-link:hover {
    background: #fff;
    box-shadow: var(--shadow-panel);
    transform: translateY(-1px);
}
.grid-actions .qa-link { transition: box-shadow var(--response-fast) var(--ease-out),
                                     transform var(--response-fast) var(--ease-out); }
.grid-actions .qa-link:active { transform: translateY(0); }

/* Categorical tones, so a row of actions is scannable rather than four
   identical blue squares. */
.qa-icon--brand  { background: var(--color-brand-50);   color: var(--color-brand-700); }
.qa-icon--navy   { background: var(--color-navy-50);    color: var(--color-navy-600); }
.qa-icon--sky    { background: var(--color-sky-50);     color: var(--color-sky-700); }
.qa-icon--teal   { background: var(--color-teal-100);   color: var(--color-teal-700); }
.qa-icon--violet { background: var(--color-violet-100); color: var(--color-violet-700); }
.qa-icon--gold   { background: var(--color-gold-50);    color: var(--color-gold-700); }
.qa-icon--ink { background: var(--color-ink-100); color: var(--color-ink-500); }

/*
|==========================================================================
| RAIL PROFILE CARD
|==========================================================================
| Sits directly under the brand, as in the RMS rail: avatar centred on top,
| then the name, then a role pill.
|
| Which account is signed in is security-relevant in a shared guidance office
| — staff leave a browser open on a machine other staff use, and a counselling
| note must never be filed under the wrong name. Stating it at the top of the
| rail is better than hiding it in a menu at the bottom.
*/
.rail-profile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.375rem;
    padding: 1.25rem 1rem 1.375rem;
    border-bottom: 1px solid var(--color-ink-100);
    flex-shrink: 0;
    background: var(--color-ink-25);
}
.rail-avatar {
    width: 4rem;
    height: 4rem;
    border-radius: 9999px;
    object-fit: cover;
    flex-shrink: 0;
    /* A white ring then a soft brand shadow, so the avatar reads as lifted off
       the tinted card rather than pasted onto it. */
    box-shadow: 0 0 0 2.5px #fff, 0 3px 10px rgb(67 97 238 / 0.22);
    transition: width var(--response-base) var(--ease-out),
                height var(--response-base) var(--ease-out);
}
.rail-avatar--initials {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-brand-600);
    color: #fff;
    font-size: var(--text-2xl);
    font-weight: 800;
    letter-spacing: -0.02em;
}
.rail-profile__text { text-align: center; min-width: 0; width: 100%; }
.rail-profile__name {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 800;
    letter-spacing: -0.015em;
    color: var(--color-ink-900);
    line-height: 1.25;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail-profile__role {
    display: inline-block;
    margin-top: 0.3125rem;
    padding: 0.125rem 0.625rem;
    border-radius: 9999px;
    background: var(--color-brand-100);
    color: var(--color-brand-800);
    font-size: var(--text-xs);
    font-weight: 700;
}
/* Collapsed: the avatar alone, smaller. */
.app-shell.is-collapsed .rail-profile,
#sidebar.sidebar-collapsed .rail-profile { padding: 0.75rem 0.5rem; }
.app-shell.is-collapsed .rail-avatar,
#sidebar.sidebar-collapsed .rail-avatar { width: 2.25rem; height: 2.25rem; font-size: var(--text-xs); }

/*
|==========================================================================
| DASHBOARD HERO
|==========================================================================
| A dashboard needs no page title — the rail already says where you are. What
| it needs is to date itself (these are shared office machines) and offer the
| two or three things this role actually starts with.
|
| This is the one place a gradient earns its keep: it marks the top of the
| product's busiest screen and gives the app a moment of colour before the
| work begins. Everywhere else stays flat on purpose.
*/
.hero {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-lg);
    margin-bottom: 1rem;
    padding: 1.5rem 1.75rem;
    background: linear-gradient(135deg, var(--color-brand-800) 0%, var(--color-brand-700) 45%, var(--color-brand-600) 100%);
}
/* Soft highlights so the panel reads as a lit surface rather than a flat
   block of colour. Purely decorative, and behind the content. */
.hero::before,
.hero::after {
    content: '';
    position: absolute;
    border-radius: 9999px;
    background: rgb(255 255 255 / 0.05);
    pointer-events: none;
}
.hero::before { width: 16rem; height: 16rem; right: -4rem; top: -6rem; }
.hero::after  { width: 14rem; height: 14rem; right: -1rem; bottom: -7rem; }

.hero__inner {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
@media (min-width: 640px) {
    .hero__inner { flex-direction: row; align-items: flex-end; justify-content: space-between; }
}
.hero__date {
    font-size: var(--text-xs);
    font-weight: 700;
    color: rgb(255 255 255 / 0.72);
}
.hero__title {
    margin-top: 0.375rem;
    font-size: var(--text-3xl);
    line-height: 1.15;
    font-weight: 800;
    letter-spacing: -0.03em;
    color: #fff;
}
.hero__sub {
    margin-top: 0.375rem;
    font-size: var(--text-sm);
    font-weight: 500;
    color: rgb(255 255 255 / 0.78);
    max-width: 62ch;
}
.hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; flex-shrink: 0; }

/* On the dark panel the usual button variants would disappear, so these are
   styled for this ground: a white solid for the primary action, a translucent
   outline for the rest. */
.hero__btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4375rem;
    height: 2.25rem;
    padding: 0 0.875rem;
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    font-weight: 700;
    letter-spacing: -0.01em;
    text-decoration: none;
    border: 1px solid transparent;
    transition: background var(--response-fast) var(--ease-out);
}
.hero__btn--primary { background: #fff; color: var(--color-brand-800); }
.hero__btn--primary:hover { background: rgb(255 255 255 / 0.9); }
.hero__btn--ghost { border-color: rgb(255 255 255 / 0.3); color: #fff; }
.hero__btn--ghost:hover { background: rgb(255 255 255 / 0.12); }
.hero__btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.hero__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    padding: 0 0.375rem;
    border-radius: 9999px;
    font-size: var(--text-2xs);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}
.hero__btn--primary .hero__count { background: var(--color-brand-100); color: var(--color-brand-800); }
.hero__btn--ghost   .hero__count { background: rgb(255 255 255 / 0.2); color: #fff; }

@media (max-width: 639px) {
    .hero { padding: 1.25rem 1.125rem; }
    .hero__title { font-size: var(--text-2xl); }
}
@media print { .hero { display: none; } }

/* Sign out is destructive-adjacent: it reads as a normal row until you reach
   for it, so a rail footer is not permanently tinted red. */
.rail-item--signout:hover { background: var(--color-danger-50); color: var(--color-danger-700); }
.rail-item--signout:hover .rail-item__icon { color: var(--color-danger-700); }

/*
|==========================================================================
| TOPBAR ACCOUNT MENU
|==========================================================================
| An avatar at the right of the bar that opens a small menu. This is where
| people look for their account, and putting it here let the rail drop the
| three permanent account rows that were taking space at the bottom of the
| navigation.
|
| Built on <details>/<summary>: no script needed, Escape and outside-click are
| a few lines, and it cannot get wedged open if something else on the page
| throws.
*/
.acct { position: relative; flex-shrink: 0; }
.acct__btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 9999px;
    cursor: pointer;
    list-style: none;
    transition: box-shadow var(--response-fast) var(--ease-out);
}
.acct__btn::-webkit-details-marker { display: none; }
.acct__btn:hover { box-shadow: 0 0 0 3px var(--color-ink-100); }
.acct[open] .acct__btn { box-shadow: 0 0 0 3px var(--color-brand-100); }
.acct__btn:focus-visible { outline: 2px solid var(--color-brand-600); outline-offset: 2px; }

.acct__avatar {
    width: 2rem;
    height: 2rem;
    border-radius: 9999px;
    object-fit: cover;
    display: block;
}
.acct__avatar--initials {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-brand-600);
    color: #fff;
    font-size: var(--text-xs);
    font-weight: 800;
}

.acct__menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    width: 14rem;
    padding: 0;
    z-index: var(--z-overlay);
}
.acct__head {
    padding: 0.625rem 0.75rem;
    border-bottom: 1px solid var(--color-ink-100);
}
.acct__name {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--color-ink-900);
    letter-spacing: -0.01em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acct__meta {
    margin: 0;
    font-size: var(--text-2xs);
    color: var(--color-ink-500);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acct__body { padding: 0.3125rem; }

/*
|==========================================================================
| CALENDAR
|==========================================================================
| The month grid on the dashboard.
|
| It read as a loose spreadsheet: every cell was a full-width block, so the
| selected day became a wide bar rather than a marked date, and the whole
| panel took far more height than a month of numbers needs. Cells are now
| square-ish and centred, today is a circle, and the grid sits on a fixed
| seven-column track so the columns line up with their day letters.
*/
.cal-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 1px;
}
.cal-day-lbl {
    padding: 0.25rem 0;
    text-align: center;
    font-size: var(--text-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--color-ink-400);
}
.cal-day {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    /* A square cell keeps the month compact and makes the round "today"
       marker sit in the middle of its date rather than on a wide bar. */
    aspect-ratio: 1 / 1;
    max-height: 2.5rem;
    margin: 0 auto;
    width: 100%;
    max-width: 2.5rem;
    border-radius: 9999px;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--color-ink-700);
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: background var(--response-instant) var(--ease-out),
                color var(--response-fast) var(--ease-out);
}
.cal-day:hover { background: var(--color-ink-100); color: var(--color-ink-900); }
.cal-day.today {
    background: var(--color-brand-600);
    color: #fff;
    font-weight: 800;
}
.cal-day.today:hover { background: var(--color-brand-700); }
.cal-day.muted { color: var(--color-ink-300); pointer-events: none; }
.cal-day.has-events { font-weight: 800; color: var(--color-ink-900); }

/* Event dots sit under the number, inside the cell. */
.cal-dot-row {
    display: flex;
    gap: 2px;
    align-items: center;
    justify-content: center;
    height: 4px;
    line-height: 0;
}
.ev-dot { width: 4px; height: 4px; border-radius: 50%; flex-shrink: 0; }
.cal-day.today .ev-dot { box-shadow: 0 0 0 1px rgb(255 255 255 / 0.8); }

.cal-month {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.5rem;
}
.cal-month-label {
    font-size: var(--text-base);
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--color-ink-900);
}

/*
|==========================================================================
| TONED STAT TILES
|==========================================================================
| A row of five identical white boxes on a grey page has nothing for the eye
| to catch: every metric looks like the same metric, and the page reads as
| flat regardless of how well it is spaced. The icon chip alone was too small
| to carry it.
|
| Each tile now takes its tone on three surfaces at once — a 3px top bar, the
| icon chip, and the figure itself. That is enough colour to make the row
| scannable ("the violet one is junior high") while the card stays white and
| the page stays calm. The tones are CATEGORICAL: they identify a metric, they
| never say whether it is good or bad.
*/
/* No coloured bar across the top of the card — the user asked for it gone.
   The tone lives on the icon chip and the figure, which is enough to tell the
   tiles apart without drawing a stripe on every card. */

/* The figure takes the tone too. It is the loudest thing in the tile, so it
   is where the colour does the most work. */
.stat__value { color: var(--stat-ink, var(--color-ink-900)); }
.stat:has(.stat__icon--brand)  .stat__value { color: var(--color-brand-700); }
.stat:has(.stat__icon--navy)   .stat__value { color: var(--color-navy-700); }
.stat:has(.stat__icon--sky)    .stat__value { color: var(--color-sky-700); }
.stat:has(.stat__icon--teal)   .stat__value { color: var(--color-teal-700); }
.stat:has(.stat__icon--violet) .stat__value { color: var(--color-violet-700); }
.stat:has(.stat__icon--gold)   .stat__value { color: var(--color-gold-700); }

/* Lifting on hover tells you the whole tile is a link — several of them are. */
a.stat:hover { box-shadow: var(--shadow-panel); transform: translateY(-1px); }
a.stat { transition: box-shadow var(--response-fast) var(--ease-out),
                     transform var(--response-fast) var(--ease-out); }
a.stat:active { transform: translateY(0); }

/*
| SECTION HEADINGS get the brand, so a page has a spine of colour running down
| it rather than one coloured band at the top and grey thereafter.
*/
.section-title { color: var(--color-brand-700); }

/*
| CARD HEADERS sit on a faint tint rather than pure white, so the header and
| the body are distinguishable without another border line.
*/
.card__header { background: var(--color-ink-25); border-radius: var(--radius-lg) var(--radius-lg) 0 0; }

/*
|==========================================================================
| DISPLAY GUARD FOR `.hidden`
|==========================================================================
| Every dialog in this codebase is opened and closed by toggling Tailwind's
| `.hidden` from JavaScript — `modal.classList.add('hidden')`. Tailwind's CDN
| build is injected BEFORE this stylesheet, so `.overlay { display: flex }`
| beat `.hidden { display: none }` on specificity-tie-broken-by-order, and a
| "closed" dialog stayed on screen covering the page.
|
| Rather than rewrite ~40 call sites to a different mechanism, the components
| that set their own `display` re-assert `.hidden` here. This is also why the
| scripts harmlessly add a `flex` class alongside: it is a no-op now.
*/
.overlay.hidden,
.menu.hidden,
.drawer.hidden,
.toast-region.hidden { display: none; }

/*
|==========================================================================
| KILL THE DARK VARIANTS FOR GOOD
|==========================================================================
| Several unconverted pages still carry ~6,700 `dark:` utility classes. The
| Tailwind config sets `darkMode: 'class'` and nothing ever adds that class,
| so in theory they are inert — but the CDN build was still emitting them
| behind `@media (prefers-color-scheme: dark)`, which means every page with
| `dark:bg-slate-900` on its main column rendered BLACK for anyone whose OS is
| in dark mode. That is what "some pages are still black" was.
|
| Rather than strip the classes from 162 files, the dark surfaces are pinned
| back to the light palette inside the same media query. `!important` is
| required: these compete with utilities of identical specificity that the CDN
| injects after this file.
|
| This block is scoped to SURFACE and TEXT colours only. It deliberately does
| not touch layout, spacing or borders, so a page that has not been converted
| yet still lays out exactly as it did — it just stays light.
*/
@media (prefers-color-scheme: dark) {
    html, body { background-color: var(--color-ink-50) !important; color: var(--color-ink-800) !important; }

    /* Dark page and panel grounds -> the app canvas / white card. */
    [class*="dark:bg-slate-9"], [class*="dark:bg-gray-9"], [class*="dark:bg-zinc-9"],
    [class*="dark:bg-slate-95"], [class*="dark:bg-neutral-9"] {
        background-color: var(--color-ink-50) !important;
    }
    [class*="dark:bg-slate-8"], [class*="dark:bg-gray-8"], [class*="dark:bg-zinc-8"],
    [class*="dark:bg-slate-7"], [class*="dark:bg-gray-7"] {
        background-color: #ffffff !important;
    }

    /* Light-on-dark text -> the ink scale. */
    [class*="dark:text-white"], [class*="dark:text-gray-1"], [class*="dark:text-gray-2"],
    [class*="dark:text-slate-1"], [class*="dark:text-slate-2"] {
        color: var(--color-ink-900) !important;
    }
    [class*="dark:text-gray-3"], [class*="dark:text-gray-4"],
    [class*="dark:text-slate-3"], [class*="dark:text-slate-4"] {
        color: var(--color-ink-500) !important;
    }

    /* Dark borders and dividers. */
    [class*="dark:border-slate-"], [class*="dark:border-gray-"] {
        border-color: var(--color-ink-200) !important;
    }
    [class*="dark:divide-slate-"], [class*="dark:divide-gray-"] > * + * {
        border-color: var(--color-ink-200) !important;
    }
}

/*
|==========================================================================
| QUICK-ACTION BUTTON THAT OPENS A DIALOG
|==========================================================================
| `.qa-link` is an anchor that goes somewhere. Some quick actions instead
| open a dialog in place — "Recent activity" is the first, moved out of the
| side column so the record table can use the full width.
|
| It is a real <button> so it is keyboard-reachable and announced correctly,
| but it wears the same box as its sibling links: a row of actions should not
| look like two different components because one navigates and one opens.
| The trailing glyph is what distinguishes them — a chevron points away, a
| dialog icon says "opens here".
*/
button.qa-link {
    width: 100%;
    border: 0;
    background: #fff;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.qa-arrow--dialog { transform: none; }
.grid-actions button.qa-link:hover .qa-arrow { transform: none; }

/*
|==========================================================================
| REQUEST DETAILS DIALOG
|==========================================================================
| The read-only view of a student's document request
| (admin/get_request_details.php, loaded into a dialog by manage_request.php).
*/
.rq-head {
    display: flex;
    align-items: flex-start;
    gap: 0.875rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--color-ink-100);
}
.rq-head__body { flex: 1; min-width: 0; }
.rq-head__name {
    margin: 0;
    font-size: var(--text-lg);
    font-weight: 700;
    letter-spacing: -0.015em;
    color: var(--color-ink-900);
}
.rq-head__mail {
    margin: 0.125rem 0 0;
    font-size: var(--text-sm);
    color: var(--color-ink-500);
    overflow-wrap: anywhere;
}
.rq-head__meta {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.375rem;
    flex-shrink: 0;
}
.rq-head__ref {
    margin: 0;
    font-size: var(--text-2xs);
    font-weight: 700;
    color: var(--color-ink-400);
    font-variant-numeric: tabular-nums;
}

.rq-dl { margin: 1rem 0 0; }
.rq-dim { color: var(--color-ink-400); }

.rq-block { margin-top: 1.25rem; }

/* The student's own words, quoted. */
.rq-quote {
    margin: 0;
    padding: 0.625rem 0 0.625rem 0.875rem;
    border-left: 2px solid var(--color-ink-200);
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--color-ink-800);
}

/* The office's reply.
   Previously a solid blue panel with blue text, which made a rejection letter
   the loudest element in the dialog. It is correspondence on the record, so it
   is quoted in ordinary ink against a brand hairline — present and readable,
   not shouting. `pre-line` keeps the letter's paragraph breaks. */
.rq-note {
    padding: 0.75rem 0 0.75rem 0.875rem;
    border-left: 2px solid var(--color-brand-300);
    font-size: var(--text-sm);
    line-height: 1.65;
    color: var(--color-ink-700);
    white-space: pre-line;
}

.rq-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-ink-100);
}
