/* =====================================================================
   PMT Health Care Institution - Student Portal
   Institutional design system.

   Design language: medical indigo, white and grey; sharp rectangular geometry;
   thin-line icons; restrained motion. Circles appear only where a shape
   is genuinely circular - the institution logo, status dots, chart arcs
   and progress rings.
   ===================================================================== */

/* ---------------------------------------------------------------------
   1. Design tokens
   --------------------------------------------------------------------- */
:root {
    /* Institutional purple, taken from the PMT crest itself, with a trace of
       the medical navy left in it so the interface is the logo's colour
       rather than an approximation of it. */
    --brand:          #473A82;
    --brand-2:        #5A4A9C;
    --brand-light:    #F0EDF9;
    --brand-border:   #CFC8E8;
    --brand-dark:     #2E2457;

    /* Institutional red. The crest carries no red, so this is the secondary
       accent only: the public call to action and the accent word of the
       masthead. Status red stays --danger, a darker and cooler red, so a
       branded button is never mistaken for a warning. */
    --red:            #C8372C;
    --red-dark:       #A32318;
    --red-light:      #F2888A;

    /* Neutrals: the dark-grey ramp.
       Text, labels, borders, rules and dividers use the dark greys below;
       the two lightest steps stay light because they are surfaces
       (page background, disabled fields, panel tints) and must not be
       darkened without losing legibility. */
    --white:           #FFFFFF;
    --grey-50:         #F9FAFB;   /* surface tint - stays light */
    --grey-100:        #F3F4F6;   /* page / disabled surfaces - stays light */
    --grey-200:        #C7CDD5;   /* dividers, rules, table borders */
    --grey-300:        #A8AFBB;   /* control borders */
    --grey-500:        #4B5563;   /* muted text, labels, headers */
    --grey-700:        #2B3441;   /* body and secondary text */
    --grey-900:        #1F2937;   /* headings */

    /* Semantic */
    --success:         #15803D;
    --success-bg:      #ECFDF3;
    --danger:          #B91C1C;
    --danger-bg:       #FEF2F2;
    --warning:         #B45309;
    --warning-bg:      #FFFBEB;
    --info:            #1D4ED8;
    --info-bg:         #EFF6FF;

    /* Typography */
    --font: "Plus Jakarta Sans", "Inter", -apple-system, BlinkMacSystemFont,
            "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

    /* Figures.
       Inter carries the words; IBM Plex Mono carries the numbers. Its digits
       are fixed-width and drawn to be told apart at a glance - a slashed
       zero, a serifed one - which is what a mark, an amount or a student
       number needs. Every numeral in the portal is set in it so that figures
       in a column line up and no digit can be mistaken for a letter. */
    --font-num: "IBM Plex Mono", "SF Mono", Consolas, "Liberation Mono", monospace;

    --fs-base: 15.5px;

    /* Corner radius: a gentle rounding on portal shapes, kept subtle so the
       institutional look stays crisp rather than playful. */
    --radius:    8px;
    --radius-lg: 10px;

    /* Spacing scale */
    --sp-1: 4px;
    --sp-2: 8px;
    --sp-3: 12px;
    --sp-4: 16px;
    --sp-5: 20px;
    --sp-6: 24px;
    --sp-8: 32px;
    --sp-10: 40px;
    --sp-12: 48px;

    /* Extended purple ramp, for gradient stops only */
    --brand-600:       #574A99;
    --brand-500:       #6757B0;
    --brand-accent:   #9A8AD8;

    /* -----------------------------------------------------------------
       Gradients
       Two axes, used consistently: 135deg for anything interactive
       (buttons, chips, icon tiles) so the light reads from the top-left,
       and 180deg for surfaces (bars, headers, footers) so panels feel lit
       from above. Nothing here is decorative - each one carries depth or
       hierarchy.
       ----------------------------------------------------------------- */
    --grad-brand:       linear-gradient(135deg, var(--brand-600) 0%, var(--brand) 100%);
    --grad-brand-hover: linear-gradient(135deg, var(--brand-500) 0%, var(--brand-2) 100%);
    --grad-deep:        linear-gradient(180deg, var(--brand) 0%, var(--brand-dark) 100%);
    --grad-bar:         linear-gradient(180deg, #4F4294 0%, var(--brand) 100%);
    --grad-rule:        linear-gradient(90deg, var(--brand) 0%, var(--brand-accent) 55%, transparent 100%);
    --grad-tile:        linear-gradient(135deg, var(--brand-light) 0%, #E2DCF2 100%);
    --grad-surface:     linear-gradient(180deg, var(--white) 0%, var(--grey-50) 100%);
    /* The band behind card headers and table column heads. Three stops down
       the brand ramp rather than two, so the band has depth of its own and
       reads as the institution's colour rather than a grey bar; the footer
       keeps the light surface. */
    --grad-head:        linear-gradient(180deg, var(--brand-2) 0%, var(--brand) 46%, var(--brand-dark) 100%);
    /* Table column-head band: the dark slate of the footer, used across every
       portal so column headings read as a neutral deep band rather than a
       second purple. */
    --grad-thead:       linear-gradient(180deg, #3C4552 0%, #2F3742 55%, #252C36 100%);
    --grad-red:         linear-gradient(135deg, var(--red) 0%, var(--red-dark) 100%);
    --grad-foot:        linear-gradient(180deg, var(--grey-50) 0%, #EFF0F3 100%);
    --grad-success:     linear-gradient(135deg, #1B9D4B 0%, var(--success) 100%);
    --grad-danger:      linear-gradient(135deg, #D62828 0%, var(--danger) 100%);
    --grad-warning:     linear-gradient(135deg, #D97A09 0%, var(--warning) 100%);

    /* -----------------------------------------------------------------
       Sidebar theme tokens

       The navigation is the one surface an institution may want to recolour,
       so it reads from its own tokens rather than the brand ones. A theme
       below redefines these and nothing else: buttons, links, badges and
       every other branded element stay PMT indigo whichever theme is set.
       ----------------------------------------------------------------- */
    --side-bg:        linear-gradient(180deg, var(--brand) 0%, var(--brand-dark) 55%, #241B45 100%);
    --side-ink:       #FFFFFF;                        /* nav item text */
    /* Every letter and every icon in the navigation is plain white. Section
       labels and icons were previously set back behind the item text; they
       are not any more, so nothing in the column is dimmed and the contrast
       holds on all themes. Hierarchy is carried by size, weight and letter
       spacing instead of by fading the colour. */
    --side-muted:     #FFFFFF;                        /* section labels, footer */
    --side-icon:      #FFFFFF;                        /* icons, at rest and active */
    --side-hover:     rgba(255, 255, 255, 0.09);
    --side-active:    rgba(255, 255, 255, 0.16);
    --side-accent:    #C6B9EC;                        /* active rail and focus ring */
    --side-divider:   rgba(255, 255, 255, 0.13);
    --side-shadow:    rgba(20, 24, 56, 0.30);

    /* Structure */
    --sidebar-w: 212px;
    --topbar-h: 60px;
    --shadow-sm: 0 1px 2px rgba(31, 41, 55, 0.06);
    --shadow: 0 1px 3px rgba(31, 41, 55, 0.10), 0 1px 2px rgba(31, 41, 55, 0.06);
    --shadow-lg: 0 8px 24px rgba(31, 41, 55, 0.14);
    --shadow-brand: 0 6px 18px rgba(71, 58, 130, 0.28);
    --shadow-lift: 0 10px 28px rgba(31, 41, 55, 0.13);
}

/* ---------------------------------------------------------------------
   1b. Sidebar themes

   Set institution-wide by an administrator under Settings, written onto
   <html data-theme="..."> by the layout. Each theme only redefines the
   --side-* tokens above. Every one keeps light text on a dark field, so
   the contrast the navigation relies on holds whichever is chosen.
   --------------------------------------------------------------------- */

/* Indigo - the PMT default, carried by :root above. */
[data-theme="midnight"] {
    --side-bg:      linear-gradient(180deg, #1B2437 0%, #141B29 55%, #0E1420 100%);
    --side-accent:  #7FA7E8;
    --side-shadow:  rgba(10, 15, 25, 0.38);
}

[data-theme="slate"] {
    --side-bg:      linear-gradient(180deg, #3C4552 0%, #2F3742 55%, #252C36 100%);
    --side-accent:  #B8C4D4;
    --side-shadow:  rgba(20, 25, 32, 0.32);
}

[data-theme="forest"] {
    --side-bg:      linear-gradient(180deg, #1E4034 0%, #17332A 55%, #10241D 100%);
    --side-accent:  #7FD1A6;
    --side-shadow:  rgba(10, 28, 22, 0.34);
}

[data-theme="ocean"] {
    --side-bg:      linear-gradient(180deg, #11435C 0%, #0D3449 55%, #092737 100%);
    --side-accent:  #74C8E6;
    --side-shadow:  rgba(8, 32, 45, 0.34);
}

[data-theme="burgundy"] {
    --side-bg:      linear-gradient(180deg, #5A1F2E 0%, #471825 55%, #35111B 100%);
    --side-accent:  #E8A3B4;
    --side-shadow:  rgba(40, 12, 20, 0.34);
}

/* ---------------------------------------------------------------------
   2. Reset and base
   --------------------------------------------------------------------- */
*,
*::before,
*::after {
    box-sizing: border-box;
    /* Rule A: rectangular geometry is the default for every component. */
    border-radius: 0;
}

html {
    -webkit-text-size-adjust: 100%;
}

body {
    margin: 0;
    background-attachment: fixed;
    font-family: var(--font);
    font-size: var(--fs-base);
    line-height: 1.55;
    color: var(--grey-700);
    background: var(--grey-100);
    -webkit-font-smoothing: antialiased;
    /* Long unbroken strings (emails, IDs, URLs) break instead of pushing
       their container past the page edge. */
    overflow-wrap: break-word;
}

h1, h2, h3, h4, h5, h6 {
    margin: 0 0 var(--sp-3);
    color: var(--grey-900);
    font-weight: 600;
    line-height: 1.3;
}

h1 { font-size: 1.6rem; }
h2 { font-size: 1.3rem; }
h3 { font-size: 1.1rem; }
h4 { font-size: 1rem; }

p { margin: 0 0 var(--sp-3); }

a {
    color: var(--brand-2);
    text-decoration: none;
}

a:hover { text-decoration: underline; }

img { max-width: 100%; height: auto; }

hr {
    border: 0;
    border-top: 1px solid var(--grey-200);
    margin: var(--sp-5) 0;
}

code, kbd, pre {
    font-family: "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.88em;
}

:focus-visible {
    outline: 2px solid var(--brand-2);
    outline-offset: 2px;
}

::selection {
    background: var(--brand-light);
    color: var(--brand-dark);
}

.icon {
    flex-shrink: 0;
    vertical-align: -0.18em;

    /* An SVG clips to its viewBox by default. Several of these icons are
       drawn right up to the edge of the 24-unit box, so half the stroke
       width fell outside it and was cut away - which is why a shoulder or
       the rim of a card ended in a flat shaved edge. Letting the strokes
       paint outside the box keeps every line whole. */
    overflow: visible;
}

/* Visible only to screen readers */
.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 999;
    padding: var(--sp-3) var(--sp-4);
    background: var(--brand);
    color: var(--white);
}

.skip-link:focus {
    left: 0;
}

/* ---------------------------------------------------------------------
   3. Utilities
   --------------------------------------------------------------------- */
.container {
    width: 100%;
    max-width: 1180px;
    margin: 0 auto;
    padding: 0 var(--sp-4);
}

.container--wide { max-width: 1420px; }

.stack > * + * { margin-top: var(--sp-4); }

.row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
}

.row--between { justify-content: space-between; }
.row--center  { align-items: center; }
.row--end     { justify-content: flex-end; }
.row--tight   { gap: var(--sp-2); }

.grid { display: grid; gap: var(--sp-4); }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid--sidebar { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

.muted      { color: var(--grey-500); }
.small      { font-size: 0.86rem; }
.tiny       { font-size: 0.78rem; }
.strong     { font-weight: 600; }
.mono       { font-family: var(--font-num); letter-spacing: 0; }
.text-right { text-align: right; }
.text-center{ text-align: center; }
.nowrap     { white-space: nowrap; }
.uppercase  { text-transform: uppercase; letter-spacing: 0.06em; }

.text-success { color: var(--success); }
.text-danger  { color: var(--danger); }
.text-warning { color: var(--warning); }
.text-brand  { color: var(--brand); }

/* Money figures.
   Digits are lining and tabular, so an amount always occupies the same
   advance per digit: columns of figures line up digit for digit and a long
   balance does not wobble against a short one. The heavier weight picks the
   amount out of the row it sits in. The face itself stays the portal face
   (Inter first), whose figures are drawn for this. */
.money {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums lining-nums;
    font-feature-settings: "tnum" 1, "lnum" 1;
    font-weight: 600;
    letter-spacing: 0;
}

/* Every other place a figure is read off the screen rather than read as
   prose: table number columns, mark boxes, page numbers and the codes and
   student numbers carried by .mono. All of them want the same fixed-width
   figures, so they share one face. */
.table td.numeric,
.table th.numeric,
.mark-input,
.pagination__link,
.detail-list dd .num,
.num {
    font-family: var(--font-num);
    font-variant-numeric: tabular-nums lining-nums;
    font-feature-settings: "tnum" 1, "lnum" 1;
}

/* A heading is still set in the UI face - only the figures beneath it move. */
.table th.numeric { font-family: var(--font); }

/* An amount never wraps mid-figure; the table wrapper scrolls instead. */
.table td.money,
.table th.money { white-space: nowrap; }


.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }
.mt-4 { margin-top: var(--sp-4); }
.mb-4 { margin-bottom: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }
.mb-6 { margin-bottom: var(--sp-6); }

.full-width { width: 100%; }

/* ---------------------------------------------------------------------
   4. Buttons
   --------------------------------------------------------------------- */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    padding: 9px var(--sp-4);
    min-height: 38px;
    border-radius: var(--radius);
    border: 1px solid transparent;
    background: var(--grad-brand);
    color: var(--white);
    font-family: inherit;
    font-size: 0.92rem;
    font-weight: 500;
    line-height: 1.2;
    text-align: center;
    cursor: pointer;
    box-shadow: var(--shadow-sm);
    transition: background-image 0.12s ease, border-color 0.12s ease,
                color 0.12s ease, box-shadow 0.12s ease;
}

.btn:hover {
    background: var(--grad-brand-hover);
    text-decoration: none;
    color: var(--white);
    box-shadow: var(--shadow-brand);
}

.btn:active {
    background: var(--grad-deep);
    box-shadow: none;
}

.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: 0.55;
    cursor: not-allowed;
}

.btn--secondary {
    background: var(--white);
    border-color: var(--brand-2);
    color: var(--brand);
}

.btn--secondary:hover {
    background: var(--brand-light);
    border-color: var(--brand-2);
    color: var(--brand);
}

.btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--brand-2);
}

.btn--ghost:hover {
    background: var(--brand-light);
    color: var(--brand);
}

.btn--danger  { background: var(--grad-danger); }
.btn--danger:hover  { background: linear-gradient(135deg, #E03131 0%, #A81A1A 100%); }

.btn--success { background: var(--grad-success); }
.btn--success:hover { background: linear-gradient(135deg, #22AE56 0%, #15803D 100%); }

.btn--sm {
    min-height: 31px;
    padding: 5px var(--sp-3);
    font-size: 0.82rem;
}

.btn--lg {
    min-height: 46px;
    padding: 12px var(--sp-6);
    font-size: 1rem;
}

/* Icon-only action button: a square tap target holding a single glyph, for
   dense table rows where three labelled buttons would not fit the page. The
   label is kept in title + an .sr-only span, so it stays discoverable on
   hover and available to screen readers. */
.btn--icon {
    width: 38px;
    padding-left: 0;
    padding-right: 0;
    gap: 0;
}
.btn--icon.btn--sm { width: 31px; }

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

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

.btn-group .btn + .btn { margin-left: -1px; }

/* ---------------------------------------------------------------------
   5. Cards and panels
   --------------------------------------------------------------------- */
.card {
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow-sm);
    /* A gentle round; overflow clips the header band, footer and flush tables
       to the same corner so nothing pokes past it. */
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 2px solid var(--red);
    background: var(--grad-head);
    /* Unstyled content inside the band inherits a light colour rather than
       the page's dark body text. */
    color: #E3E7EC;
}

.card__title {
    margin: 0;
    font-size: 0.96rem;
    font-weight: 600;
    color: var(--white);
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

/* Everything that sits on the dark header band is drawn light. */
.card__header a:not(.btn) { color: #E3E7EC; }
.card__header a:not(.btn):hover { color: var(--white); }
.card__header .btn--ghost { color: var(--white); }
.card__header .btn--ghost:hover { background: rgba(255, 255, 255, 0.14); color: var(--white); }
.card__header .muted { color: #D8DDE4; }
.card__header .badge--ok { color: #8FEAB4; }
.card__header .badge--bad { color: #FFB3B3; }
.card__header .badge--warn { color: #F6C368; }
.card__header .badge--neutral { color: #E3E7EC; }
.card__header .badge--brand { color: #DCD4F0; }

.card__body { padding: var(--sp-5); }
.card__body--flush { padding: 0; }

.card__footer {
    padding: var(--sp-3) var(--sp-5);
    border-top: 1px solid var(--grey-200);
    background: var(--grad-foot);
}

/* Statistic tile.
   The tile's icon stands on its own at the top-left - nothing drawn behind
   it - the label sits under it, and the figure below that is the thing the
   tile exists to show. The icon's colour is what marks the tile's state: a
   fee balance in arrears turns its icon red.

   Every one of these is built from the existing markup:

       <a class="stat stat--danger">
         <span class="stat__label"><svg class="icon">..</svg> Label</span>
         <span class="stat__value">49</span>
         <span class="stat__meta">caption</span>
       </a>

   No view had to change for this look. */
.stat {
    /* A column, so the caption can be pushed to the bottom and a row of
       tiles lines up along its baseline however long each caption runs. */
    display: flex;
    flex-direction: column;
    height: 100%;
    position: relative;
    padding: var(--sp-3) var(--sp-5);
    border-radius: var(--radius-lg);
    background: var(--white);
    border: 1px solid var(--grey-200);
    transition: border-color 0.16s ease;

    /* The figure is sized against the tile's own width, not the viewport,
       so a long amount shrinks to fit its tile instead of spilling over
       three lines the way "N$ 314,951.00" did. */
    container-type: inline-size;

    /* Icon colour, restated by each semantic variant below. */
    --stat-chip-fg: var(--brand);
}

.stat:hover { text-decoration: none; }

a.stat:hover {
    border-color: var(--brand-border);
}

/* Label and icon share one element: a column with the icon on top. The
   label text is an anonymous item beside the icon, so it simply inherits
   the type set here. */
.stat__label {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-3);
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--grey-500);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    line-height: 1.4;
}

/* The bare icon, sized here so it reads at the same weight whatever size
   the view asked for, with nothing drawn behind it. */
.stat__label .icon {
    width: 22px;
    height: 22px;
    color: var(--stat-chip-fg);
    flex-shrink: 0;
    transition: color 0.16s ease;
}

/* These are spans in the markup, so they need to be made block-level or the
   value and its caption run together on one line and the margins are lost. */
.stat__value {
    display: block;
    margin-top: var(--sp-2);
    font-family: var(--font-num);
    font-size: 2.1rem;
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums lining-nums;
    color: var(--grey-900);
    white-space: nowrap;      /* a figure never breaks across lines */
    margin-bottom: var(--sp-2);
}

/* An amount is several times longer than a count, so it is measured against
   the tile it has to fit in: roughly one tenth of the tile's width per step,
   held between a floor that stays readable and the ceiling above. */
.stat__value.money {
    font-size: 1.3rem;                          /* if cqi is not understood */
    font-size: clamp(1.05rem, 10cqi, 1.6rem);
    letter-spacing: -0.01em;
}

/* Some tiles report a word - "Cleared", "Pending" - rather than a figure.
   Words are set in the text face at a size that suits them. */
.stat__value--word {
    font-family: var(--font);
    font-size: 1.25rem;                         /* if cqi is not understood */
    font-size: clamp(1.05rem, 11cqi, 1.45rem);
    letter-spacing: 0;
}

/* Pinned to the foot of the tile, with a guaranteed gap above it. Pinning
   is what lines the rules up across a row when one tile holds a long
   amount and its neighbour holds a single digit. */
.stat__meta {
    display: block;
    margin-top: auto;
    padding-top: var(--sp-3);
    border-top: 1px solid var(--grey-100);
    font-size: 0.78rem;
    line-height: 1.45;
    color: var(--grey-500);
}

/* A status label in the caption is given the caption's own line box and no
   padding above or below, so a tile whose caption is a badge keeps its rule
   level with the plain-text tiles beside it. */
.stat__meta .badge {
    padding-block: 0;
    padding-inline: 0;
    font-size: 0.74rem;
    line-height: 1.45;
}

/* The denominator of a figure given as a fraction - "3 / 5" - sits quieter
   and smaller than the number it qualifies. */
.stat__value-of {
    font-size: 0.5em;
    font-weight: 500;
    color: var(--grey-500);
}

/* A semantic variant restates the icon colour, and the figure takes
   the same hue - so the tile reads as one piece, and a row of tiles can be
   scanned by colour alone without reading a word of it. */
.stat--success { --stat-chip-fg: var(--success); }
.stat--danger  { --stat-chip-fg: var(--danger); }
.stat--warning { --stat-chip-fg: var(--warning); }

.stat--success .stat__value { color: var(--success); }
.stat--danger .stat__value  { color: var(--danger); }
.stat--warning .stat__value { color: var(--warning); }

/* Theme picker.
   Each option carries its own data-theme, so the swatch is painted by the
   very tokens that option would apply - the preview cannot drift from the
   theme it is advertising. */
.theme-picker {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--sp-3);
    border: 0;
    padding: 0;
    margin: 0;
}

.theme-option {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3);
    border: 1px solid var(--grey-300);
    background: var(--white);
    cursor: pointer;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.theme-option:hover { border-color: var(--brand-border); }

.theme-option__swatch {
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    background: var(--side-bg);
    border: 1px solid rgba(0, 0, 0, 0.18);
    position: relative;
}

/* The stripe repeats the accent each theme uses for the current nav item. */
.theme-option__swatch::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 4px;
    background: var(--side-accent);
}

.theme-option__name {
    font-size: 0.88rem;
    font-weight: 500;
    color: var(--grey-700);
}

.theme-option:has(input:checked) {
    border-color: var(--brand);
    box-shadow: 0 0 0 2px var(--brand-light);
}

.theme-option:has(input:focus-visible) {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
}

.theme-option:has(input:checked) .theme-option__name {
    color: var(--brand);
    font-weight: 600;
}

/* Key/value description list */
.detail-list {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--sp-2) var(--sp-5);
    margin: 0;
}

.detail-list dt {
    font-size: 0.82rem;
    color: var(--grey-500);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.detail-list dd {
    margin: 0;
    font-weight: 500;
    color: var(--grey-900);
}

/* ---------------------------------------------------------------------
   6. Badges and alerts
   --------------------------------------------------------------------- */
.badge {
    display: inline-block;
    padding: 2px var(--sp-2);
    /* Status labels sit straight on the page: no box, no border, no fill. */
    background: transparent;
    border: 0;
    font-size: 0.74rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    line-height: 1.5;
    white-space: nowrap;
}

.badge--ok      { color: var(--success); }
.badge--bad     { color: var(--danger); }
.badge--warn    { color: var(--warning); }
.badge--neutral { color: var(--grey-700); }
.badge--brand  { color: var(--brand); }

/* A genuine circle: a small status dot. */
.dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-right: 6px;
    vertical-align: 1px;
}

.dot--ok   { background: var(--success); }
.dot--bad  { background: var(--danger); }
.dot--warn { background: var(--warning); }

.alert {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    /* Notifications sit straight on the page: no box, no border, no shadow. */
    background: transparent;
    border: 0;
    box-shadow: none;
    font-size: 0.92rem;
}

.alert__body { flex: 1; }
.alert p:last-child { margin-bottom: 0; }

.alert--success { color: #14532D; }
.alert--danger  { color: var(--danger); }
.alert--warning { color: #78350F; }
.alert--info    { color: #1E3A8A; }
.alert--brand  { color: var(--brand-dark); }

.alert__close {
    background: none;
    border: 0;
    color: inherit;
    cursor: pointer;
    opacity: 0.6;
    padding: 0;
    line-height: 1;
}

.alert__close:hover { opacity: 1; }

/* ---------------------------------------------------------------------
   Notifications

   These are the one-shot messages carried across a redirect, and they are
   deliberately a different thing from .alert above: an .alert is part of
   the page and stays, a notification announces what just happened and
   leaves after a few seconds.

   They float clear of the page rather than sitting in the flow, so a
   message appearing and then going does not shift the content underneath
   it, and the page beneath is never rearranged while it is being read.
   --------------------------------------------------------------------- */
.toasts {
    position: fixed;
    top: var(--sp-4);
    right: var(--sp-4);
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    width: min(380px, calc(100vw - var(--sp-8)));
    pointer-events: none;      /* the page stays usable around them */
}

.toast {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    background: #111418;
    color: #F2F4F7;
    border: 1px solid rgba(255, 255, 255, 0.12);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.32);
    font-size: 0.9rem;
    line-height: 1.45;
    pointer-events: auto;
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.28s ease, transform 0.28s ease;
}

/* The panel is black whatever the message, so the icon is left to carry
   which kind of message it is. */
.toast__icon {
    display: flex;
    flex-shrink: 0;
    margin-top: 1px;
}

.toast--success .toast__icon { color: #4ADE80; }
.toast--danger .toast__icon  { color: #F87171; }
.toast--warning .toast__icon { color: #FBBF24; }
.toast--info .toast__icon    { color: #7DD3FC; }

.toast__body { flex: 1; min-width: 0; }

.toast__close {
    background: none;
    border: 0;
    padding: 0;
    margin-top: 2px;
    color: inherit;
    opacity: 0.55;
    cursor: pointer;
    line-height: 1;
    flex-shrink: 0;
}

.toast__close:hover { opacity: 1; }

.toast__close:focus-visible {
    outline: 2px solid #F2F4F7;
    outline-offset: 2px;
}

/* The state the script sets just before it removes the element. */
.toast[data-leaving="true"] {
    opacity: 0;
    transform: translateY(-6px);
}

@media (max-width: 640px) {
    .toasts {
        top: var(--sp-3);
        right: var(--sp-3);
        left: var(--sp-3);
        width: auto;
    }
}

/* ---------------------------------------------------------------------
   7. Forms
   --------------------------------------------------------------------- */
.field { margin-bottom: var(--sp-4); }

.label {
    display: block;
    margin-bottom: var(--sp-2);
    font-size: 0.86rem;
    font-weight: 500;
    color: var(--grey-700);
}

.label .req { color: var(--danger); margin-left: 2px; }

.input,
.select,
.textarea {
    display: block;
    width: 100%;
    padding: 9px var(--sp-3);
    min-height: 38px;
    border-radius: var(--radius);
    border: 1px solid var(--grey-300);
    background: var(--white);
    color: var(--grey-900);
    font-family: inherit;
    font-size: 0.94rem;
    line-height: 1.4;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.input:hover,
.select:hover,
.textarea:hover { border-color: var(--grey-500); }

.input:focus,
.select:focus,
.textarea:focus {
    outline: none;
    border-color: var(--brand-2);
    box-shadow: 0 0 0 3px rgba(75, 52, 120, 0.12);
}

.input:disabled,
.select:disabled,
.textarea:disabled {
    background: var(--grey-100);
    color: var(--grey-500);
    cursor: not-allowed;
}

.input--error,
.select--error,
.textarea--error { border-color: var(--danger); }

.textarea { min-height: 110px; resize: vertical; }

.select {
    appearance: none;
    /* Sharp chevron drawn as a background image - no rounded control. */
    background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%234B5563' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--sp-3) center;
    padding-right: var(--sp-8);
}

.field__hint {
    margin-top: var(--sp-1);
    font-size: 0.8rem;
    color: var(--grey-500);
}

.field__error {
    margin-top: var(--sp-1);
    font-size: 0.8rem;
    color: var(--danger);
    display: flex;
    align-items: center;
    gap: 4px;
}

.checkbox,
.radio {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-2);
    font-size: 0.92rem;
    cursor: pointer;
    margin-bottom: var(--sp-2);
}

.checkbox input,
.radio input {
    margin: 3px 0 0;
    width: 16px;
    height: 16px;
    accent-color: var(--brand);
    flex-shrink: 0;
}

/* The radio control itself is a genuine circle - left alone. */
.radio input { border-radius: 50%; }

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

.input-group .input { border-right: 0; }
.input-group .btn { flex-shrink: 0; }

/* File upload drop zone */
.upload-zone {
    padding: var(--sp-6);
    border: 1px dashed var(--grey-300);
    background: var(--grey-50);
    text-align: center;
    cursor: pointer;
    transition: border-color 0.12s ease, background-color 0.12s ease;
}

.upload-zone:hover,
.upload-zone--active {
    border-color: var(--brand-2);
    background: var(--brand-light);
}

.upload-zone input[type="file"] { margin-top: var(--sp-3); }

fieldset {
    margin: 0 0 var(--sp-6);
    padding: 0;
    border: 0;
}

legend {
    padding: 0 0 var(--sp-3);
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--brand);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    border-bottom: 1px solid var(--grey-200);
    width: 100%;
    margin-bottom: var(--sp-4);
}

/* Filter bar above a table */
.filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-3);
    padding: var(--sp-4);
    background: var(--white);
    border: 1px solid var(--grey-200);
    margin-bottom: var(--sp-4);
}

.filters .field {
    margin-bottom: 0;
    min-width: 165px;
    flex: 1 1 165px;
}

.filters .field--actions {
    flex: 0 0 auto;
    display: flex;
    gap: var(--sp-2);
}

/* ---------------------------------------------------------------------
   8. Tables
   --------------------------------------------------------------------- */
.table-wrap {
    width: 100%;
    overflow-x: auto;
    border-radius: var(--radius-lg);
    border: 1px solid var(--grey-200);
    background: var(--white);

    /* On a narrow screen a wide table has to scroll. The four layers below
       are the usual shadow trick: two solid white covers pinned to the table
       itself and two shadows pinned to the viewport behind them, so a shadow
       shows only at an edge there is more table beyond. A reader can then see
       that the row continues rather than assuming it was cut off. */
    background-image:
        linear-gradient(to right, var(--white), rgba(255, 255, 255, 0)),
        linear-gradient(to left, var(--white), rgba(255, 255, 255, 0)),
        linear-gradient(to right, rgba(26, 15, 46, 0.18), rgba(26, 15, 46, 0)),
        linear-gradient(to left, rgba(26, 15, 46, 0.18), rgba(26, 15, 46, 0));
    background-position: left center, right center, left center, right center;
    background-repeat: no-repeat;
    background-size: 28px 100%, 28px 100%, 12px 100%, 12px 100%;
    background-attachment: local, local, scroll, scroll;
}

.card .table-wrap { border: 0; }

/* Safety net. The content column is clipped (.main { overflow-x: clip }) so no
   page ever scrolls sideways, which means a table placed straight into a page
   WITHOUT a .table-wrap would be cut off on the right instead of scrolling.
   This makes any such stray table scroll on its own, so every table fits on
   every screen even if a wrap was forgotten. Tables inside a .table-wrap are
   excluded and keep their normal full-width layout. */
.content .table:not(.table-wrap .table) {
    display: block;
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9rem;
}

.table th,
.table td {
    padding: 10px var(--sp-4);
    text-align: left;
    border-bottom: 1px solid var(--grey-200);
    vertical-align: middle;
}

.table thead th {
    background: var(--grad-thead);
    font-size: 0.76rem;
    font-weight: 600;
    color: #E9ECF1;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    /* A fine red rule closes the band: the institution's second colour, used
       as a finishing line rather than as meaning. */
    border-bottom: 2px solid var(--red);
}

/* A card header and the column heads below it are one continuous band, so
   the red rule belongs at the foot of the pair, not between them. Where a
   table follows a header directly, the header gives up the accent and the
   column heads keep it. */
.card__header:has(+ .card__body--flush .table thead),
.card__header:has(+ .card__body .table thead),
.card__header:has(+ .table-wrap .table thead) {
    border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}

/* Secondary text inside a heading - a weighting, a unit, a hint - is drawn in
   a muted grey meant for a white row, which disappears against the dark band.
   It is lightened to sit on the band instead. */
.table thead th .muted,
.table thead th .small,
.table thead th .tiny { color: #C6CCD5; }

/* A column heading is the widest thing in a narrow column surprisingly often,
   and a heading that cannot wrap sets a floor under the whole table's width.
   Headings wrap onto a second line so the table keeps fitting; the ones that
   read badly broken are held together individually with .nowrap. */
.table thead th { white-space: normal; }
.table thead th.nowrap { white-space: nowrap; }

/* Results (CA) grades table: lighter column-head band — a light lavender
   surface with dark text instead of the deep purple band, by request. */
.table.table--ca > thead th {
    background: var(--brand-light);          /* #F0EDF9 light lavender */
    color: var(--brand-dark);                /* #2E2457 dark purple text */
    border-bottom: 2px solid var(--brand-border);
}
.table.table--ca > thead th .muted,
.table.table--ca > thead th .small,
.table.table--ca > thead th .tiny { color: var(--grey-500); }

.table tbody tr:hover { background: var(--grey-50); }
.table tbody tr:last-child td { border-bottom: 0; }

.table--compact th,
.table--compact td { padding: 6px var(--sp-3); }

/* Marks tables: light, zebra-striped reading style after the CA-marks grid -
   a quiet header, thin row rules, alternating stripes and compact cells. */
.table--marks {
    font-size: 0.88rem;
}

.table--marks thead th {
    /* The light lettering needs the dark band behind it; left transparent it
       was white-on-white and the column headings could not be read. */
    background: var(--grad-thead);
    color: #E9ECF1;
    font-weight: 600;
    font-size: 0.8rem;
    text-transform: none;
    letter-spacing: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.18);
}

.table--marks th,
.table--marks td {
    padding: 7px var(--sp-3);
    border-bottom: 1px solid var(--grey-200);
}

/* Quiet neutral stripes and hover, matching the CA-marks reference. */
/* Column floors for the mark grid. They keep a name readable and the mark
   boxes aligned, and give way on a smaller screen so the grid stays on the
   page; with enough assessments it must still scroll, which is what a
   spreadsheet of this shape should do. */
.table--marks th.col-student { min-width: 150px; }

/* Numbered test columns. They are deliberately narrow: the heading is only
   T1, T2, T3 ... and what each one stands for is listed in the Test Weights
   panel above the grid, so a whole class fits across the screen. */
.table--marks th.col-slot {
    width: 72px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.table--marks th.col-total { width: 78px; }

/* The name column takes every spare pixel, which keeps the numbered columns
   gathered together on the right instead of drifting apart across the width
   of the screen. */
.table--marks th.col-student { width: 99%; }

/* A mark sits centred under its own column heading. */
.table--marks .mark-input {
    display: block;
    margin-inline: auto;
    text-align: center;
}

/* ---------------------------------------------------------------------
   The student-facing CA grid.

   The same shape as the lecturer's capture grid, read-only: one row per
   module, each assessment in its own numbered column, and the weight behind
   each number a click away rather than on the face of the table.
   --------------------------------------------------------------------- */
.table--ca {
    font-size: 0.88rem;
}
.table--ca th.col-code,
.table--ca td.col-code {
    width: 1%;
    white-space: nowrap;
}

/* The numbered columns are narrow and centred, as on a mark sheet: what T3
   actually was is in the Test Weights panel, not in the heading. */
.table--ca th.col-slot { width: 58px; }

.table--ca th.col-slot,
.table--ca td.col-slot {
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* The band naming the programme and year the rows below belong to. */
.ca-group td {
    background: var(--grey-100);
    border-bottom: 1px solid var(--grey-200);
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--grey-700);
    letter-spacing: 0.02em;
}

.table--ca tbody tr:not(.ca-group):not(.ca-detail):hover { background: var(--grey-50); }

.ca-link {
    font-size: 0.82rem;
    white-space: nowrap;
}

/* The weights panel. Closed by default and opened by the link in its row
   through :target, so the table needs no script to expand a module - and
   opening one closes whichever was open before. */
.ca-detail { display: none; }
.ca-detail:target { display: table-row; }

.ca-detail > td {
    background: var(--brand-light);
    border-bottom: 2px solid var(--brand-border);
    padding: var(--sp-3) var(--sp-4);
}

.ca-detail__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-bottom: var(--sp-2);
    font-size: 0.85rem;
}

.ca-detail__table {
    max-width: 620px;
    background: var(--white);
    border: 1px solid var(--grey-200);
}

@media (max-width: 1150px) {
    .table--marks th.col-student { min-width: 120px; }
    .table--marks th.col-slot { min-width: 62px; }
    .table--marks th,
    .table--marks td { padding: 7px var(--sp-2); }
    .mark-input { width: 62px; }

    /* A long surname on one line was setting the width of the whole grid.
       It wraps here rather than pushing the mark boxes off the screen. */
    .table--marks td.nowrap { white-space: normal; }
}

.table--marks tbody tr:nth-child(even) { background: #FAFAFB; }
.table--marks tbody tr:hover { background: #F1F3F5; }
.table--marks tbody tr:last-child td { border-bottom: 1px solid var(--grey-200); }

.table--marks tfoot td {
    background: var(--grey-100);
    color: var(--grey-700);
    border-top: 1px solid var(--grey-300);
}

/* Links inside a marks table read as the classic report blue. */
.table--marks a {
    color: #1F5AA8;
}
.table--marks a:hover { color: #15417A; text-decoration: underline; }

/* A programme / year section heading row spanning the whole marks table. */
.table--marks tbody tr.table__group td,
.table--marks .table__group td {
    padding-top: 15px;
    padding-bottom: 6px;
    background: var(--white);
    color: #1F5AA8;
    font-weight: 600;
    font-size: 0.86rem;
    border-bottom: 1px solid var(--grey-300);
}

.table--marks tbody tr.table__group:hover td { background: var(--white); }

.table td.numeric,
.table th.numeric {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.table tfoot td {
    background: linear-gradient(180deg, #F7F4FC 0%, var(--brand-light) 100%);
    font-weight: 600;
    color: var(--brand-dark);
    border-top: 1px solid var(--brand-border);
}

.table .actions {
    display: flex;
    gap: var(--sp-2);
    justify-content: flex-end;
}

/* Collapsible legend naming the numbered test columns. Closed by default so
   it costs nothing to the lecturer who already knows the scheme. */
.weights {
    border: 1px solid var(--grey-200);
    background: var(--white);
}

.weights__summary {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--brand);
    cursor: pointer;
    user-select: none;
}

.weights__summary:hover { background: var(--grey-50); }
.weights__summary:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.weights[open] .weights__summary { border-bottom: 1px solid var(--grey-200); }
.weights .table-wrap { border: 0; }

.table-empty {
    padding: var(--sp-10) var(--sp-5);
    text-align: center;
    color: var(--grey-500);
}

.table-empty .icon { color: var(--grey-300); margin-bottom: var(--sp-3); }

/* Mark entry grid */
.mark-input {
    width: 78px;
    padding: 5px var(--sp-2);
    min-height: 30px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------
   9. Pagination
   --------------------------------------------------------------------- */
.pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--grey-200);
    background: var(--grey-50);
    font-size: 0.86rem;
}

.pagination__pages {
    display: flex;
    gap: 0;
}

.pagination__link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 34px;
    height: 32px;
    padding: 0 var(--sp-2);
    border: 1px solid var(--grey-300);
    margin-left: -1px;
    background: var(--white);
    color: var(--grey-700);
    font-size: 0.85rem;
}

.pagination__link:hover {
    background: var(--brand-light);
    color: var(--brand);
    text-decoration: none;
}

.pagination__link--active {
    background: var(--grad-brand);
    border-color: var(--brand);
    color: var(--white);
    font-weight: 600;
}

.pagination__link--disabled {
    color: var(--grey-300);
    pointer-events: none;
}

/* ---------------------------------------------------------------------
   10. Application shell
   --------------------------------------------------------------------- */
.shell {
    display: flex;
    flex-direction: row;
    min-height: 100vh;
}

/* The navigation runs down the left of the page. */
.sidebar {
    width: var(--sidebar-w);
    flex-shrink: 0;
    background: var(--side-bg);
    box-shadow: 1px 0 0 rgba(255, 255, 255, 0.06) inset, 3px 0 14px var(--side-shadow);
    color: var(--side-ink);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    position: sticky;
    top: 0;
    left: 0;
    height: 100vh;
    z-index: 40;
    overflow: hidden;
}

/* The molecular lattice across the navigation field, fainter than on the
   masthead: at this width it should be felt rather than seen, and it fades
   out down the column so the nav items lower down stay on clean ground. */
/* The nursing library at the foot of the navigation.

   It sits at the bottom because that is the one part of the column with no
   nav items to compete with, and it dissolves upward into the gradient so
   there is no edge where the photograph begins. Blended on luminosity, it
   keeps only its light and shade and takes its colour from the gradient
   underneath - so the sidebar is still the institution's indigo, and still
   whatever colour a different theme sets, rather than a picture pasted onto
   it. Held well back, because a navigation has to be read, not admired. */
/* Selector carries the parent so it outranks `.sidebar > *` below, which
   makes every child relative and in flow - this one must stay out of the
   flex column and behind it. */
.sidebar > .sidebar__art {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 0;
    /* A band at the foot, not a wall: on a tall display the column would
       otherwise carry the photograph half way up the navigation. */
    height: 46%;
    min-height: 230px;
    max-height: 400px;
    background: url("../img/sidebar-books.jpg") center bottom / cover no-repeat;
    opacity: 0.12;
    mix-blend-mode: luminosity;
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 62%);
    mask-image: linear-gradient(180deg, transparent 0%, #000 62%);
    pointer-events: none;
}

.sidebar > * { position: relative; z-index: 1; }

.sidebar__brand {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-4);
    border-bottom: 1px solid var(--side-divider);
    flex-shrink: 0;
}

/* The institution logo is genuinely circular - the one permitted circle. */
.sidebar__logo {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: var(--white);
    padding: 2px;
    flex-shrink: 0;
    object-fit: contain;
}

.sidebar__name {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--side-ink);
    line-height: 1.25;
}

.sidebar__sub {
    font-size: 0.68rem;
    color: var(--side-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.sidebar__nav {
    flex: 1;
    min-height: 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    padding: var(--sp-3) 0;
    overflow-x: hidden;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.35) transparent;
}

.sidebar__nav::-webkit-scrollbar { width: 6px; }
.sidebar__nav::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.28); }
.sidebar__nav::-webkit-scrollbar-track { background: transparent; }

/* A group heading is a label, not a destination. Setting it back from the
   items in both weight and colour is what lets the eye find the groups
   first and then the item inside one - previously every line in the
   navigation was the same full white and the structure was invisible. */
.nav-section {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    white-space: nowrap;
    padding: var(--sp-5) var(--sp-4) var(--sp-2);
    font-size: 0.64rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.13em;
    color: var(--side-muted);
}

.nav-section:first-child { padding-top: var(--sp-2); }

.nav-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-shrink: 0;
    white-space: nowrap;
    padding: 9px var(--sp-4) 9px calc(var(--sp-4) - 3px);
    color: var(--side-ink);
    font-size: 0.88rem;
    border-left: 3px solid transparent;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

/* The icon sits back until the row is hovered or current, so a column of
   icons reads as one quiet rail rather than competing with the labels. */
.nav-item .icon {
    color: var(--side-icon);
    flex-shrink: 0;
    transition: color 0.12s ease;
}

.nav-item:hover {
    background: var(--side-hover);
    color: var(--side-ink);
    text-decoration: none;
}

.nav-item:hover .icon { color: var(--side-ink); }

.nav-item:focus-visible {
    outline: 2px solid var(--side-accent);
    outline-offset: -2px;
}

.nav-item--active {
    background: var(--side-active);
    border-left-color: var(--side-accent);
    color: var(--side-ink);
    font-weight: 600;
}

/* The current row is marked by its rail and its background, not by tinting
   the icon: the icon stays white like every other one. */
.nav-item--active .icon { color: var(--side-ink); }

.sidebar__footer {
    padding: var(--sp-4);
    border-top: 1px solid var(--side-divider);
    font-size: 0.7rem;
    line-height: 1.6;
    color: var(--side-muted);
    flex-shrink: 0;
}

.sidebar__footer strong,
.sidebar__role {
    display: block;
    color: var(--side-ink);
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 0.66rem;
}

.main {
    flex: 1;
    min-width: 0;
    margin-left: 0;
    display: flex;
    flex-direction: column;
    /* Final guard: nothing in a portal page may push the layout wider than the
       space beside the sidebar. `clip` contains stray width without turning the
       column into a scroll container, so the sticky top bar still works and any
       genuinely wide table keeps its own horizontal scroll (.table-wrap). */
    overflow-x: clip;
}

.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    height: var(--topbar-h);
    padding: 0 var(--sp-6);
    background: var(--white);
    border-bottom: 1px solid var(--grey-200);
    position: sticky;
    top: 0;
    z-index: 30;
}

.topbar__left {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    min-width: 0;
}

.topbar__title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: var(--grey-900);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.topbar__right {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}

/* Quick search in the top bar: an icon button that expands an input inline. */
.topbar__search {
    position: relative;
    display: flex;
    align-items: center;
}
/* The search icon sits inside the field, on the left, over the input padding. */
.topbar__search-btn {
    position: absolute;
    left: 4px;
    top: 0;
    bottom: 0;
    width: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    color: var(--grey-500);
    cursor: pointer;
    flex: 0 0 auto;
    z-index: 1;
}
.topbar__search-btn:hover { color: var(--brand); }
/* Always visible - no click needed to expand. Left padding clears the icon. */
.topbar__search-input {
    width: clamp(150px, 38vw, 230px);
    min-width: 0;
    height: 36px;
    padding: 0 16px 0 38px;
    border: 1px solid var(--grey-200);
    background: var(--grey-100);
    border-radius: 999px;
    font-family: inherit;
    font-size: 0.88rem;
    color: var(--grey-900);
    transition: border-color .12s ease, box-shadow .12s ease;
}
.topbar__search-input:focus {
    outline: none;
    border-color: var(--brand-2);
    box-shadow: 0 0 0 3px rgba(75, 52, 120, 0.12);
}

.topbar__campus {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px var(--sp-3);
    background: transparent;
    border: 0;
    color: var(--brand);
    font-size: 0.78rem;
    font-weight: 500;
}

.menu-toggle {
    display: none;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border: 1px solid var(--grey-300);
    background: var(--white);
    color: var(--grey-700);
    cursor: pointer;
}

.menu-toggle:hover { border-color: var(--brand-2); color: var(--brand); }

/* User menu */
.user-menu { position: relative; }

.user-menu__trigger {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 4px var(--sp-2) 4px 4px;
    border: 1px solid var(--grey-200);
    background: var(--white);
    color: var(--grey-700);
    font-family: inherit;
    font-size: 0.85rem;
    cursor: pointer;
}

.user-menu__trigger:hover { border-color: var(--brand-2); }

/* Initials chip - always a circle, like every other profile mark. */
.avatar {
    display: inline-flex;
    border-radius: 50%;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    background: var(--grad-brand);
    color: var(--white);
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    flex-shrink: 0;
}

.avatar--lg { width: 52px; height: 52px; font-size: 1.1rem; }

.user-menu__panel {
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    min-width: 230px;
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow-lg);
    z-index: 50;
    display: none;
}

.user-menu__panel[data-open="true"] { display: block; }

.user-menu__head {
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--grey-200);
    background: var(--grey-50);
}

.user-menu__link {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: 9px var(--sp-4);
    color: var(--grey-700);
    font-size: 0.88rem;
}

.user-menu__link:hover {
    background: var(--brand-light);
    color: var(--brand);
    text-decoration: none;
}

.content {
    flex: 1;
    min-width: 0;
    padding: var(--sp-6);
    /* Long unbroken strings (emails, tokens, URLs) break instead of forcing
       the page wider. */
    overflow-wrap: break-word;
}

/* Page header.
   Title, the line of context under it, and the actions for the page. The
   rule underneath closes the header off as a band, so the headline figures
   that follow read as a separate thing rather than floating under the
   title. The actions keep to the right until there is no longer room, then
   drop onto their own line instead of squeezing the title. */
.page-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-4);
    margin-bottom: var(--sp-5);
    padding-bottom: var(--sp-4);
    border-bottom: 1px solid var(--grey-200);
    position: relative;
}

/* A short accent length riding on the rule, picking up the brand. */
.page-head::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -1px;
    width: 56px;
    height: 2px;
    background: var(--grad-brand);
}

.page-head > div:first-child { min-width: 0; }

.page-head__title {
    margin: 0 0 var(--sp-2);
    letter-spacing: -0.015em;
}

/* The context line carries a few facts separated by middots. It is left as
   ordinary text so those separators keep their own spacing, and simply
   wraps onto a second line when the window is narrow. */
.page-head__sub {
    margin: 0;
    color: var(--grey-500);
    font-size: 0.86rem;
    line-height: 1.5;
}

.page-head__actions {
    display: flex;
    gap: var(--sp-2);
    flex-wrap: wrap;
    flex-shrink: 0;
}

.breadcrumb {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
    font-size: 0.8rem;
    color: var(--grey-500);
}

.breadcrumb a { color: var(--grey-500); }
.breadcrumb a:hover { color: var(--brand); }

.footer-note {
    padding: var(--sp-4) var(--sp-6);
    border-top: 1px solid var(--grey-200);
    font-size: 0.78rem;
    color: var(--grey-500);
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-2);
}

/* Sidebar backdrop on small screens */
.backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(31, 41, 55, 0.45);
    z-index: 35;
}

.backdrop[data-open="true"] { display: block; }

/* ---------------------------------------------------------------------
   11. Tabs
   --------------------------------------------------------------------- */
.tabs {
    display: flex;
    gap: 0;
    border-bottom: 1px solid var(--grey-200);
    margin-bottom: var(--sp-5);
    overflow-x: auto;
}

.tab {
    padding: 10px var(--sp-4);
    border-bottom: 2px solid transparent;
    color: var(--grey-500);
    font-size: 0.9rem;
    font-weight: 500;
    white-space: nowrap;
}

.tab:hover { color: var(--brand); text-decoration: none; }

.tab--active {
    border-bottom-color: var(--brand);
    color: var(--brand);
}

/* ---------------------------------------------------------------------
   12. Charts (hand-drawn SVG - circles here are genuine data shapes)
   --------------------------------------------------------------------- */
/* Vertical column chart (dashboards). Named distinctly from the horizontal
   .chart-bars list used on the analytics page, which otherwise overrode this
   and stacked the columns, overflowing the fixed height and clipping labels. */
.col-chart {
    display: flex;
    align-items: flex-end;
    /* Group the columns together and centre them, rather than letting a few
       bars spread thinly across the whole card. */
    justify-content: center;
    gap: clamp(10px, 2.5vw, 28px);
    height: 190px;
    padding-top: var(--sp-4);
}

.chart-bars__col {
    /* Even, fixed-width columns so the bars read as one tidy group. */
    flex: 0 1 72px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    align-items: center;
    gap: var(--sp-2);
    height: 100%;
    min-width: 0;
}

.chart-bars__bar {
    width: 100%;
    max-width: 56px;
    background: linear-gradient(180deg, var(--brand-accent) 0%, var(--brand) 100%);
    min-height: 2px;
    border-radius: 6px 6px 0 0;
    transition: background-color 0.12s ease;
}

.chart-bars__col:hover .chart-bars__bar { background: var(--brand-2); }

.chart-bars__value {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--grey-900);
}

.chart-bars__label {
    font-size: 0.74rem;
    color: var(--grey-500);
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

.chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    margin-top: var(--sp-4);
    font-size: 0.82rem;
}

.chart-legend__item { display: flex; align-items: center; gap: 6px; }

/* Square legend swatch, keeping the rectangular language. */
.chart-legend__swatch { width: 11px; height: 11px; flex-shrink: 0; }

/* Linear progress meter */
.meter {
    height: 8px;
    background: var(--grey-200);
    overflow: hidden;
}

.meter__fill {
    height: 100%;
    background: var(--grad-brand);
    transition: width 0.3s ease;
}

.meter__fill--success { background: var(--grad-success); }
.meter__fill--danger  { background: var(--grad-danger); }
.meter__fill--warning { background: var(--grad-warning); }

/* ---------------------------------------------------------------------
   13. Public landing page
   --------------------------------------------------------------------- */
.public-body { background: var(--white); }

/* Dark institutional bar that flows into the hero below it. */
.public-nav {
    position: sticky;
    top: 0;
    z-index: 50;
    background: linear-gradient(180deg, #FBFBFC 0%, #EFF1F4 58%, #E2E5EA 100%);
    border-bottom: 1px solid var(--grey-300);
    box-shadow: 0 1px 3px rgba(31, 41, 55, 0.06);
}

.public-nav__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    height: 68px;
}

.public-brand {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    color: var(--grey-900);
}

.public-brand:hover { text-decoration: none; }

/* The institution mark is genuinely circular. */
.public-brand__logo {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--white);
    box-shadow: 0 0 0 1px var(--grey-200);
    padding: 2px;
    object-fit: contain;
    flex-shrink: 0;
}

.public-brand__name {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--brand);
    line-height: 1.15;
    letter-spacing: 0.01em;
}

.public-brand__tag {
    font-size: 0.66rem;
    color: var(--grey-500);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-weight: 500;
}

.public-links {
    display: flex;
    align-items: center;
    gap: var(--sp-6);
}

.public-links a {
    color: var(--grey-700);
    font-size: 0.92rem;
    font-weight: 500;
    padding: var(--sp-2) 0;
    border-bottom: 2px solid transparent;
    white-space: nowrap;
    flex-shrink: 0;         /* stop the buttons being squeezed on narrow bars */
}

.public-links a:hover,
.public-links a.active {
    color: var(--brand);
    border-bottom-color: var(--brand);
    text-decoration: none;
}

.public-links .btn,
.public-links .btn:hover { border-bottom: 0; }

/* Highlighted call to action with its "new" flag, as in the reference design.
   Scoped under .public-links so it outranks the plain link rule above. */
.public-links a.nav-cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 16px;
    border-radius: 0;
    background: linear-gradient(135deg, var(--brand-2), #6A5ABA);
    color: var(--white) !important;
    font-size: 0.92rem;
    font-weight: 400;
    border-bottom: 0 !important;
}

.public-links a.nav-cta:hover {
    background: linear-gradient(135deg, #4B3E8E, #8B7BD4);
    text-decoration: none;
}

/* Red is otherwise reserved for unpaid and rejected statuses inside the
   portal, so this uses a warmer red than the --danger token to keep the two
   readings apart. */
.public-links a.nav-signin {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 18px;
    border-radius: 0;
    background: var(--grad-red);
    border: 1px solid var(--red-dark);
    box-shadow: var(--shadow-sm);
    color: var(--white) !important;
    font-size: 0.92rem;
    font-weight: 400;
    border-bottom: 0 !important;
}

.public-links a.nav-signin:hover {
    background: linear-gradient(135deg, #D8463A 0%, #B52A1E 100%);
    border-color: #8E1C12;
    color: var(--white) !important;
    text-decoration: none;
}

.nav-toggle {
    display: none;
    width: 40px;
    height: 40px;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--grey-300);
    background: var(--white);
    cursor: pointer;
    color: var(--grey-700);
}

/* ---------------------------------------------------------------------
   13a. Landing hero
   The home page masthead: dot-pattern field, serif headline, and the
   institution mark watermarked left and a photograph bled in on the right.
   --------------------------------------------------------------------- */
.landing {
    position: relative;
    overflow: hidden;
    color: var(--white);
    padding: 84px 0 92px;

    /* Fill the screen below the sticky bar so the masthead always covers the
       fold, however tall the display. svh keeps mobile browser chrome from
       pushing the section taller than the visible area. */
    min-height: calc(100vh - 68px);
    min-height: calc(100svh - 68px);
    display: flex;
    align-items: center;

    /* Three layers: two soft colour pools that give the field depth, over a
       diagonal base ramp. Radial stops are placed off-centre so the light
       appears to come from behind the institution mark. */
    background:
        radial-gradient(900px 520px at 78% 18%, rgba(126, 91, 190, 0.42) 0%, transparent 62%),
        radial-gradient(700px 480px at 12% 88%, rgba(43, 27, 74, 0.70) 0%, transparent 60%),
        linear-gradient(152deg, #4B3E8E 0%, #473A82 46%, #2E2457 100%);
}

/* The molecular lattice behind the masthead, matching the sign-in panel. Held at a
   low opacity so it reads as texture under the headline, never as decoration
   competing with it. */
.landing::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image: none;
    background-size: 300px 300px;
    opacity: 0;
    /* A smooth left-to-right gradient: the texture is full at the left edge
       and dissolves gradually across the field, carrying on past the midline
       so it blends into the photograph rather than stopping short of it. */
    -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 12%, transparent 62%);
    mask-image: linear-gradient(90deg, #000 0%, #000 12%, transparent 62%);
    pointer-events: none;
}

/* Softens the pattern behind the headline, then fades the whole field into
   the section that follows so there is no hard horizontal seam. */
.landing::after {
    content: "";
    position: absolute;
    inset: 0;
    background:
        linear-gradient(100deg, rgba(42, 26, 72, 0.70) 0%, rgba(42, 26, 72, 0.22) 46%, transparent 72%),
        linear-gradient(180deg, transparent 82%, rgba(26, 15, 46, 0.45) 100%);
    pointer-events: none;
}

/* The copy sits above both the watermark and the photograph, and is held to
   the left half so the text never runs over the faces in the picture. */
.landing__inner {
    position: relative;
    z-index: 1;
    width: 100%;
    /* Not centred or capped: the picture is anchored to the right edge of the
       screen, so the copy is anchored to the left edge and sized in vw to
       always stop short of it, at any width. */
    max-width: none;
    padding-inline: clamp(var(--sp-4), 5vw, 96px);
}

.landing__copy {
    /* The picture starts at 44% of the width, so the copy stops short of it. */
    max-width: 34vw;
    min-width: 0;
}

.landing__title {
    /* Bold condensed sans, matching the lettering on the institution logo. */
    font-family: "Archivo Narrow", "Arial Narrow", "Franklin Gothic Medium", Arial, sans-serif;
    overflow-wrap: break-word;
    /* Display size for the PMT wordmark, held in check so it never crowds
       the copy beneath it or breaks out of the column at wide widths. */
    font-size: clamp(3.5rem, 9vw, 10rem);
    max-width: 100%;
    font-weight: 700;
    /* Archivo Narrow stops at 700, so a thin outline in the text colour thickens the strokes. */
    -webkit-text-stroke: 0.03em currentColor;
    paint-order: stroke fill;
    line-height: 1.04;
    letter-spacing: -0.015em;
    color: var(--white);
    margin: 0 0 var(--sp-3);
}

.landing__title-mark { display: block; }

/* One small line under the wordmark, left to right, never wrapping. */
.landing__title-sub {
    display: block;
    font-family: var(--font);
    font-size: clamp(1rem, 1.5vw, 1.35rem);
    font-weight: 400;
    -webkit-text-stroke: 0;
    letter-spacing: 0.05em;
    line-height: 1.35;
    color: #E5E0F3;
    white-space: nowrap;
    margin-top: var(--sp-2);
}

.landing__subtitle {
    font-family: "Source Serif 4", Georgia, "Times New Roman", serif;
    font-style: italic;
    font-size: 1.6rem;
    font-weight: 400;
    line-height: 1.25;
    color: #C8BCE9;
    margin: 0 0 var(--sp-5);
}

.landing__lead {
    font-size: clamp(0.9rem, 1.15vw, 1.3rem);
    line-height: 1.65;
    color: #DAD2EE;
    max-width: 100%;
    margin-bottom: var(--sp-6);
}

.landing__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-bottom: var(--sp-6);
}

/* Primary action: white on indigo is the strongest contrast the
   institutional palette allows, and keeps red reserved for "unpaid". */
.landing__cta {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 9px 26px;
    border: 1px solid var(--white);
    border-radius: 7px;
    background: linear-gradient(180deg, var(--white) 0%, #F3F0FA 100%);
    color: var(--brand);
    font-size: 1rem;
    font-weight: 500;
    box-shadow: 0 6px 20px rgba(16, 8, 34, 0.26);
    transition: transform 0.14s ease, box-shadow 0.14s ease, background-color 0.14s ease;
}

.landing__cta:hover {
    background: linear-gradient(180deg, var(--white) 0%, var(--brand-light) 100%);
    border-color: var(--white);
    color: var(--brand-dark);
    text-decoration: none;
    transform: translateY(-1px);
    box-shadow: 0 10px 26px rgba(16, 8, 34, 0.32);
}

.landing__cta--ghost {
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.14) 0%, rgba(255, 255, 255, 0.04) 100%);
    border-color: rgba(255, 255, 255, 0.42);
    color: var(--white);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.landing__cta--ghost:hover {
    background: linear-gradient(135deg, rgba(255, 255, 255, 0.24) 0%, rgba(255, 255, 255, 0.10) 100%);
    border-color: var(--white);
    color: var(--white);
}

/* --- Staff portals, under the main actions ------------------------ */
/* A quieter second rank than the two calls to action above: the student
   sign-in stays the one obvious thing to do on the page, and the staff
   areas are available without competing with it. */
.landing__portals {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-6);
}

.landing__portals-label {
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: rgba(255, 255, 255, 0.62);
    margin-right: var(--sp-1);
}

.portal-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 6px 14px;
    border: 1px solid rgba(255, 255, 255, 0.34);
    border-radius: 7px;
    background: rgba(255, 255, 255, 0.07);
    color: var(--white);
    font-size: 0.84rem;
    font-weight: 500;
    transition: background-color 0.14s ease, border-color 0.14s ease;
}

.portal-btn .icon { opacity: 0.8; }

.portal-btn:hover {
    background: rgba(255, 255, 255, 0.17);
    border-color: var(--white);
    color: var(--white);
    text-decoration: none;
}

.portal-btn:hover .icon { opacity: 1; }

.portal-btn:focus-visible {
    outline: 2px solid var(--white);
    outline-offset: 2px;
}

/* --- Faded institution mark, far left ----------------------------- */
/* Purely decorative: the mark already appears in the navigation bar, so this
   is a watermark and is hidden from assistive technology in the markup. */
.landing__watermark {
    position: absolute;
    top: 50%;
    left: -110px;
    transform: translateY(-50%);
    width: 560px;
    height: 560px;
    border-radius: 50%;
    object-fit: contain;
    opacity: 0.08;
    pointer-events: none;
    z-index: 0;
}

/* --- Photograph bled into the indigo field, right ------------------ */
.landing__photo {
    /* One fade shared by the photograph and its tint overlay: the picture
       bleeds to the right and bottom edges and dissolves on its left, where it
       meets the copy, and across its top. The two ramps are intersected so the
       top-left corner falls away into the indigo field. */
    --landing-photo-fade: none;
    /* A straight vertical line divides the copy from the picture. */
    border-left: 2px solid rgba(255, 255, 255, 0.35);
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 56%;
    overflow: hidden;
    pointer-events: none;
    z-index: 0;
}

.landing__photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 58% 42%;
    /* Settle the photograph into the indigo field before the overlay
       tints it, so the photograph sits in the palette rather than beside it. */
    filter: saturate(0.72) contrast(1.06) brightness(0.92);
    /* Dissolve the picture into the ring field on its left and top, so neither
       edge reads as a hard cut against the copy or the bar above. */
    -webkit-mask-image: var(--landing-photo-fade);
    mask-image: var(--landing-photo-fade);
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
}

/* No tint overlay: the photograph keeps its own colours and simply dissolves
   into the indigo field through the mask on its left edge. */
/* Hero */
.hero {
    background: var(--brand);
    color: var(--white);
    padding: var(--sp-12) 0;
    position: relative;
    overflow: hidden;
}

.hero > * { position: relative; z-index: 1; }

.hero__grid {
    display: grid;
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
    gap: var(--sp-10);
    align-items: center;
}

.hero h1 {
    color: var(--white);
    font-size: 2.3rem;
    line-height: 1.2;
    margin-bottom: var(--sp-4);
}

.hero__lead {
    font-size: 1.05rem;
    color: #DCD4F0;
    max-width: 48ch;
    margin-bottom: var(--sp-6);
}

.hero__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

/* The default button is indigo, which would vanish on an indigo hero;
   invert it to white so it still reads as the primary action. */
.hero .btn:not(.btn--secondary) {
    background: var(--white);
    border-color: var(--white);
    color: var(--brand);
}

.hero .btn:not(.btn--secondary):hover {
    background: var(--brand-light);
    border-color: var(--brand-light);
    color: var(--brand-dark);
}

.hero .btn--secondary {
    background: transparent;
    border-color: rgba(255, 255, 255, 0.5);
    color: var(--white);
}

.hero .btn--secondary:hover {
    background: rgba(255, 255, 255, 0.1);
    border-color: var(--white);
    color: var(--white);
}

.hero__panel {
    background: rgba(255, 255, 255, 0.07);
    border: 1px solid rgba(255, 255, 255, 0.2);
    padding: var(--sp-6);
}

.hero__logo {
    display: block;
    width: 150px;
    height: 150px;
    margin: 0 auto var(--sp-5);
    border-radius: 50%;     /* the circular institution mark */
    background: var(--white);
    padding: 6px;
    object-fit: contain;
}

/* Generic public section */
.section { padding: var(--sp-12) 0; }
.section--grey {
    background: linear-gradient(180deg, var(--grey-50) 0%, var(--grey-100) 55%, #EDEFF2 100%);
}

.section__head {
    max-width: 62ch;
    margin: 0 auto var(--sp-8);
    text-align: center;
}

.section__head h2 { font-size: 1.75rem; }

.section__head p { color: var(--grey-500); margin-bottom: 0; }

/* Portal access cards */
.portal-card {
    display: flex;
    flex-direction: column;
    padding: var(--sp-6);
    background: var(--white);
    border: 1px solid var(--grey-200);
    border-top: 3px solid var(--brand);
    height: 100%;
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.portal-card:hover {
    border-color: var(--brand-border);
    border-top-color: var(--brand-2);
    box-shadow: var(--shadow);
    text-decoration: none;
}

/* The icon stands on its own - no tinted tile behind it. */
.portal-card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--brand);
    margin-bottom: var(--sp-3);
}

.portal-card__title {
    font-size: 1.02rem;
    font-weight: 600;
    color: var(--grey-900);
    margin-bottom: var(--sp-2);
}

.portal-card__text {
    font-size: 0.89rem;
    color: var(--grey-500);
    flex: 1;
    margin-bottom: var(--sp-4);
}

.portal-card__link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--brand-2);
}

/* Feature list */
.feature {
    display: flex;
    gap: var(--sp-4);
    padding: var(--sp-5);
    background: var(--white);
    border: 1px solid var(--grey-200);
    height: 100%;
}

/* The icon stands on its own - no filled tile behind it, so it takes the
   brand colour instead of white. */
.feature__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--brand);
}

.feature__title {
    font-size: 0.96rem;
    font-weight: 600;
    color: var(--grey-900);
    margin-bottom: var(--sp-1);
}

.feature__text {
    font-size: 0.87rem;
    color: var(--grey-500);
    margin: 0;
}

/* Announcement item */
.notice {
    display: block;
    padding: var(--sp-5);
    background: var(--white);
    border: 1px solid var(--grey-200);
    border-left: 3px solid var(--brand);
    height: 100%;
}

.notice:hover {
    text-decoration: none;
    border-left-color: var(--brand-2);
    box-shadow: var(--shadow);
}

.notice__date {
    font-size: 0.76rem;
    color: var(--grey-500);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: var(--sp-2);
}

.notice__title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--grey-900);
    margin-bottom: var(--sp-2);
}

.notice__text {
    font-size: 0.88rem;
    color: var(--grey-500);
    margin: 0;
}

/* Programme card */
.programme {
    padding: var(--sp-5);
    background: var(--white);
    border: 1px solid var(--grey-200);
    height: 100%;
}

.programme__code {
    display: inline-block;
    padding: 2px var(--sp-2);
    background: transparent;
    border: 0;
    color: var(--brand);
    font-size: 0.74rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    margin-bottom: var(--sp-3);
}

.programme__name {
    font-size: 1rem;
    font-weight: 600;
    color: var(--grey-900);
    margin-bottom: var(--sp-2);
}

.programme__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--grey-200);
    font-size: 0.82rem;
    color: var(--grey-500);
}

/* Contact block */
.contact-block {
    position: relative;
    padding: var(--sp-6) var(--sp-5) var(--sp-5);
    background: var(--white);
    border: 1px solid var(--grey-200);
    border-radius: 14px;
    box-shadow: var(--shadow-sm);
    height: 100%;
    overflow: hidden;
    transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}

.contact-block:hover {
    transform: translateY(-4px);
    box-shadow: var(--shadow-lift);
    border-color: var(--brand-border);
}

/* Campus / location name, set off from the detail rows by a hairline. */
.contact-block > .strong {
    display: block;
    font-size: 1.05rem;
    letter-spacing: -0.01em;
    padding-bottom: var(--sp-4);
    margin-bottom: var(--sp-4);
    border-bottom: 1px solid var(--grey-100);
}

.contact-block__row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    font-size: 0.9rem;
    line-height: 1.4;
    margin-bottom: var(--sp-3);
    color: var(--grey-700);
}

.contact-block__row:last-child { margin-bottom: 0; }

.contact-block__row a {
    color: var(--brand-2);
    font-weight: 500;
    text-decoration: none;
    transition: color .2s ease;
}

.contact-block__row a:hover { color: var(--brand); text-decoration: underline; }

/* Each detail icon sits in a soft brand chip that fills in on card hover. */
.contact-block__row .icon {
    flex: 0 0 auto;
    box-sizing: border-box;
    width: 34px;
    height: 34px;
    padding: 8px;
    border-radius: 9px;
    background: var(--brand-light);
    color: var(--brand);
    margin-top: 0;
    transition: background .25s ease, color .25s ease;
}

.contact-block:hover .contact-block__row .icon {
    background: var(--brand);
    color: var(--white);
}

/* Public footer */
.public-footer {
    position: relative;
    background: linear-gradient(180deg, var(--brand) 0%, var(--brand-dark) 60%, #22143B 100%);
    color: #CDC3E6;
    padding: var(--sp-10) 0 0;
}

/* A thin luminous rule where the footer meets the page above it. */
.public-footer::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(154, 138, 216, 0.6), transparent);
}

.public-footer__grid {
    display: grid;
    grid-template-columns: 1.6fr 1fr 1.1fr;
    gap: var(--sp-10);
    padding-bottom: var(--sp-8);
}

/* Brand lockup */
.public-footer__lockup {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    margin-bottom: var(--sp-4);
}

.public-footer__lockup img {
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    padding: 3px;
}

.public-footer__inst {
    color: var(--white);
    font-weight: 700;
    font-size: 1rem;
    line-height: 1.25;
    letter-spacing: -0.01em;
}

.public-footer__blurb {
    font-size: 0.88rem;
    line-height: 1.6;
    max-width: 42ch;
    margin: 0;
    color: #BCAFDC;
}

.public-footer h4 {
    color: var(--white);
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: var(--sp-4);
    position: relative;
    padding-bottom: var(--sp-2);
}

/* Short accent underline beneath each column heading. */
.public-footer h4::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    width: 28px;
    height: 2px;
    border-radius: 2px;
    background: var(--brand-accent);
}

.public-footer a {
    color: #CDC3E6;
    font-size: 0.88rem;
    transition: color .2s ease;
}

.public-footer a:hover { color: var(--white); }

.public-footer ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

.public-footer li { margin-bottom: var(--sp-3); }

/* Contact links: icon chip beside each line. */
.public-footer__contact a {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
}

.public-footer__contact .icon {
    flex: 0 0 auto;
    color: var(--brand-accent);
    transition: color .2s ease;
}

.public-footer__contact a:hover .icon { color: var(--white); }

.public-footer__bar {
    /* Slate grey bar with a red rule on top. */
    background: linear-gradient(180deg, #565B70 0%, #444960 100%);
    border-top: 2px solid var(--red);
    color: #FFFFFF;
    padding: var(--sp-4) 0;
    font-size: 0.8rem;
}

.public-footer__bar a,
.home__foot a { color: #C9C4F2; }
.public-footer__bar a:hover,
.home__foot a:hover { color: #FFFFFF; }

.public-footer__bar-inner {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-3);
}

/* Legal links in the footer bar, between the copyright and the tagline. */
.public-footer__legal {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
}
.public-footer__legal a { white-space: nowrap; }

/* ---------------------------------------------------------------------
   14. Authentication pages
   --------------------------------------------------------------------- */

.auth {
    height: 100vh;
    height: 100svh;
    overflow: hidden;           /* the sign-in screen fills the viewport, never scrolls */
    display: grid;
    grid-template-columns: 1fr 1fr;
}

.auth__aside { overflow: hidden; }

/* Everything on the sign-in screen is kept inside the viewport: the column
   centres its card, and if the window is shorter than the form the card
   scrolls inside the column instead of being cut off. The spacing below is
   compacted so the whole form usually fits on short laptop windows. */
.auth__main {
    overflow-y: auto;         /* the card scrolls inside the viewport if the
                                 window is short, instead of being clipped */
    overflow-x: hidden;
    padding: clamp(16px, 4vh, 40px) var(--sp-6);
}

.auth .auth__head { padding: var(--sp-5) var(--sp-5) var(--sp-3); }
.auth .auth__body { padding: var(--sp-5); }
.auth .auth__foot { padding: var(--sp-3) var(--sp-5); }
.auth .field { margin-bottom: var(--sp-3); }
.auth .field__hint { margin-top: 4px; }
.auth .auth__body .btn { margin-top: var(--sp-1); }
.auth .auth__body .btn--lg { min-height: 42px; padding: 10px var(--sp-6); }
.auth .auth__body p { margin-top: var(--sp-3) !important; }
.auth__main > div > .text-center:last-child { margin-top: var(--sp-3) !important; font-size: 0.82rem; }

.auth__aside {
    position: relative;
    overflow: hidden;
    background:
        radial-gradient(760px 460px at 50% 8%, rgba(126, 91, 190, 0.38) 0%, transparent 60%),
        linear-gradient(162deg, #4B3E8E 0%, #473A82 48%, #2E2457 100%);
    color: var(--white);
    padding: var(--sp-12) var(--sp-10);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
}

/* The molecular lattice behind the brand, quiet enough that the emblem and the
   points list stay the only things being read. */
/* The students photo sits behind the brand panel, dimmed into the indigo so
   the logo and the points list stay the things being read, and masked so it
   fades out under the logo at the top. */
.auth__aside::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--auth-photo, url("../img/students.jpg")) center 32% / cover no-repeat;
    opacity: 0.42;
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 38%, #000 100%);
    mask-image: linear-gradient(180deg, transparent 0%, #000 38%, #000 100%);
    pointer-events: none;
}

/* A second veil over the photo keeps the indigo dominant (matching the brand
   panel) and holds enough contrast under the white heading and bullets. */
.auth__aside::after {
    content: "";
    position: absolute;
    inset: 0;
    /* A soft pool behind the heading over a vertical fade that deepens into the
       indigo towards the foot, matching the homepage photo treatment. */
    background:
        radial-gradient(640px 320px at 50% 30%, rgba(42, 26, 72, 0.34) 0%, transparent 72%),
        linear-gradient(180deg, rgba(60, 48, 112, 0.34) 0%, rgba(46, 36, 87, 0.80) 56%, #2E2457 100%);
    pointer-events: none;
}

.auth__brand-block {
    position: relative;
    z-index: 1;
    max-width: 30rem;
}

/* The emblem sits inside a soft glowing disc ringed in white. */
.auth__logo-disc {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 158px;
    height: 158px;
    border-radius: 50%;
    margin: 0 auto var(--sp-6);
    /* Kept close to transparent: the care pattern runs on behind the emblem
       rather than stopping at a disc of flat colour. */
    background: radial-gradient(circle at 50% 40%,
        rgba(255, 255, 255, 0.07) 0%,
        rgba(255, 255, 255, 0.02) 58%,
        rgba(255, 255, 255, 0) 100%);
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, 0.22),
        0 0 0 10px rgba(255, 255, 255, 0.04),
        0 0 0 11px rgba(255, 255, 255, 0.10),
        0 24px 50px rgba(16, 8, 34, 0.45);
}

.auth__logo {
    width: 96px;
    height: 96px;
    border-radius: 50%;     /* the circular institution mark */
    background: var(--white);
    padding: 4px;
    object-fit: contain;
}

.auth__brand-title {
    font-family: "Source Serif 4", Georgia, "Times New Roman", serif;
    color: var(--white);
    font-size: clamp(1.7rem, 2.4vw, 2.3rem);
    font-weight: 700;
    line-height: 1.14;
    letter-spacing: -0.01em;
    margin: 0 0 var(--sp-3);
}

.auth__brand-accent { color: #F2585A; }  /* the accent word, picked out in red */

.auth__brand-sub {
    color: #DCD4F0;
    font-size: 1rem;
    line-height: 1.6;
    max-width: 34ch;
    margin: 0 auto;
}

.auth__points {
    position: relative;
    z-index: 1;
    list-style: none;
    padding: 0;
    margin: var(--sp-8) 0 0;
    display: inline-block;
    text-align: left;
}

.auth__points li {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    margin-bottom: var(--sp-3);
    font-size: 0.92rem;
    color: #DCD4F0;
}

.auth__points .icon { color: var(--white); margin-top: 2px; }

.auth__main {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-10) var(--sp-6);
    background: var(--grey-100);
}

.auth__card {
    width: 100%;
    max-width: 408px;
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow);
}

.auth__head {
    padding: var(--sp-6) var(--sp-6) var(--sp-4);
    border-bottom: 1px solid var(--grey-200);
}

.auth__head h1 { font-size: 1.25rem; margin-bottom: var(--sp-1); }
.auth__head p { color: var(--grey-500); font-size: 0.88rem; margin: 0; }

.auth__body { padding: var(--sp-6); }

.auth__foot {
    padding: var(--sp-4) var(--sp-6);
    border-top: 1px solid var(--grey-200);
    background: var(--grey-50);
    font-size: 0.86rem;
    text-align: center;
}

.auth__mobile-brand {
    display: none;
    align-items: center;
    gap: var(--sp-3);
    justify-content: center;
    margin-bottom: var(--sp-5);
}

/* ---------------------------------------------------------------------
   15. Print
   --------------------------------------------------------------------- */
@media print {
    .sidebar,
    .topbar,
    .page-head__actions,
    .filters,
    .pagination,
    .footer-note,
    .public-nav,
    .public-footer,
    .btn { display: none !important; }

    .main { margin-left: 0; }
    .content { padding: 0; }
    body { background: var(--white); font-size: 11pt; }
    .card { border: 1px solid #5A6270; box-shadow: none; }
    .table th, .table td { border-bottom: 1px solid #A8AFBB; }
    /* Wide tables print in full rather than being clipped at the scroll edge. */
    .table-wrap { overflow: visible; }
    .card, .stat, .panel { break-inside: avoid; }
    /* Dark header bands print as plain light rows so nothing is lost when
       the browser omits background colours. */
    .card__header,
    .table thead th { background: none; color: #2B3441; border-color: #A8AFBB; }
}

/* ---------------------------------------------------------------------
   16. Responsive
   --------------------------------------------------------------------- */
/* A wide table on a laptop screen is the tightest fit in the portal: the
   sidebar takes its width off the top before a single column is drawn. Cells
   give up some of their breathing room so every column, and the row buttons
   at the far right, stay on the page instead of running off it. */
@media (max-width: 1400px) {
    .table th,
    .table td { padding: 10px var(--sp-3); }

    .content { padding: var(--sp-5); }
}

/* A table sharing its row with a form has about two thirds of the content
   width to work in, which a seven-column table outgrows on a laptop: the
   last column - usually the row action - lands beyond the edge, and a
   button nobody can see might as well not be there.

   Three stages, in order of how much they cost the reader. First the side
   padding goes, and the action column is pinned to its own width so the
   columns that can wrap absorb the loss. Then the type comes down a step.
   Below 1200px no amount of squeezing is honest, so the row splits and the
   table takes the full width with the form beneath it. The squeezing rules
   are bounded below by that breakpoint - once the table has the width to
   itself there is nothing to buy. */
@media (min-width: 1201px) and (max-width: 1500px) {
    .grid--sidebar .table th,
    .grid--sidebar .table td { padding-left: var(--sp-2); padding-right: var(--sp-2); }

    .grid--sidebar .table td.text-right,
    .grid--sidebar .table th:last-child { width: 1%; white-space: nowrap; }
}

@media (min-width: 1201px) and (max-width: 1300px) {
    .grid--sidebar .table th,
    .grid--sidebar .table td { padding-left: 6px; padding-right: 6px; }

    .grid--sidebar .table { font-size: 0.86rem; }
    .grid--sidebar .table thead th { font-size: 0.72rem; letter-spacing: 0.03em; }
}

@media (max-width: 1200px) {
    .grid--sidebar { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 1150px) {
    .table th,
    .table td { padding: 9px var(--sp-2); }

    .table { font-size: 0.86rem; }
    .table thead th { font-size: 0.72rem; letter-spacing: 0.04em; }
}

/* Only once a row of buttons genuinely has nowhere left to go do they stack.
   Allowing it any earlier backfires: a wrapping button row reports a much
   smaller minimum width, so the table squeezes the column and stacks them
   while there was still room to sit side by side. */
@media (max-width: 1050px) {
    .table .actions { flex-wrap: wrap; justify-content: flex-start; }
    .table .actions .btn { flex: 1 1 auto; justify-content: center; }
}

@media (max-width: 1100px) {
    .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .public-footer__grid { grid-template-columns: 1fr 1fr; }
    .hero__grid { grid-template-columns: 1fr; }
    .hero__panel { max-width: 420px; }

    .landing__copy { max-width: 36vw; }
    .landing__watermark { width: 440px; height: 440px; left: -140px; }
}

@media (max-width: 920px) {
    .landing {
        padding: 56px 0 64px;
        min-height: 0;
        display: block;
    }

    .landing__copy { max-width: 100%; }
    .landing::after { background: linear-gradient(180deg, rgba(42, 26, 72, 0.55) 30%, transparent 100%); }

    /* There is no room for a side-by-side photograph, so it spans the whole
       masthead as a faint backdrop and the overlay carries most of the tint. */
    .landing__photo { width: 100%; border-left: 0; }

    /* Full-width backdrop here, so a tint is reinstated purely for text
       legibility (the desktop overlay was removed). */
    .landing__photo::after {
        content: "";
        position: absolute;
        inset: 0;
        background:
            linear-gradient(180deg,
                rgba(42, 26, 72, 0.92) 0%,
                rgba(56, 36, 95, 0.80) 45%,
                rgba(42, 26, 72, 0.94) 100%);
    }

    .landing__watermark {
        width: 380px;
        height: 380px;
        left: -150px;
        opacity: 0.06;
    }
}

@media (max-width: 920px) {
    /* Below this width the sidebar becomes a drawer: it slides in from the
       left over the content, and the menu toggle in the topbar opens it. */
    .sidebar {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        width: min(80vw, 300px);
        height: 100vh;
        transform: translateX(-100%);
        transition: transform 0.18s ease;
        will-change: transform;
    }

    .sidebar[data-open="true"] { transform: translateX(0); }

    .main { margin-left: 0; }

    .menu-toggle { display: inline-flex; }

    .grid--3,
    .grid--sidebar { grid-template-columns: minmax(0, 1fr); }

    /* Keep rows of KPI stat tiles laid out across the width rather than
       collapsing into a single tall column. */
    .grid:has(> .stat) { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    .auth { grid-template-columns: 1fr; }
    .auth__aside { display: none; }
    .auth__mobile-brand { display: flex; }

    .public-links {
        display: none;
        position: absolute;
        top: 68px;
        left: 0;
        right: 0;
        flex-direction: column;
        align-items: stretch;
        gap: 0;
        padding: var(--sp-2) var(--sp-4) var(--sp-4);
        background: var(--white);
        border-bottom: 1px solid var(--grey-200);
        box-shadow: var(--shadow);
    }

    .public-links[data-open="true"] { display: flex; }

    .public-links a {
        padding: var(--sp-3) 0;
        border-bottom: 1px solid var(--grey-200);
    }

    .public-links a:hover,
    .public-links a.active { border-bottom-color: var(--grey-200); }

    .public-links .btn { margin-top: var(--sp-3); }

    .nav-toggle { display: inline-flex; }
    .public-nav__inner { position: relative; }
}

@media (max-width: 640px) {
    :root { --fs-base: 15px; }

    .content { padding: var(--sp-4); }
    .topbar { padding: 0 var(--sp-4); }

    /* Masthead at phone width */
    .landing { padding: 40px 0 48px; }
    .landing__subtitle { font-size: 1.2rem; }
    .landing__lead { font-size: 0.97rem; }
    .landing__actions { flex-direction: column; }
    .landing__actions .landing__cta { width: 100%; justify-content: center; }

    /* The staff buttons stay on one line and share the width between them,
       rather than stacking into three more full-width blocks under the two
       above. */
    .landing__portals-label { width: 100%; margin-bottom: var(--sp-1); }
    .portal-btn { flex: 1 1 0; justify-content: center; }

    .landing__watermark { width: 300px; height: 300px; left: -120px; }

    .nav-cta, .nav-signin { width: 100%; justify-content: center; }

    .grid--2,
    .grid--4 { grid-template-columns: minmax(0, 1fr); }

    /* KPI stat tiles are small enough to sit two-across on a phone, which
       reads left-to-right rather than as one tall stack. This wins over the
       single-column rule above because the :has() selector is more specific. */
    .grid:has(> .stat) { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    .hero { padding: var(--sp-8) 0; }
    .hero h1 { font-size: 1.75rem; }

    .section { padding: var(--sp-8) 0; }
    .section__head h2 { font-size: 1.4rem; }

    .public-footer__grid { grid-template-columns: 1fr; gap: var(--sp-6); }

    .page-head { flex-direction: column; }
    .page-head__actions { width: 100%; }
    .page-head__actions .btn { flex: 1; }

    .detail-list { grid-template-columns: 1fr; gap: var(--sp-1); }
    .detail-list dd { margin-bottom: var(--sp-3); }

    .filters .field { flex-basis: 100%; }
    .filters .field--actions { width: 100%; }
    .filters .field--actions .btn { flex: 1; }

    .card__header { flex-direction: column; align-items: flex-start; }

    .pagination { flex-direction: column; align-items: stretch; }
    .pagination__pages { justify-content: center; }

    /* Touch-friendly controls */
    .btn { min-height: 42px; }
    .input, .select, .textarea { min-height: 42px; font-size: 16px; }
}

/* ---------------------------------------------------------------------
   17. Reduced motion
   --------------------------------------------------------------------- */
@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;
    }
}

/* =====================================================================
   Pro features: notification bell, Campus Chat, misc helpers
   Added with the security / messaging layer. Content-area components, so
   they use the light surface tokens that stay light under every sidebar
   theme.
   ===================================================================== */

/* -- shared small helpers -- */
.btn--link {
    background: none;
    border: 0;
    padding: 0;
    color: var(--brand);
    cursor: pointer;
    font: inherit;
}
.btn--link:hover { text-decoration: underline; }
.list-plain { list-style: none; margin: 0; padding: 0; }

/* -- notification bell -- */
.topbar__chat {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border: 0;
    background: transparent;
    color: var(--grey-700);
    border-radius: 8px;
    cursor: pointer;
}
.topbar__chat:hover,
.topbar__chat--active { background: transparent; }
.topbar__chat-badge {
    position: absolute;
    top: -6px;
    right: -6px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: 9px;
    background: var(--red);
    color: var(--white);
    font-size: 0.68rem;
    font-weight: 700;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

.notif {
    position: relative;
}
.notif__trigger {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border: 0;
    background: transparent;
    color: var(--grey-700);
    border-radius: 8px;
    cursor: pointer;
}
.notif__trigger:hover { background: var(--grey-100); }
.notif__count {
    position: absolute;
    top: -6px;
    right: -6px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: 9px;
    background: var(--red);
    color: var(--white);
    font-size: 0.68rem;
    font-weight: 700;
    display: none;
    align-items: center;
    justify-content: center;
    line-height: 1;
}
.notif__count[data-show="true"] { display: inline-flex; }
.notif__panel {
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    width: 340px;
    max-width: 86vw;
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow-lg);
    z-index: 50;
    display: none;
}
.notif__panel[data-open="true"] { display: block; }
.notif__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--grey-200);
    background: var(--grey-50);
    font-weight: 600;
}
.notif__scroll { max-height: 360px; overflow-y: auto; }
.notif__item {
    display: flex;
    gap: 10px;
    padding: 11px var(--sp-4);
    border-bottom: 1px solid var(--grey-100);
    color: var(--grey-700);
}
.notif__item:hover { background: var(--brand-light); text-decoration: none; }
.notif__item--unread { background: var(--grey-50); }
.notif__item .icon { color: var(--brand); flex: 0 0 auto; margin-top: 2px; }
.notif__item-title { font-weight: 600; font-size: 0.86rem; }
.notif__item-body  { font-size: 0.8rem; color: var(--grey-500); }
.notif__item-time  { font-size: 0.72rem; color: var(--grey-500); margin-top: 2px; }
.notif__foot {
    padding: 10px var(--sp-4);
    text-align: center;
    border-top: 1px solid var(--grey-200);
    background: var(--grey-50);
}
.notif__empty { padding: var(--sp-6); text-align: center; color: var(--grey-500); }

/* -- Campus Chat -- */
.chat {
    display: grid;
    grid-template-columns: 300px 1fr;
    gap: 0;
    border: 1px solid var(--grey-200);
    background: var(--white);
    box-shadow: var(--shadow-sm);
    min-height: 60vh;
    overflow: hidden;
}
.chat__list {
    border-right: 1px solid var(--grey-200);
    overflow-y: auto;
    max-height: 72vh;
    background: var(--grey-50);
}
.chat__item {
    display: flex;
    gap: 10px;
    align-items: center;
    padding: 12px 14px;
    border-bottom: 1px solid var(--grey-200);
    color: var(--grey-700);
}
.chat__item:hover { background: var(--brand-light); text-decoration: none; }
.chat__item--active { background: var(--white); box-shadow: inset 3px 0 0 var(--brand); }
.chat__avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px; height: 38px;
    border-radius: 50%;
    background: var(--grad-brand);
    color: var(--white);
    font-size: 0.78rem;
    font-weight: 600;
    flex: 0 0 auto;
}
.chat__item-body { min-width: 0; flex: 1; }
.chat__item-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.chat__name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat__preview { display: block; font-size: 0.78rem; color: var(--grey-500); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chat__badge {
    background: var(--red); color: var(--white);
    font-size: 0.68rem; font-weight: 700;
    min-width: 18px; height: 18px; padding: 0 5px;
    border-radius: 9px; display: inline-flex; align-items: center; justify-content: center;
}
.chat__thread { display: flex; flex-direction: column; max-height: 72vh; }
.chat__thread-head {
    display: flex; align-items: center; gap: 10px;
    padding: 12px 16px; border-bottom: 1px solid var(--grey-200);
    background: var(--white);
}
.chat__messages {
    flex: 1;
    overflow-y: auto;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: var(--grey-50);
}
.chat__bubble-row { display: flex; justify-content: flex-start; }
.chat__bubble-row--mine { justify-content: flex-end; }
.chat__bubble {
    max-width: 72%;
    padding: 8px 12px;
    border-radius: 12px;
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow-sm);
}
.chat__bubble-row--mine .chat__bubble {
    background: var(--brand);
    color: var(--white);
    border-color: var(--brand);
}
.chat__bubble-text { font-size: 0.9rem; line-height: 1.45; }
.chat__bubble-time { font-size: 0.68rem; opacity: 0.7; margin-top: 3px; text-align: right; }
.chat__compose {
    display: flex;
    gap: 8px;
    padding: 12px;
    border-top: 1px solid var(--grey-200);
    background: var(--white);
}
.chat__compose .input { flex: 1; }
.chat__placeholder, .chat__empty, .chat__placeholder .muted {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 10px; color: var(--grey-500); text-align: center; padding: var(--sp-10) var(--sp-6);
    flex: 1;
}
.chat__empty { padding: var(--sp-8) var(--sp-4); }

@media (max-width: 720px) {
    .chat { grid-template-columns: 1fr; }
    /* On a phone, show the list on the index and the thread on its own page. */
    .chat--thread-open .chat__list { display: none; }
}

/* Unread badge on the Campus Chat sidebar link. */
.nav-item__badge {
    margin-left: auto;
    background: var(--red);
    color: var(--white);
    font-size: 0.66rem;
    font-weight: 700;
    min-width: 17px;
    height: 17px;
    padding: 0 5px;
    border-radius: 9px;
    align-items: center;
    justify-content: center;
}

/* Server-rendered horizontal bar charts (analytics). */
.chart-bars { display: flex; flex-direction: column; gap: 10px; }
.chart-bar {
    display: grid;
    grid-template-columns: 110px 1fr auto;
    align-items: center;
    gap: 10px;
}
.chart-bar__label { font-size: 0.82rem; color: var(--grey-700); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.chart-bar__track { background: var(--grey-100); border-radius: 4px; height: 16px; overflow: hidden; }
.chart-bar__fill  { display: block; height: 100%; border-radius: 4px; min-width: 2px; transition: width .3s ease; }
.chart-bar__value { font-size: 0.78rem; color: var(--grey-500); white-space: nowrap; font-variant-numeric: tabular-nums; }
@media (max-width: 520px) {
    .chart-bar { grid-template-columns: 84px 1fr; }
    .chart-bar__value { grid-column: 2; text-align: right; }
}

/* Document hub rows. */
.doc-row { display: flex; gap: 14px; padding: 14px 16px; border-bottom: 1px solid var(--grey-200); align-items: center; }
.doc-row__qr { flex: 0 0 auto; width: 84px; height: 84px; background: #fff; padding: 2px; border: 1px solid var(--grey-200); }
.doc-row__qr img, .doc-row__qr canvas { display: block; }
.doc-row__body { min-width: 0; flex: 1; }
.doc-row__actions { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }

/* =====================================================================
   Campus Chat voice call overlay
   ===================================================================== */
.call-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(20, 24, 40, 0.55);
    backdrop-filter: blur(2px);
    padding: 16px;
}
.call-overlay[hidden] { display: none; }
.call-box {
    width: 320px;
    max-width: 92vw;
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow-lg);
    border-radius: 14px;
    padding: 28px 24px;
    text-align: center;
}
.call-avatar {
    width: 84px;
    height: 84px;
    border-radius: 50%;
    margin: 0 auto 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    /* A deeper, clearly diagonal wash so the disc reads dark and dimensional,
       not a flat brand fill. */
    background: linear-gradient(145deg, var(--brand-2) 0%, var(--brand-dark) 100%);
    color: var(--white);
    font-size: 1.7rem;
    font-weight: 600;
    box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.18);
    animation: call-pulse 1.8s ease-out infinite;
}
.call-overlay[data-role="caller"] .call-avatar { animation-duration: 2.2s; }
@keyframes call-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(71, 58, 130, 0.45); }
    70%  { box-shadow: 0 0 0 18px rgba(71, 58, 130, 0); }
    100% { box-shadow: 0 0 0 0 rgba(71, 58, 130, 0); }
}
.call-name   { font-size: 1.15rem; font-weight: 600; color: var(--grey-900); }
.call-sub    { font-size: 0.8rem; color: var(--grey-500); }
.call-status { font-size: 0.9rem; color: var(--brand); margin-top: 6px; }
.call-timer  { font-size: 1.1rem; font-variant-numeric: tabular-nums; color: var(--grey-700); margin-top: 4px; }
.call-actions {
    display: flex;
    justify-content: center;
    gap: 16px;
    margin-top: 22px;
}
.call-btn {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    border: 0;
    cursor: pointer;
    background: none;
    font: inherit;
    font-size: 0.72rem;
    color: var(--grey-700);
}
/* The JS toggles buttons with the `hidden` attribute; the display rule above
   would otherwise override it and leave Accept/Mute showing when they should
   not (e.g. after a microphone error). */
.call-btn[hidden] { display: none; }
.call-btn > .icon {
    width: 54px;
    height: 54px;
    padding: 15px;
    border-radius: 50%;
    background: var(--grey-100);
    color: var(--grey-700);
    box-sizing: border-box;
}
.call-btn--accept > .icon { background: var(--success); color: #fff; }
.call-btn--end > .icon    { background: var(--danger);  color: #fff; }
.call-btn:hover > .icon   { filter: brightness(0.95); }
@media (prefers-reduced-motion: reduce) { .call-avatar { animation: none; } }

/* ------------------------------------------------------------------ */
/* Online library - reading-room book grid                            */
/* ------------------------------------------------------------------ */
.book-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: var(--sp-4);
}
.book-card {
    display: flex;
    flex-direction: column;
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow-sm);
    color: inherit;
    text-decoration: none;
    transition: box-shadow .15s ease, transform .15s ease;
}
.book-card:hover {
    box-shadow: var(--shadow-lift);
    transform: translateY(-2px);
}
.book-card__cover {
    display: block;
    aspect-ratio: 3 / 4;
    background: var(--brand-light);
    overflow: hidden;
}
.book-card__cover img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.book-card__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: var(--brand);
}
.book-card__body {
    display: block;
    padding: var(--sp-3) var(--sp-3) var(--sp-4);
}
.book-card__cat {
    display: inline-block;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--brand);
    margin-bottom: 4px;
}
.book-card__title {
    display: block;
    font-weight: 600;
    line-height: 1.3;
    margin-bottom: 2px;
}
.book-card__author {
    display: block;
    font-size: 13px;
    color: #6B7280;
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .book-card__author { color: #9AA3AF; }
}

/* ---------------------------------------------------------------------
   Fee account - fintech layout
   ---------------------------------------------------------------------
   A money view, so it borrows the language of a banking dashboard: a
   large balance hero, a green "paid" highlight beside it, and a row of
   softer cards below for progress, breakdown and payment history. The
   green here is local to this block, not the institutional purple, the
   way an "amount paid" figure earns its own accent. Built on the same
   grey/white tokens as every other card so it tracks the active theme.
   --------------------------------------------------------------------- */
.fin {
    --fin-ink:     var(--brand-dark);   /* deep institutional purple: text/buttons */
    --fin-accent:  var(--brand);        /* brand purple: bars, the round action */
    --fin-lime-1:  #EDE9F8;             /* highlight card, top of the wash */
    --fin-lime-2:  #DCD3F1;             /* highlight card, foot of the wash */

    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
}

.fin__top {
    display: grid;
    grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
    gap: var(--sp-4);
}
.fin__bottom {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
    gap: var(--sp-4);
}

/* Shared card shell: rounder than the default card, to read as its own
   panel the way the dashboard tiles in the reference do. */
.fin-hero,
.fin-reward,
.fin-card {
    border-radius: 12px;
    padding: var(--sp-5);
}
.fin-hero,
.fin-card {
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow-sm);
}

/* --- Balance hero ------------------------------------------------- */
.fin-hero {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    container-type: inline-size;
}
.fin-hero__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-4);
}

/* Year selector as a pill group, like the currency toggle. */
.fin-pills {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    background: var(--grey-100);
    border-radius: 999px;
}
.fin-pill {
    padding: 6px 15px;
    border-radius: 999px;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--grey-500);
    white-space: nowrap;
}
.fin-pill:hover { text-decoration: none; color: var(--grey-900); }
.fin-pill--active {
    background: var(--white);
    color: var(--fin-ink);
    box-shadow: var(--shadow-sm);
}

.fin-acct { text-align: right; line-height: 1.3; }
.fin-acct__label {
    display: block;
    font-size: 0.72rem;
    color: var(--grey-500);
}
.fin-acct__num {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--grey-900);
    letter-spacing: 0.02em;
}

.fin-hero__body { display: flex; flex-direction: column; gap: 4px; }
.fin-label {
    font-size: 0.82rem;
    color: var(--grey-500);
}
.fin-figure {
    font-family: var(--font-num);
    font-size: clamp(1.9rem, 5.5cqi, 2.6rem);
    font-weight: 600;
    line-height: 1.05;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums lining-nums;
}
.fin-figure.is-ok     { color: var(--grey-900); }
.fin-figure.is-danger { color: var(--danger); }

.fin-hero__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    margin-top: auto;
    flex-wrap: wrap;
}
.fin-actions { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.fin-btn {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 10px 18px;
    border-radius: 999px;
    font-size: 0.86rem;
    font-weight: 600;
    border: 1px solid transparent;
    transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
.fin-btn .icon { width: 16px; height: 16px; }
.fin-btn--dark {
    background: var(--fin-ink);
    color: #FFFFFF;
}
.fin-btn--dark:hover { background: var(--brand); text-decoration: none; color: #FFFFFF; }
.fin-btn--ghost {
    background: var(--white);
    border-color: var(--grey-200);
    color: var(--grey-700);
}
.fin-btn--ghost:hover { border-color: var(--fin-ink); color: var(--fin-ink); text-decoration: none; }

.fin-fab {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 54px;
    height: 54px;
    border-radius: 50%;
    background: var(--fin-accent);
    color: #FFFFFF;
    box-shadow: 0 6px 16px rgba(71, 58, 130, 0.35);
    transition: transform .15s ease;
}
.fin-fab:hover { transform: rotate(45deg); text-decoration: none; color: #FFFFFF; }

/* --- Paid highlight (green card) ---------------------------------- */
.fin-reward {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    color: var(--fin-ink);
    background: linear-gradient(150deg, var(--fin-lime-1) 0%, var(--fin-lime-2) 100%);
    container-type: inline-size;
}
.fin-reward__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
}
.fin-chip {
    display: inline-flex;
    align-items: center;
    padding: 5px 13px;
    border-radius: 999px;
    background: var(--fin-ink);
    color: #FFFFFF;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.08em;
}
.fin-reward__label { font-size: 0.82rem; opacity: 0.8; margin-top: auto; }
.fin-reward__figure-row {
    display: flex;
    align-items: baseline;
    gap: var(--sp-3);
    flex-wrap: wrap;
}
.fin-reward__figure {
    font-family: var(--font-num);
    font-size: clamp(1.6rem, 9.5cqi, 2.2rem);
    font-weight: 600;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums lining-nums;
}
.fin-reward__badge {
    padding: 3px 10px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.55);
    font-size: 0.74rem;
    font-weight: 600;
}
.fin-reward__tiles {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-3);
    margin-top: var(--sp-2);
}
.fin-mini {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: var(--sp-2) var(--sp-3);
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.5);
}
.fin-mini__label {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.74rem;
    font-weight: 600;
    opacity: 0.85;
}
.fin-mini__label .icon { width: 14px; height: 14px; }
.fin-mini__val {
    font-family: var(--font-num);
    font-size: 1.1rem;
    font-weight: 600;
}

/* --- Lower cards: shared head ------------------------------------- */
.fin-card { display: flex; flex-direction: column; }
.fin-card__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-bottom: var(--sp-4);
}
.fin-card__title {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
    color: var(--grey-900);
}
.fin-card__title .icon { width: 17px; height: 17px; color: var(--fin-ink); }
.fin-pill-sm {
    padding: 4px 11px;
    border-radius: 999px;
    background: var(--grey-100);
    font-size: 0.76rem;
    font-weight: 600;
    color: var(--grey-700);
}

/* --- Progress donut ----------------------------------------------- */
.fin-progress { align-items: center; }
.fin-progress .fin-card__head { width: 100%; }
.fin-ring {
    position: relative;
    width: 160px;
    height: 160px;
    border-radius: 50%;
    margin: var(--sp-2) 0;
    background: conic-gradient(
        var(--fin-accent) calc(var(--pct) * 1%),
        var(--grey-100) 0
    );
}
.fin-ring.is-ok { background: conic-gradient(var(--success) 100%, var(--success) 0); }
.fin-ring__hole {
    position: absolute;
    inset: 16px;
    border-radius: 50%;
    background: var(--white);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
}
.fin-ring__value {
    font-family: var(--font-num);
    font-size: 1.9rem;
    font-weight: 600;
    color: var(--grey-900);
    letter-spacing: -0.02em;
}
.fin-ring__sub { font-size: 0.76rem; color: var(--grey-500); }
.fin-progress__note {
    margin: var(--sp-3) 0 0;
    font-size: 0.86rem;
    color: var(--grey-500);
    text-align: center;
}

/* --- Breakdown bars ----------------------------------------------- */
.fin-break__figure {
    font-family: var(--font-num);
    font-size: 2rem;
    font-weight: 600;
    color: var(--grey-900);
    letter-spacing: -0.02em;
    line-height: 1.1;
}
.fin-break__cap { font-size: 0.82rem; color: var(--grey-500); }
.fin-break__row { margin-top: var(--sp-4); }
.fin-break__row-head {
    display: flex;
    justify-content: space-between;
    gap: var(--sp-3);
    font-size: 0.84rem;
    margin-bottom: 7px;
}
.fin-break__row-head span:first-child { color: var(--grey-500); }
.fin-break__row-head span:last-child  { font-weight: 600; color: var(--grey-900); }
.fin-bar {
    height: 8px;
    border-radius: 999px;
    background: var(--grey-100);
    overflow: hidden;
}
.fin-bar__fill { height: 100%; border-radius: 999px; }
.fin-bar__fill.is-paid { background: var(--fin-accent); }
.fin-bar__fill.is-due  { background: var(--danger); }
.fin-break__note {
    margin: var(--sp-4) 0 0;
    font-size: 0.78rem;
    color: var(--grey-500);
}

/* --- Payment history as a transaction feed ------------------------ */
.fin-txn-list { list-style: none; margin: 0; padding: 0; }
.fin-txn {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) 0;
    border-top: 1px solid var(--grey-100);
}
.fin-txn:first-child { border-top: 0; }
.fin-txn__icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 12px;
    background: rgba(132, 204, 22, 0.16);
    color: var(--success);
}
.fin-txn__icon .icon { width: 18px; height: 18px; }
.fin-txn__body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
.fin-txn__name {
    font-weight: 600;
    font-size: 0.88rem;
    color: var(--grey-900);
}
.fin-txn__meta {
    font-size: 0.76rem;
    color: var(--grey-500);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.fin-txn__amount {
    flex: 0 0 auto;
    font-family: var(--font-num);
    font-weight: 600;
    font-size: 0.92rem;
    color: var(--success);
}
.fin-txn-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-top: var(--sp-3);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--grey-200);
    font-weight: 600;
    color: var(--grey-900);
}

/* --- Responsive --------------------------------------------------- */
@media (max-width: 1100px) {
    .fin__top    { grid-template-columns: 1fr; }
    .fin__bottom { grid-template-columns: 1fr; }
}
@media (max-width: 560px) {
    .fin-hero__head { flex-direction: column; }
    .fin-acct { text-align: left; }
    .fin-actions { width: 100%; }
    .fin-btn { flex: 1; justify-content: center; }
}

/* ---------------------------------------------------------------------
   Missed-call log
   ---------------------------------------------------------------------
   The /chat/missed page: a plain list of calls the user did not pick up,
   newest first, each with a "call back". Rows unseen when the page opened
   carry a light brand wash so a glance finds the new ones. */
.chat__missed-link { position: relative; }
.chat__missed-link .chat__badge { margin-left: 3px; }

.calllog { list-style: none; margin: 0; padding: 0; }
.calllog__row {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-4) var(--sp-5);
    border-bottom: 1px solid var(--grey-100);
}
.calllog__row:last-child { border-bottom: 0; }
.calllog__row--new { background: var(--brand-light); }

.calllog__body {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.calllog__name { font-weight: 600; color: var(--grey-900); }
.calllog__meta {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 0.8rem;
    color: var(--grey-500);
}
.calllog__meta .icon { width: 13px; height: 13px; flex-shrink: 0; }
.calllog__tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-weight: 600;
    color: var(--red);
}
.calllog__row .btn { flex: 0 0 auto; }

@media (max-width: 560px) {
    .calllog__row { flex-wrap: wrap; }
    .calllog__body { flex-basis: 55%; }
}

/* ---------------------------------------------------------------------
   Mobile bottom navigation (app-style tab bar)
   ---------------------------------------------------------------------
   Shown only on phones and small tablets (<=920px), where the sidebar is
   off-canvas. Four role-primary destinations plus a Menu tab that opens the
   full drawer. Sits above the page (z 34) but below the drawer + backdrop
   (35/40), the toasts (60) and the call overlay (200), so nothing it would
   cover is ever trapped behind it. Built on existing tokens, so it tracks
   the active theme in light and dark. */
.bottom-nav { display: none; }

@media (max-width: 920px) {
    .bottom-nav {
        display: flex;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 34;
        background: var(--white);
        border-top: 1px solid var(--grey-200);
        box-shadow: 0 -2px 12px rgba(31, 41, 55, 0.08);
        /* Clear the home-indicator / gesture bar on modern phones. */
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }

    .bottom-nav__item {
        flex: 1 1 0;
        min-width: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        min-height: 56px;
        padding: 8px 2px;
        border: 0;
        background: none;
        cursor: pointer;
        color: var(--grey-500);
        font-family: inherit;
        font-size: 0.64rem;
        font-weight: 600;
        letter-spacing: 0.01em;
        text-decoration: none;
        -webkit-tap-highlight-color: transparent;
        transition: color 0.14s ease;
    }
    .bottom-nav__item:hover { text-decoration: none; color: var(--grey-700); }
    .bottom-nav__item:active { background: var(--grey-50); }
    .bottom-nav__item.is-active { color: var(--brand); }

    .bottom-nav__icon {
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    .bottom-nav__icon .icon { width: 23px; height: 23px; }

    .bottom-nav__label {
        line-height: 1;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .bottom-nav__badge {
        position: absolute;
        top: -5px;
        right: -9px;
        min-width: 16px;
        height: 16px;
        padding: 0 4px;
        border-radius: 8px;
        background: var(--red);
        color: #FFFFFF;
        font-size: 0.6rem;
        font-weight: 700;
        align-items: center;      /* display toggled to inline-flex by JS */
        justify-content: center;
    }

    /* Make room so page content and the footer are never hidden behind the
       bar. The bar is ~56px plus whatever the safe-area inset adds. */
    .main { padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); }

    /* The call overlay covers the whole screen; lift its controls above the
       bar's safe-area padding too (overlay z-index already wins). */
    .call-overlay { padding-bottom: env(safe-area-inset-bottom, 0px); }
}

/* ------------------------------------------------------------------ */
/* Responsive tables - stack into labelled cards on phones            */
/* ------------------------------------------------------------------ */
/* A table marked .table--stack drops its horizontal layout at phone  */
/* width: each row becomes a self-contained card and each cell a      */
/* line reading "Label ........ value" from left to right, so the     */
/* whole row fits the screen and nothing is cut off or scrolled away. */
@media (max-width: 640px) {
    /* The scroll container stops scrolling and loses its edge shadows,
       since there is no longer anything off to the side. */
    .table-wrap:has(.table--stack) {
        overflow-x: visible;
        border: 0;
        background: none;
    }

    .table--stack,
    .table--stack tbody,
    .table--stack tr,
    .table--stack td {
        display: block;
        width: 100%;
    }

    .table--stack thead { display: none; }

    .table--stack tr {
        border: 1px solid var(--grey-200);
        margin-bottom: var(--sp-3);
        background: var(--white);
    }
    .table--stack tr:last-child { margin-bottom: 0; }

    .table--stack td {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: var(--sp-4);
        text-align: right;
        padding: 9px var(--sp-4);
        border-bottom: 1px solid var(--grey-100);
        white-space: normal;
    }
    .table--stack td:last-child { border-bottom: 0; }

    /* The column name, taken from the cell's data-label, sits on the left. */
    .table--stack td::before {
        content: attr(data-label);
        flex: 0 0 40%;
        text-align: left;
        font-weight: 600;
        font-size: 0.72rem;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        color: #6B7280;
    }

    /* A cell with no label (an actions cell, say) spans the full width. */
    .table--stack td:not([data-label])::before,
    .table--stack td[data-label=""]::before { content: none; }
    .table--stack td:not([data-label]),
    .table--stack td[data-label=""] {
        justify-content: flex-end;
        padding-top: 11px;
        padding-bottom: 11px;
    }
    .table--stack td.numeric { text-align: right; }

    /* Give a button in an unlabelled actions cell room to be tapped. */
    .table--stack td:not([data-label]) .btn { min-height: 40px; }
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .table--stack tr { background: transparent; }
    :root:not([data-theme="light"]) .table--stack td::before { color: #9AA3AF; }
}


/* ---------------------------------------------------------------------
   Home screen over the sign-in screen
   The home page is the first state of the sign-in layout. It fills the
   viewport; pressing "Sign in" slides its right edge to the left until it
   covers only the left half (where the brand panel is), uncovering the
   white sign-in section, then its own content fades out.
   --------------------------------------------------------------------- */
.auth--intro { position: relative; }

.home {
    --home-cut: 100%;                      /* slides fully away, uncovering the brand panel */
    position: absolute;
    inset: 0;
    z-index: 5;
    color: var(--white);
    background:
        radial-gradient(900px 520px at 30% 12%, rgba(126, 91, 190, 0.40) 0%, transparent 62%),
        linear-gradient(162deg, #4B3E8E 0%, #473A82 48%, #2E2457 100%);
    clip-path: inset(0 0 0 0);
    transition: clip-path 0.95s cubic-bezier(0.77, 0, 0.18, 1);
    will-change: clip-path;
}

/* The picture fills the top and fades out towards the middle, where the
   indigo gradient takes over. It is strongest at the very top. */
.home__photo {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.home__photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 60% 30%;
    filter: saturate(0.78) contrast(1.05) brightness(0.92);
    -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 24%, transparent 70%);
    mask-image: linear-gradient(180deg, #000 0%, #000 24%, transparent 70%);
}

/* The indigo gradient (matching the brand panel) rises from the bottom and
   covers the picture up to the middle, fading to clear above so the picture
   stays visible at the top. A soft pool behind the heading keeps it readable. */
.home__photo::after {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(760px 320px at 50% 50%, rgba(42, 26, 72, 0.40) 0%, transparent 70%),
        linear-gradient(180deg, transparent 42%, rgba(46, 36, 87, 0.86) 72%, #2E2457 100%);
}

.home__content {
    position: relative;
    height: 100%;
    display: flex;
    flex-direction: column;
    padding: clamp(14px, 3vh, 28px) clamp(20px, 5vw, 96px);
    /* The home screen fits within the viewport and never scrolls. */
    overflow: hidden;
    transition: opacity 0.45s ease 0.4s, visibility 0s linear 0.95s;
}

.home__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-6);
    flex-wrap: wrap;
}

.home__brand {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    color: var(--white);
    font-weight: 600;
    text-decoration: none;
}

.home__brand img { border-radius: 50%; background: var(--white); padding: 2px; }

.home__nav { display: flex; gap: clamp(14px, 2.4vw, 36px); flex-wrap: wrap; }

.home__nav a,
.home__foot a {
    color: #E5E0F3;
    text-decoration: none;
    font-size: 0.95rem;
    padding: 4px 0;
    border-bottom: 2px solid transparent;
    transition: border-color 0.2s ease, color 0.2s ease;
}

.home__nav a:hover,
.home__foot a:hover { color: var(--white); border-bottom-color: var(--white); }

.home__body {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 0;
    /* A little headroom lifts the brand clear of the face in the photo, but
       kept small so the whole panel still fits the viewport without scrolling. */
    padding-top: clamp(120px, 30vh, 300px);
    padding-bottom: var(--sp-4);
}

/* The brand panel, centred on the page over the photo: logo, institution
   name, the points list and the apply / verify buttons, all centred. */
.home__hero {
    text-align: center;
    max-width: 560px;
    margin-inline: auto;
}

/* Buttons sit directly under the subtitle; the points list follows them. */
.home__hero .home__links { justify-content: center; margin-top: var(--sp-4); }
.home__hero .auth__points { margin-top: var(--sp-5); }

/* A slightly smaller logo and tighter bullets on the home screen so the brand,
   buttons and points all fit the viewport together. */
.home .auth__logo-disc {
    width: clamp(130px, 21vh, 200px);
    height: clamp(130px, 21vh, 200px);
    margin-bottom: var(--sp-3);
}
.home .auth__logo { width: clamp(84px, 13vh, 126px); height: clamp(84px, 13vh, 126px); }
.home .auth__brand-title { margin-bottom: var(--sp-2); }
.home .auth__points li { margin-bottom: var(--sp-2); }

.home__title {
    font-family: "Archivo Narrow", "Arial Narrow", "Franklin Gothic Medium", Arial, sans-serif;
    font-size: clamp(3.5rem, min(10vw, 22vh), 10rem);
    font-weight: 700;
    -webkit-text-stroke: 0.03em currentColor;
    paint-order: stroke fill;
    line-height: 1;
    letter-spacing: -0.015em;
    color: var(--white);
    margin: 0;
}

.home__sub {
    font-size: clamp(1rem, 1.5vw, 1.35rem);
    letter-spacing: 0.05em;
    color: #E5E0F3;
    margin: var(--sp-2) 0 var(--sp-4);
    white-space: nowrap;
}

.home__lead {
    font-size: clamp(0.9rem, 1.15vw, 1.3rem);
    line-height: 1.65;
    color: #DAD2EE;
    margin: 0 0 var(--sp-6);
}

.home__links { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

.home__link {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 9px 18px;
    border: 1px solid var(--white);
    border-radius: 8px;
    background: var(--white);
    color: var(--brand);
    font-size: 0.95rem;
    text-decoration: none;
    transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

.home__link:hover {
    background: var(--white);
    border-color: var(--white);
    color: var(--brand);
    box-shadow: 0 10px 24px rgba(16, 8, 34, 0.28);
}

/* The eye-catching sign-in button, on the right. */
/* Pinned to the right edge, level with the middle of the screen, so it stays
   put whatever the content beside it does. It never moves on hover either. */
.home__signin {
    position: absolute;
    right: clamp(20px, 5vw, 96px);
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
}

.home__signin-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-4);
    padding: 14px 18px 14px 14px;
    border-radius: 999px;
    background: linear-gradient(180deg, #FFFFFF 0%, #EFEAFA 100%);
    color: var(--brand);
    text-decoration: none;
    box-shadow: 0 14px 40px rgba(16, 8, 34, 0.45);
    transition: box-shadow 0.25s ease, background 0.25s ease;
    animation: home-pulse 2.6s ease-out infinite;
}

.home__signin-btn:hover {
    background: #FFFFFF;
    color: var(--brand-dark);
    text-decoration: none;
}

.home__signin-icon,
.home__signin-arrow {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    background: var(--grad-brand);
    color: var(--white);
}

.home__signin-arrow { width: 38px; height: 38px; }

.home__signin-text { display: flex; flex-direction: column; line-height: 1.15; padding-right: var(--sp-2); }
.home__signin-text strong { font-size: 1.45rem; font-weight: 700; letter-spacing: 0.01em; }
.home__signin-text small { font-size: 0.8rem; color: var(--grey-600); }

@keyframes home-pulse {
    0%   { box-shadow: 0 14px 40px rgba(16, 8, 34, 0.45), 0 0 0 0 rgba(255, 255, 255, 0.45); }
    70%  { box-shadow: 0 14px 40px rgba(16, 8, 34, 0.45), 0 0 0 22px rgba(255, 255, 255, 0); }
    100% { box-shadow: 0 14px 40px rgba(16, 8, 34, 0.45), 0 0 0 0 rgba(255, 255, 255, 0); }
}

/* Full-width footer bar, flush to the edges, with the red line running right
   across the top of it — the same bar as the public site. The negative inline
   and bottom margins pull it out of the content padding so it bleeds to the
   screen edges. */
.home__foot {
    margin-inline: calc(-1 * clamp(20px, 5vw, 96px));
    margin-bottom: calc(-1 * clamp(14px, 3vh, 28px));
    padding: var(--sp-2) clamp(20px, 5vw, 96px);
    border-top: 2px solid var(--red);
    /* The same slate bar as the public-site footer. */
    background: linear-gradient(180deg, #565B70 0%, #444960 100%);
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-6);
    font-size: 0.8rem;
    color: #FFFFFF;
}

.home__foot-legal { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5); }
.home__foot-legal a { white-space: nowrap; }

/* The white section waits, then arrives as the home screen leaves. */
.auth--intro .auth__main > div {
    opacity: 0;
    transform: translateX(56px);
    transition: opacity 0.6s ease 0.35s, transform 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.3s;
}

/* The brand panel is uncovered by the slide; its logo and bullets fade in as
   the home screen clears, so the swap over the shared photo reads as one move. */
.auth--intro .auth__brand-block,
.auth--intro .auth__points {
    opacity: 0;
    transition: opacity 0.5s ease;
}

/* --- Slid away: the right edge travels left, uncovering the brand panel --- */
.auth.is-signin .home { clip-path: inset(0 var(--home-cut) 0 0); pointer-events: none; }
.auth.is-signin .home__content { opacity: 0; visibility: hidden; }
.auth.is-signin .auth__main > div { opacity: 1; transform: none; }
.auth.is-signin .auth__brand-block,
.auth.is-signin .auth__points { opacity: 1; transition-delay: 0.5s; }

/* Straight to the form (the back button): no animation. */
.auth.is-instant .home,
.auth.is-instant .home__content,
.auth.is-instant .auth__main > div { transition: none; }

/* Narrower than this there is no room for the pinned button beside the centred
   brand panel, so bar and footer pin to the edges and the panel plus the
   sign-in button centre together as one column. */
@media (max-width: 1180px) {
    .home__content { justify-content: center; padding-top: clamp(220px, 50vh, 480px); }
    .home__body { flex: none; }

    .home__bar,
    .home__foot {
        position: absolute;
        left: 0;
        right: 0;
        padding-inline: clamp(20px, 5vw, 96px);
    }
    .home__bar { top: clamp(14px, 3vh, 28px); }
    /* The footer bar stays flush to the bottom edge, full width. */
    .home__foot { bottom: 0; margin: 0; }

    .home__signin {
        position: static;
        transform: none;
        align-self: center;
        margin: var(--sp-6) auto 0;
    }
}

@media (max-width: 920px) {
    .home { --home-cut: 100%; }          /* no brand panel on phones: slide fully away */
    .home__hero { max-width: 100%; }
    .home__nav { display: none; }
    .home__signin { position: static; transform: none; align-self: stretch; }
    .home__signin-btn { width: 100%; justify-content: space-between; }
    .home__signin-text { flex: 1; }
    .home__foot { justify-content: center; text-align: center; gap: var(--sp-1) var(--sp-4); }
    .home__photo img { object-position: 70% 30%; }
    .home__photo::after {
        background:
            radial-gradient(600px 320px at 50% 46%, rgba(42, 26, 72, 0.42) 0%, transparent 70%),
            linear-gradient(180deg, transparent 40%, rgba(46, 36, 87, 0.86) 70%, #2E2457 100%);
    }
}

/* On a short viewport, drop the secondary points list and tighten the spacing
   so the brand, buttons and sign-in still fit on one screen without scrolling. */
@media (max-height: 740px) {
    .home .auth__points { display: none; }
    .home__body { padding-top: clamp(10px, 3vh, 32px); padding-bottom: var(--sp-2); }
    .home .auth__logo-disc { width: clamp(84px, 13vh, 120px); height: clamp(84px, 13vh, 120px); }
    .home__signin { margin-top: var(--sp-4); }
}

@media (prefers-reduced-motion: reduce) {
    .home,
    .home__content,
    .auth--intro .auth__main > div { transition: none; }
    .home__signin-btn { animation: none; }
}


/* ---------------------------------------------------------------------
   Transferring students between campuses
   --------------------------------------------------------------------- */

/* The tick column is as narrow as a tick. */
.table th.col-pick,
.table td.col-pick {
    width: 1%;
    white-space: nowrap;
    text-align: center;
}

/* The bar appears only once something is selected, so the list is not
   carrying a disabled control around all day. */
.transfer-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    background: var(--brand-light);
    border-bottom: 2px solid var(--brand);
}

.transfer-bar[hidden] { display: none; }

.transfer-bar__count {
    font-size: 0.86rem;
    color: var(--grey-700);
}

.transfer-bar .select {
    width: auto;
    min-width: 220px;
}

/* ---------------------------------------------------------------------
   Dialogue

   Used where a message has to be read and acted on rather than noticed in
   passing - a refused transfer naming the class that is missing.
   --------------------------------------------------------------------- */
.modal {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
    background: rgba(20, 16, 42, 0.55);
}

.modal[hidden] { display: none; }

.modal__panel {
    width: min(560px, 100%);
    max-height: 86vh;
    overflow-y: auto;
    background: var(--white);
    border: 1px solid var(--grey-200);
    box-shadow: var(--shadow-lg);
}

.modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-4);
    background: var(--grad-head);
    color: var(--white);
    border-bottom: 2px solid var(--red);
}

.modal__head--danger { background: var(--grad-danger); }

.modal__title {
    margin: 0;
    font-size: 0.98rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

.modal__close {
    background: none;
    border: 0;
    color: inherit;
    cursor: pointer;
    padding: 2px;
    line-height: 0;
}

.modal__body { padding: var(--sp-4); }

.modal__foot {
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    background: var(--grad-foot);
    border-top: 1px solid var(--grey-200);
}

/* One refused student, with the reasons underneath. */
.refusal {
    padding: var(--sp-3) 0;
    border-bottom: 1px solid var(--grey-200);
}

.refusal:last-of-type { border-bottom: 0; }

.refusal__student {
    font-weight: 600;
    font-size: 0.9rem;
    margin-bottom: 4px;
}

.refusal__reasons {
    margin: 0;
    padding-left: var(--sp-5);
    font-size: 0.86rem;
    color: var(--grey-700);
}

.refusal__reasons li { margin-bottom: 2px; }
