/* ==========================================================================
   School Management System - application theme
   Brand: sky blue. No purple is used anywhere in this stylesheet.
   Bootstrap 5.3 provides the grid, forms and components; this file supplies
   the school branding and adapts cleanly to light and dark color schemes.
   ========================================================================== */

:root {
    color-scheme: light;

    /* Sky blue brand ramp */
    --sky-50: #f0f9ff;
    --sky-100: #e0f2fe;
    --sky-200: #bae6fd;
    --sky-300: #7dd3fc;
    --sky-400: #38bdf8;
    --sky-500: #0ea5e9;
    --sky-600: #0284c7;
    --sky-700: #0369a1;
    --sky-800: #075985;
    --sky-900: #0c4a6e;

    /* Surfaces */
    --surface: #ffffff;
    --surface-muted: #f2f8fd;
    --border: #d5e6f3;
    --text: #0f172a;
    --text-muted: #52657a;
    --shadow-card: 0 18px 42px rgba(2, 132, 199, 0.14);
    --ring: rgba(14, 165, 233, 0.28);

    /* Text drawn on top of a primary-colored surface. The public theme can
       override it; the admin panel never does, so admin buttons are unchanged. */
    --on-primary: #ffffff;

    /* Bootstrap overrides that are evaluated at runtime */
    --bs-primary: var(--sky-500);
    --bs-primary-rgb: 14, 165, 233;

    /* Bootstrap ships unused base-palette variables for purple/indigo. The
       school brand forbids purple, so they are remapped to sky blue: nothing
       in this UI can ever resolve to a purple value. */
    --bs-purple: var(--sky-600);
    --bs-indigo: var(--sky-700);
    --bs-pink: var(--sky-500);
    --bs-link-color: var(--sky-700);
    --bs-link-color-rgb: 3, 105, 161;
    --bs-link-hover-color: var(--sky-800);
    --bs-link-hover-color-rgb: 7, 89, 133;
    --bs-focus-ring-color: var(--ring);
    --bs-body-color: var(--text);
    --bs-body-bg: var(--surface-muted);
    --bs-border-color: var(--border);
    --bs-body-font-family: system-ui, -apple-system, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
}

[data-bs-theme="dark"] {
    color-scheme: dark;

    --surface: #0f1e2b;
    --surface-muted: #08141e;
    --border: #1e3a4f;
    --text: #e6f2fb;
    --text-muted: #9db4c7;
    --shadow-card: 0 18px 42px rgba(0, 0, 0, 0.5);
    --ring: rgba(56, 189, 248, 0.35);

    --bs-primary: var(--sky-400);
    --bs-primary-rgb: 56, 189, 248;
    --bs-link-color: var(--sky-300);
    --bs-link-color-rgb: 125, 211, 252;
    --bs-link-hover-color: var(--sky-200);
    --bs-link-hover-color-rgb: 186, 230, 253;
    --bs-focus-ring-color: var(--ring);
    --bs-body-color: var(--text);
    --bs-body-bg: var(--surface-muted);
    --bs-border-color: var(--border);

    /* Keep components (inputs, alerts, dropdowns) consistent in dark mode */
    --bs-tertiary-bg: var(--surface);
    --bs-secondary-bg: var(--surface);
    --bs-emphasis-color: var(--text);

    /* The sidebar is a brand surface, so at night it keeps its sky identity
       rather than turning neutral grey - but the mid-sky bar used in Day is the
       brightest thing on the screen next to a near-black page, which is the
       opposite of what Night mode is for. The gradient override below drops
       the luminance and keeps the hue, and these tokens carry the sky accent
       that is left: white ink stays at the contrast it has in Day. */
    --sidebar-ink: rgba(226, 240, 250, 0.86);
    --sidebar-ink-strong: #ffffff;
    --sidebar-hover: rgba(125, 211, 252, 0.16);
    --sidebar-active: rgba(56, 189, 248, 0.3);
    --sidebar-line: rgba(125, 211, 252, 0.22);
}

html,
body {
    min-height: 100%;
}

body {
    background-color: var(--surface-muted);
    background-image: radial-gradient(
        1100px 560px at 50% -12%,
        rgba(56, 189, 248, 0.18),
        transparent 62%
    );
    background-repeat: no-repeat;
    color: var(--text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* --------------------------------------------------------------------------
   Brand mark
   -------------------------------------------------------------------------- */

.brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: 0.85rem;
    background: linear-gradient(160deg, var(--sky-400), var(--sky-600));
    color: #ffffff;
    font-size: 1.35rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    box-shadow: 0 10px 20px rgba(2, 132, 199, 0.32);
}

.brand-mark--sm {
    width: 2rem;
    height: 2rem;
    border-radius: 0.6rem;
    font-size: 0.95rem;
    background: rgba(255, 255, 255, 0.16);
    border: 1px solid rgba(255, 255, 255, 0.38);
    box-shadow: none;
}

/* --- the uploaded logo, in the administration -----------------------------

   Wherever the panel shows the school's mark, the configured logo stands in for
   the letter tile. These give it a box the same size as the tile it replaces, so
   a wide logo shrinks to fit rather than stretching the header, and a tall one
   cannot push the layout around.

   The public site has its own sizing (.site-brand-logo in public.css), scoped to
   .public-page because the public bar's rhythm is taller. Sizing is the only
   thing that differs between the two: which image to show is decided once, in
   partials/brand-mark.                                                            */

.sidebar-brand-logo,
.auth-brand-logo {
    display: block;
    flex: 0 0 auto;
    width: auto;
    object-fit: contain;
    object-position: center;
}

.sidebar-brand-logo {
    height: 2rem;
    max-width: 7rem;
}

.auth-brand-logo {
    height: 3rem;
    max-width: min(9rem, 60vw);
    margin-inline: auto;
}

/* --------------------------------------------------------------------------
   Uploaded images - one fitting strategy

   Every uploaded image is shown inside a FRAME. The frame owns the size and
   clips the overflow; the image only ever fits inside it. Keeping those two
   jobs apart is what stops an image from being able to change the layout it
   lands in: a 6000px photograph and a 40px icon occupy exactly the same box.

   A frame always has a definite height or an aspect ratio, so an image can
   never contribute height to the page. The image inside fills that box with
   object-fit, which scales it without ever changing its proportions - the two
   things a bare <img> cannot promise on its own.

   Three fit modes, chosen per context rather than one rule applied to every
   image. A logo, a gallery photograph and a hero backdrop genuinely want
   different treatment, so each one states which it is:

     --contain   the whole image is visible, nothing is cropped. Logos,
                 gallery slides and admin previews: an administrator should
                 always see all of what they uploaded, whatever shape it is.
     --cover     the box is filled and the excess is cropped. Photographic
                 backdrops, where a consistent shape across images matters more
                 than showing every pixel.
     (neither)   the image keeps its own proportions within max bounds. Used
                 where the frame must otherwise stay out of the way.

   A frame can also be given a fixed shape (--16x9, --4x3) or a height, and
   --fill makes the image fill it. Without --fill an image keeps its intrinsic
   ratio, which is what an auto-height preview such as the logo frame wants.

   Two ways in, for the two situations that actually occur:

     <div class="img-frame img-frame--fill img-frame--cover img-frame--16x9">
       <img …>
     </div>

     <img class="img-contain" …>     an image that is its own frame, sized by
                                     its own class (position: absolute, a fixed
                                     height, and so on).

   Usage note: object-fit needs the image to have a definite box. A frame
   that only sets min-height does not give it one, which is why every frame
   below is written with a height or an aspect ratio.
   -------------------------------------------------------------------------- */

.img-frame {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    min-width: 0;
    background: var(--surface-muted);
}

/* Bounded, but free to take its own shape. */
.img-frame > img {
    display: block;
    max-width: 100%;
    max-height: 100%;
}

/* Fills a frame that has a definite height or ratio. Declared after the rule
   above so it wins at equal specificity. */
.img-frame--fill > img {
    width: 100%;
    height: 100%;
}

/* contain - the whole image is visible. Never cropped, never stretched.

   Every context that uses this is one where somebody is judging what was
   uploaded: an administrator checking a photograph, a gallery visitor looking
   at the picture the school chose. Cropping hides part of the answer, so the
   whole image is always shown and the frame takes the letterboxing. That
   letterboxing is why these frames carry a background colour - the gap is part
   of the design, not an accident. */
.img-frame--contain > img,
.img-contain {
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    object-position: center;
}

/* cover - fills the box and crops the excess.

   Only for photographic backdrops, where text sits on top and a consistent
   shape across images matters more than showing every pixel. Nothing that
   carries information a visitor needs is ever fitted this way. */
.img-frame--cover > img,
.img-cover {
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: cover;
    object-position: center;
}

/* Fixed shapes. A frame that reserves its space cannot jump when a carousel or
   a list moves between images of different dimensions. */
.img-frame--16x9 { aspect-ratio: 16 / 9; }
.img-frame--4x3 { aspect-ratio: 4 / 3; }

/* --------------------------------------------------------------------------
   Authentication page
   -------------------------------------------------------------------------- */

.auth-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 2.5rem 1rem;
}

.auth-card {
    width: 100%;
    max-width: 26rem;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-top: 4px solid var(--sky-500);
    border-radius: 1rem;
    padding: 2.25rem 2rem 1.75rem;
    box-shadow: var(--shadow-card);
}

.brand-block {
    text-align: center;
    margin-bottom: 1.75rem;
}

.brand-title {
    margin: 0.95rem 0 0.2rem;
    font-size: 1.15rem;
    font-weight: 650;
    color: var(--text);
    line-height: 1.35;
}

.brand-subtitle {
    margin: 0;
    font-size: 0.9rem;
    color: var(--text-muted);
}

.auth-footnote {
    margin: 1.5rem 0 0;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
    font-size: 0.78rem;
    text-align: center;
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */

.form-label {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 0.35rem;
}

.form-control {
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    color: var(--text);
    padding: 0.65rem 0.8rem;
    transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

/* Selects match the inputs, but keep the extra right padding Bootstrap reserves
   for its chevron and only override background-color so the icon survives. */
.form-select {
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    color: var(--text);
    padding: 0.65rem 2.25rem 0.65rem 0.8rem;
    transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.form-control::placeholder {
    color: var(--text-muted);
    opacity: 0.75;
}

.form-control:focus,
.form-select:focus {
    background-color: var(--surface);
    border-color: var(--sky-400);
    color: var(--text);
    box-shadow: 0 0 0 0.22rem var(--ring);
}

.form-control.is-invalid,
.form-select.is-invalid {
    border-color: #dc3545;
}

.form-control.is-invalid:focus,
.form-select.is-invalid:focus {
    border-color: #dc3545;
    box-shadow: 0 0 0 0.22rem rgba(220, 53, 69, 0.25);
}

.invalid-feedback {
    font-size: 0.82rem;
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.btn {
    border-radius: 0.6rem;
    font-weight: 600;
}

/* White on the brand blue has to clear WCAG AA, and the ramp's two lightest
   plausible steps do not: white on sky-500 measures 2.77:1 and on sky-600
   4.10:1, where normal-size text needs 4.5:1. sky-700 is the first step that
   passes, at 5.93:1.
 *
   It is also where the public stylesheet already puts every surface that
   --on-primary is drawn on: the hero gradient, the footer and the skip link all
   start at sky-700 or deeper. So this is not a new colour invented to satisfy a
   checker - it is the brand's existing "text sits on this" blue, and the admin
   panel now agrees with the public site instead of contradicting it.
 *
   Hover and active step further DOWN the ramp rather than up it. The opposite
   direction is the intuitive one for a hover, but brightening a background that
   carries white text can only lose contrast - it would have dropped to 4.10:1
   on hover and 2.77:1 while pressed. Darkening keeps the label readable at the
   moment the user is actually pressing the control. */
.btn-sky {
    background-color: var(--sky-700);
    border: 1px solid var(--sky-700);
    color: var(--on-primary);
    padding: 0.65rem 1rem;
    transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
}

.btn-sky:hover,
.btn-sky:focus {
    background-color: var(--sky-800);
    border-color: var(--sky-800);
    color: #ffffff;
}

.btn-sky:active {
    background-color: var(--sky-900);
    border-color: var(--sky-900);
    color: #ffffff;
}

.btn-sky:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.22rem var(--ring);
}

.btn-sky:disabled {
    opacity: 0.7;
}

/* --------------------------------------------------------------------------
   Authenticated application shell - sidebar navigation
   -------------------------------------------------------------------------- */

:root {
    --sidebar-width: 17.5rem;
    --sidebar-ink: rgba(255, 255, 255, 0.84);
    --sidebar-ink-strong: #ffffff;
    --sidebar-hover: rgba(255, 255, 255, 0.12);
    --sidebar-active: rgba(255, 255, 255, 0.2);
    --sidebar-line: rgba(255, 255, 255, 0.16);
}

.app-sidebar {
    background: linear-gradient(180deg, var(--sky-700) 0%, var(--sky-800) 55%, var(--sky-900) 100%);
    color: var(--sidebar-ink-strong);
    display: flex;
    flex-direction: column;
    padding: 0.85rem 0.75rem 1rem;
    overflow-x: hidden;
}

.sidebar-head {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    min-width: 0;
    padding: 0.45rem 0.5rem 0.85rem;
    border-bottom: 1px solid var(--sidebar-line);
}

.sidebar-brand-text {
    min-width: 0;
    font-size: 0.95rem;
    font-weight: 650;
    line-height: 1.25;
    color: var(--sidebar-ink-strong);
    overflow-wrap: anywhere;
}

.sidebar-close {
    display: none;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    margin-left: auto;
    padding: 0;
    border: 1px solid var(--sidebar-line);
    border-radius: 0.6rem;
    background: transparent;
    color: var(--sidebar-ink-strong);
    cursor: pointer;
}

.sidebar-close:hover {
    background: var(--sidebar-hover);
}

.sidebar-nav {
    flex: 1 1 auto;
    min-height: 0;
    margin-top: 0.6rem;
    overflow-x: hidden;
    overflow-y: auto;
}

.sidebar-list,
.sidebar-sublist {
    margin: 0;
    padding: 0;
    list-style: none;
}

.sidebar-list {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

/* Top-level link and group toggle share one appearance ------------------- */

.sidebar-link,
.sidebar-summary {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 100%;
    padding: 0.6rem 0.7rem;
    border: 0;
    border-radius: 0.6rem;
    background: transparent;
    color: var(--sidebar-ink);
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.3;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out;
}

.sidebar-link:hover,
.sidebar-summary:hover {
    background: var(--sidebar-hover);
    color: var(--sidebar-ink-strong);
}

.sidebar-link:focus-visible,
.sidebar-summary:focus-visible,
.sidebar-sublink:focus-visible,
.sidebar-signout:focus-visible,
.sidebar-close:focus-visible,
.sidebar-toggle:focus-visible {
    outline: 2px solid #ffffff;
    outline-offset: 2px;
}

.sidebar-link.is-active,
.sidebar-summary.is-active {
    background: var(--sidebar-active);
    color: var(--sidebar-ink-strong);
}

.sidebar-link.is-active {
    box-shadow: inset 3px 0 0 0 #ffffff;
}

.sidebar-label {
    min-width: 0;
    overflow-wrap: anywhere;
}

.sidebar-icon {
    flex: 0 0 auto;
    opacity: 0.92;
}

/* The menu and close icons are emitted with their own names rather than
   "sidebar-icon", so without this they were the only two icons in the panel with
   no rule at all - sized by their width/height attributes and nothing else. */
.sidebar-close-icon,
.sidebar-toggle-icon {
    display: block;
    flex: 0 0 auto;
}

.sidebar-chevron {
    flex: 0 0 auto;
    margin-left: auto;
    opacity: 0.75;
    transition: transform 0.18s ease-in-out;
}

/* <details> is used so the groups work with no JavaScript at all --------- */

.sidebar-group > summary {
    list-style: none;
}

.sidebar-group > summary::-webkit-details-marker {
    display: none;
}

.sidebar-group > summary::marker {
    content: "";
}

.sidebar-group[open] > .sidebar-summary > .sidebar-chevron {
    transform: rotate(90deg);
}

.sidebar-sublist {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    margin: 0.15rem 0 0.4rem 1.05rem;
    padding-left: 0.85rem;
    border-left: 1px solid var(--sidebar-line);
    animation: sidebar-sublist-in 0.18s ease-out;
}

@keyframes sidebar-sublist-in {
    from {
        opacity: 0;
        transform: translateY(-0.2rem);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.sidebar-sublink {
    display: block;
    padding: 0.42rem 0.65rem;
    border-radius: 0.5rem;
    color: var(--sidebar-ink);
    font-size: 0.855rem;
    font-weight: 500;
    text-decoration: none;
    /* A long submenu name wraps rather than running past the edge of the drawer
       or forcing the sidebar to scroll sideways. */
    overflow-wrap: anywhere;
    transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out;
}

.sidebar-sublink:hover {
    background: var(--sidebar-hover);
    color: var(--sidebar-ink-strong);
}

.sidebar-sublink.is-active {
    background: var(--sidebar-active);
    color: var(--sidebar-ink-strong);
    font-weight: 700;
}

.sidebar-foot {
    margin-top: 0.4rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--sidebar-line);
}

.sidebar-logout {
    margin: 0;
}

.sidebar-signout {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 100%;
    padding: 0.6rem 0.7rem;
    border: 1px solid var(--sidebar-line);
    border-radius: 0.6rem;
    background: transparent;
    color: var(--sidebar-ink-strong);
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.15s ease-in-out;
}

.sidebar-signout:hover {
    background: var(--sidebar-hover);
}

/* Night mode: the same sky hue, dropped to a deep ramp so the sidebar stops
   being the brightest surface on a dark page. Deliberately declared before the
   small-screen drawer rule, which owns the drop shadow the drawer needs and
   must keep winning there. */
[data-bs-theme="dark"] .app-sidebar {
    background: linear-gradient(180deg, #0d3b58 0%, #0a2c44 55%, #061c2c 100%);
    box-shadow: inset -1px 0 0 0 rgba(125, 211, 252, 0.16);
}

/* Day / Night switch, in the sidebar footer ------------------------------ */

.theme-toggle {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: 100%;
    margin-bottom: 0.4rem;
    padding: 0.6rem 0.7rem;
    border: 1px solid transparent;
    border-radius: 0.6rem;
    background: transparent;
    color: var(--sidebar-ink);
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out;
}

.theme-toggle:hover {
    background: var(--sidebar-hover);
    color: var(--sidebar-ink-strong);
}

.theme-toggle:focus-visible {
    outline: 2px solid #ffffff;
    outline-offset: 2px;
}

/* The switch itself. Its position is driven entirely by the mode attribute on
   <html>, which nav-init.js sets before the first paint - so the track and knob
   are drawn correctly on the very first frame, with no script having touched
   this element and no dependence on when the deferred scripts run. */
.theme-toggle-track {
    position: relative;
    flex: 0 0 auto;
    width: 2.9rem;
    height: 1.5rem;
    border: 1px solid var(--sidebar-line);
    border-radius: 999px;
    background: rgba(8, 47, 73, 0.45);
    transition: background-color 0.18s ease-in-out, border-color 0.18s ease-in-out;
}

.theme-toggle-knob {
    position: absolute;
    top: 50%;
    left: 0.18rem;
    width: 1.05rem;
    height: 1.05rem;
    border-radius: 50%;
    background: #ffffff;
    transform: translateY(-50%);
    transition: transform 0.18s ease-in-out;
}

[data-bs-theme="dark"] .theme-toggle-track {
    border-color: rgba(125, 211, 252, 0.5);
    background: rgba(56, 189, 248, 0.35);
}

/* Travel is the track's inner width less the knob: 2.9rem - 0.18rem at each
   end - 1.05rem - 2px of border. 1.3rem leaves the knob a hair clear of the
   end rather than flush against it. */
[data-bs-theme="dark"] .theme-toggle-knob {
    transform: translateY(-50%) translateX(1.3rem);
}

.theme-toggle-label {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
}

.theme-toggle-text {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Sized down from the 20px the shared icon helper emits, which is a
   presentation attribute and so yields to this. */
.theme-toggle-moon {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    opacity: 0.85;
}

/* Small-screen top bar carrying the menu button -------------------------- */

/* Top bar carrying the page title and the account menu -------------------- */

/* This used to be a small-screen-only bar, shown just while the navigation was
   a drawer. It is now the home of the account menu, which is the only route to
   Profile Settings, so it is present at every size - and present from the
   stylesheet rather than from [data-nav-ready], so the menu is still reachable
   when scripting is off. The hamburger inside it is what stays conditional: it
   exists to open the drawer, and there is no drawer on a wide screen. */
.app-topbar {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.55rem 0.9rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.app-topbar-text {
    /* Grows, so the account menu is pushed to the far end of the bar whatever
       length the page title is. */
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.95rem;
    font-weight: 650;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A bare icon, not a slab. An outlined pale box next to the page title reads as
   a secondary button sitting in the bar rather than as the menu control, and on
   a phone that box is one of the largest pieces of chrome on the screen. The tap
   target still measures a full 44px. */
.sidebar-toggle {
    /* Hidden by default and shown only where the drawer exists - see the
       small-screen block below. On a wide screen the sidebar is always on
       screen, so a menu button would open nothing. */
    display: none;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    border: 0;
    border-radius: 0.6rem;
    background: transparent;
    color: var(--sky-700);
    cursor: pointer;
    transition: background-color 0.15s ease-in-out;
}

.sidebar-toggle:hover {
    background: var(--sky-50);
}

.sidebar-scrim:not([hidden]) {
    position: fixed;
    inset: 0;
    z-index: 1040;
    background: rgba(8, 47, 73, 0.5);
}

body.nav-drawer-open {
    overflow: hidden;
}

/* Drawer, once nav-init.js has flagged that scripting is available -------- */

@media (max-width: 991.98px) {
    [data-nav-ready] .app-topbar {
        position: sticky;
        top: 0;
        z-index: 1045;
    }

    /* The drawer, and therefore the button that opens it, only exists once
       nav-init.js has flagged that scripting is available. */
    [data-nav-ready] .sidebar-toggle {
        display: inline-flex;
    }

    [data-nav-ready] .sidebar-close {
        display: inline-flex;
    }

    [data-nav-ready] .app-sidebar {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 1050;
        /* 20rem is the target width; the percentage is the guard for the
           narrowest phones. 88vw rather than 86vw so that even a 320px screen
           gets a drawer at the low end of the intended 280-320px range, while
           still leaving a strip of the page showing to tap. On a fixed element
           this can never extend the document width, so it cannot introduce
           sideways scrolling. */
        width: min(20rem, 88vw);
        transform: translateX(-100%);
        /* visibility keeps the closed drawer out of the keyboard tab order. */
        visibility: hidden;
        transition: transform 0.22s ease-in-out, visibility 0.22s ease-in-out;
        box-shadow: 0 18px 40px rgba(8, 47, 73, 0.35);
    }

    [data-nav-ready] .app-sidebar.is-open {
        transform: none;
        visibility: visible;
    }
}

/* Desktop: the sidebar sits in its own column and the content flows beside it */

@media (min-width: 992px) {
    body.dashboard-page {
        display: grid;
        grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
        /* Two rows: the bar spans the top of the content column, the page fills
           what is left. The sidebar spans both, so it keeps its full height. */
        grid-template-rows: auto minmax(0, 1fr);
        grid-template-areas:
            "sidebar topbar"
            "sidebar main";
        align-items: stretch;
        min-height: 100vh;
    }

    .app-sidebar {
        grid-area: sidebar;
        position: sticky;
        top: 0;
        height: 100vh;
    }

    .app-topbar {
        grid-area: topbar;
        position: sticky;
        top: 0;
        z-index: 1045;
    }

    .app-main {
        grid-area: main;
        min-width: 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .sidebar-sublist {
        animation: none;
    }

    /* The switch is included: its knob slides, which is motion. The colour
       transitions on the toggle itself are left alone - a fade is not motion,
       and removing it would only make the control feel less responsive. */
    .sidebar-chevron,
    .theme-toggle-knob,
    [data-nav-ready] .app-sidebar {
        transition: none;
    }
}

.app-main {
    padding: 2.5rem 1.25rem 3rem;
}

.app-container {
    max-width: 72rem;
    margin: 0 auto;
}

.page-heading {
    margin: 0 0 0.35rem;
    font-size: 1.6rem;
    font-weight: 700;
    color: var(--text);
}

.page-subheading {
    margin: 0 0 1.75rem;
    font-size: 0.95rem;
    color: var(--text-muted);
}

.empty-note {
    margin: 0;
    padding: 0.85rem 1rem;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--sky-400);
    border-radius: 0.6rem;
    font-size: 0.9rem;
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Alerts
   -------------------------------------------------------------------------- */

.alert {
    border-radius: 0.6rem;
    font-size: 0.9rem;
}

/* --------------------------------------------------------------------------
   Error pages
   -------------------------------------------------------------------------- */

.error-page {
    display: grid;
    place-items: center;
    min-height: 100vh;
    padding: 2.5rem 1rem;
}

.error-card {
    max-width: 34rem;
    width: 100%;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-top: 4px solid var(--sky-500);
    border-radius: 1rem;
    padding: 2rem;
    box-shadow: var(--shadow-card);
    text-align: center;
}

.error-code {
    margin: 0;
    font-size: 2.5rem;
    font-weight: 700;
    color: var(--sky-500);
    line-height: 1;
}

.error-card h1 {
    margin: 0.5rem 0 0.5rem;
    font-size: 1.2rem;
    color: var(--text);
}

.error-card p {
    margin: 0 0 0.5rem;
    color: var(--text-muted);
}

.error-ref {
    font-size: 0.85rem;
}

.error-ref code {
    background-color: var(--sky-100);
    color: var(--sky-800);
    padding: 0.15rem 0.4rem;
    border-radius: 0.35rem;
}

[data-bs-theme="dark"] .error-ref code {
    background-color: rgba(14, 165, 233, 0.2);
    color: var(--sky-200);
}

/* The error page's primary action. Same surface as .btn-sky and for the same
   reason - it carries white text, so it starts at the first ramp step that
   clears AA - and the same hover step. It is a link, so it has no :active. */
.btn-brand {
    display: inline-block;
    margin-top: 1rem;
    padding: 0.55rem 1.15rem;
    border-radius: 0.6rem;
    background-color: var(--sky-700);
    color: #ffffff;
    font-weight: 600;
    text-decoration: none;
}

.btn-brand:hover,
.btn-brand:focus {
    background-color: var(--sky-800);
    color: #ffffff;
}

.error-detail {
    margin: 1.25rem 0 0;
    padding: 0.9rem;
    background-color: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    font-size: 0.78rem;
    color: var(--text-muted);
    text-align: left;
    white-space: pre-wrap;
    overflow-x: auto;
}

/* --------------------------------------------------------------------------
   Stage 2: teacher management
   -------------------------------------------------------------------------- */

/* Page toolbar ------------------------------------------------------------ */

.page-toolbar {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1.5rem;
}

.page-toolbar .page-subheading {
    margin-bottom: 0;
}

/* Dashboard statistic card ------------------------------------------------ */

.stat-card {
    display: flex;
    align-items: center;
    gap: 1rem;
    height: 100%;
    padding: 1.25rem 1.35rem;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-left: 4px solid var(--sky-500);
    border-radius: 0.85rem;
    box-shadow: 0 6px 18px rgba(2, 132, 199, 0.08);
    color: inherit;
    text-decoration: none;
    transition: transform 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

a.stat-card:hover,
a.stat-card:focus {
    color: inherit;
    transform: translateY(-2px);
    box-shadow: 0 14px 28px rgba(2, 132, 199, 0.18);
}

a.stat-card:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.22rem var(--ring);
}

.stat-card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 3rem;
    height: 3rem;
    border-radius: 0.75rem;
    background: linear-gradient(160deg, var(--sky-400), var(--sky-600));
    color: #ffffff;
}

.stat-card-icon svg {
    width: 1.5rem;
    height: 1.5rem;
}

.stat-card-body {
    display: block;
    min-width: 0;
}

.stat-card-label {
    display: block;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.stat-card-value {
    display: block;
    margin-top: 0.15rem;
    font-size: 1.9rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--text);
}

/* Data table -------------------------------------------------------------- */

.table-wrap {
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.85rem;
    box-shadow: 0 6px 18px rgba(2, 132, 199, 0.08);
    overflow: hidden;
}

.app-table {
    --bs-table-bg: transparent;
    --bs-table-color: var(--text);
    --bs-table-border-color: var(--border);
    margin-bottom: 0;
    color: var(--text);
}

.app-table > :not(caption) > * > * {
    background-color: transparent;
    padding: 0.75rem 1rem;
}

.app-table thead th {
    background-color: var(--sky-50);
    color: var(--sky-800);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    border-bottom: 1px solid var(--border);
}

.app-table tbody td {
    border-color: var(--border);
    font-size: 0.92rem;
}

.app-table tbody tr:last-child td {
    border-bottom: 0;
}

.app-table tbody tr:hover td {
    background-color: rgba(14, 165, 233, 0.06);
}

.cell-muted {
    color: var(--text-muted);
}

[data-bs-theme="dark"] .app-table thead th {
    background-color: rgba(14, 165, 233, 0.14);
    color: var(--sky-200);
}

.row-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
    flex-wrap: wrap;
}

/* Secondary buttons ------------------------------------------------------- */

.btn-outline-sky {
    background-color: transparent;
    border: 1px solid var(--sky-500);
    color: var(--sky-700);
}

.btn-outline-sky:hover,
.btn-outline-sky:focus {
    background-color: var(--sky-700);
    border-color: var(--sky-700);
    color: #ffffff;
}

.btn-outline-sky:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.22rem var(--ring);
}

.btn-soft-danger {
    background-color: transparent;
    border: 1px solid #dc3545;
    color: #b02a37;
}

.btn-soft-danger:hover,
.btn-soft-danger:focus {
    background-color: #dc3545;
    border-color: #dc3545;
    color: #ffffff;
}

.btn-soft-danger:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.22rem rgba(220, 53, 69, 0.3);
}

[data-bs-theme="dark"] .btn-outline-sky {
    color: var(--sky-300);
}

[data-bs-theme="dark"] .btn-soft-danger {
    color: #f1aeb5;
    border-color: #f1aeb5;
}

[data-bs-theme="dark"] .btn-soft-danger:hover,
[data-bs-theme="dark"] .btn-soft-danger:focus {
    background-color: #dc3545;
    border-color: #dc3545;
    color: #ffffff;
}

/* Form card --------------------------------------------------------------- */

.form-card {
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.85rem;
    padding: 1.5rem;
    box-shadow: 0 6px 18px rgba(2, 132, 199, 0.08);
}

.form-actions {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--border);
}

.confirm-text {
    margin: 0 0 1.25rem;
    font-size: 1rem;
    color: var(--text);
}

.app-container--narrow {
    max-width: 46rem;
}

@media (max-width: 575.98px) {
    .page-toolbar .btn {
        width: 100%;
    }

    .row-actions .btn {
        flex: 1 1 auto;
    }
}

/* --------------------------------------------------------------------------
   Stage 6: school fee management
   -------------------------------------------------------------------------- */

/* Section headings and notes ---------------------------------------------- */

.section-heading {
    margin: 0 0 0.75rem;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text);
}

.section-heading--spaced {
    margin-top: 2.25rem;
}

.section-note {
    margin: 0 0 1.25rem;
    font-size: 0.85rem;
    color: var(--text-muted);
    max-width: 60rem;
}

.section-rule {
    margin: 1.5rem 0;
    border-color: var(--border);
    opacity: 1;
}

.form-hint {
    margin: 0 0 1rem;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.result-summary {
    margin: 0 0 1rem;
    font-size: 0.9rem;
    color: var(--text-muted);
}

/* Section tabs (shared by the fee and student sub-navigations) ------------- */

.app-tabs,
.fee-tabs {
    margin-bottom: 1.25rem;
}

.app-tabs ul,
.fee-tabs ul {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.app-tab,
.fee-tab {
    display: inline-block;
    padding: 0.45rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    background-color: var(--surface);
    color: var(--sky-700);
    font-size: 0.88rem;
    font-weight: 600;
    text-decoration: none;
    transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out;
}

.app-tab:hover,
.app-tab:focus,
.fee-tab:hover,
.fee-tab:focus {
    background-color: var(--sky-50);
    color: var(--sky-800);
}

.app-tab.is-active,
.fee-tab.is-active {
    background-color: var(--sky-700);
    border-color: var(--sky-700);
    color: #ffffff;
}

.app-tab:focus-visible,
.fee-tab:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.22rem var(--ring);
}

[data-bs-theme="dark"] .app-tab,
[data-bs-theme="dark"] .fee-tab {
    color: var(--sky-300);
}

[data-bs-theme="dark"] .app-tab:hover,
[data-bs-theme="dark"] .app-tab:focus,
[data-bs-theme="dark"] .fee-tab:hover,
[data-bs-theme="dark"] .fee-tab:focus {
    background-color: rgba(56, 189, 248, 0.12);
    color: var(--sky-200);
}

/* Filter bars ------------------------------------------------------------- */

.filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.75rem;
}

.filter-field {
    flex: 1 1 10rem;
    min-width: 0;
}

.filter-field--grow {
    flex: 2 1 16rem;
}

.filter-actions {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/* Money ------------------------------------------------------------------- */

.money {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.money-input {
    max-width: 9rem;
}

.stat-card-value.money {
    font-size: 1.5rem;
}

/* Status badges ----------------------------------------------------------- */

.badge {
    display: inline-block;
    padding: 0.3rem 0.55rem;
    border-radius: 0.45rem;
    font-size: 0.75rem;
    font-weight: 700;
    line-height: 1.2;
    white-space: nowrap;
}

.badge-success {
    background-color: rgba(25, 135, 84, 0.14);
    border: 1px solid rgba(25, 135, 84, 0.32);
    color: #146c43;
}

.badge-warning {
    background-color: rgba(255, 193, 7, 0.18);
    border: 1px solid rgba(255, 193, 7, 0.42);
    color: #8a6100;
}

.badge-danger {
    background-color: rgba(220, 53, 69, 0.13);
    border: 1px solid rgba(220, 53, 69, 0.32);
    color: #b02a37;
}

.badge-muted {
    background-color: var(--surface-muted);
    border: 1px solid var(--border);
    color: var(--text-muted);
}

[data-bs-theme="dark"] .badge-success {
    color: #75d1a4;
}

[data-bs-theme="dark"] .badge-warning {
    color: #ffd76e;
}

[data-bs-theme="dark"] .badge-danger {
    color: #f1aeb5;
}

/* Night mode: the remaining controls that were still written for a light page.

   Each of these set either a dark sky text colour or a near-white surface. On
   the night background the dark sky text falls well below a readable contrast
   ratio, and the near-white surfaces read as a bright flash rather than a
   hover. They are corrected here rather than by re-colouring the Day rules, so
   the light theme is untouched. */
[data-bs-theme="dark"] .sidebar-toggle {
    color: var(--sky-300);
}

[data-bs-theme="dark"] .sidebar-toggle:hover {
    background-color: rgba(56, 189, 248, 0.16);
}

[data-bs-theme="dark"] .wysiwyg-btn:hover {
    background-color: rgba(56, 189, 248, 0.16);
}

[data-bs-theme="dark"] .wysiwyg-btn.is-active {
    background-color: rgba(56, 189, 248, 0.28);
    border-color: var(--sky-400);
    color: var(--sky-100);
}

/* A 6% sky wash is visible on white and invisible on near-black, so the row
   highlight needs a lighter tint to read as a highlight at all. */
[data-bs-theme="dark"] .app-table tbody tr:hover td {
    background-color: rgba(56, 189, 248, 0.1);
}

/* Card shadows are a sky-blue glow in Day. On a dark page a coloured glow
   reads as haze rather than depth, so night uses a plain dark shadow. */
[data-bs-theme="dark"] .form-card,
[data-bs-theme="dark"] .stat-card {
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.42);
}

[data-bs-theme="dark"] a.stat-card:hover,
[data-bs-theme="dark"] a.stat-card:focus {
    box-shadow: 0 14px 28px rgba(0, 0, 0, 0.55);
}

/* Selected student -------------------------------------------------------- */

.student-card {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.25rem;
    padding: 1rem 1.15rem;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-left: 4px solid var(--sky-500);
    border-radius: 0.85rem;
    box-shadow: 0 6px 18px rgba(2, 132, 199, 0.08);
}

.student-card-name {
    margin: 0 0 0.2rem;
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text);
}

.student-card-meta {
    margin: 0;
    font-size: 0.88rem;
    color: var(--text-muted);
}

.student-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/* Fee context panel ------------------------------------------------------- */

.context-panel {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: 0.75rem;
    padding: 0.9rem;
    background-color: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: 0.6rem;
}

.context-item {
    min-width: 0;
}

.context-label {
    display: block;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.context-value {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.98rem;
    font-weight: 600;
    color: var(--text);
    overflow-wrap: anywhere;
}

/* Fee configuration grid -------------------------------------------------- */

.fee-class {
    margin-bottom: 1.25rem;
}

.fee-class-title {
    margin: 0 0 0.5rem;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text);
}

.fee-term {
    white-space: nowrap;
}

/* Compact summary tiles --------------------------------------------------- */

.summary-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
    gap: 0.75rem;
    margin-bottom: 1.5rem;
}

.stat-tile {
    padding: 0.75rem 0.9rem;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--sky-400);
    border-radius: 0.6rem;
}

.stat-tile-label {
    display: block;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.stat-tile-value {
    display: block;
    margin-top: 0.15rem;
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--text);
}

/* Promotion page ---------------------------------------------------------- */

.page-toolbar-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.cell-check {
    width: 2.75rem;
    text-align: center;
}

.cell-check .form-check-input {
    margin: 0;
}

.promotion-route {
    margin: 0;
    font-size: 1rem;
    color: var(--text);
}

.confirm-list {
    margin: 0;
    padding: 0.75rem 0.9rem 0.75rem 1.9rem;
    max-height: 16rem;
    overflow-y: auto;
    background-color: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    font-size: 0.9rem;
    color: var(--text);
}

.confirm-list li {
    margin-bottom: 0.2rem;
}

/* Academic session indicator ---------------------------------------------- */

.session-banner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 1.5rem;
    padding: 0.85rem 1.1rem;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-left: 4px solid var(--sky-500);
    border-radius: 0.85rem;
    box-shadow: 0 6px 18px rgba(2, 132, 199, 0.08);
}

.session-banner-label {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.session-banner-value {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
}

/* Enrollment ------------------------------------------------------------- */

.form-section-title {
    margin: 0.25rem 0 1rem;
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--sky-700);
}

[data-bs-theme="dark"] .form-section-title {
    color: var(--sky-300);
}

.form-section-title + .row {
    margin-bottom: 1.5rem;
}

.app-table tbody tr.is-selected td,
.app-table tbody tr.is-selected th {
    background-color: rgba(14, 165, 233, 0.08);
}

.app-table tfoot td,
.app-table tfoot th {
    border-top: 2px solid var(--border);
    font-weight: 700;
    color: var(--text);
}

/* Per-row section move control on the roster ------------------------------ */

.section-move-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.35rem;
}

.section-move-form .form-select {
    width: auto;
    min-width: 8rem;
    padding: 0.3rem 2rem 0.3rem 0.6rem;
    font-size: 0.85rem;
}

@media (max-width: 575.98px) {
    .section-move-form {
        justify-content: stretch;
    }

    .section-move-form .form-select {
        flex: 1 1 100%;
        width: 100%;
    }
}

/* --------------------------------------------------------------------------
   Setup wizard (public/setup.php)
   -------------------------------------------------------------------------- */

.setup-page {
    background-color: var(--bs-body-bg, var(--surface-muted));
}

.setup-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 2.5rem 1rem;
}

.setup-card {
    width: 100%;
    max-width: 36rem;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-top: 4px solid var(--sky-500);
    border-radius: 1rem;
    padding: 2rem 1.75rem 1.5rem;
    box-shadow: var(--shadow-card);
}

.setup-title {
    margin: 0.75rem 0 0.2rem;
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--text);
    text-align: center;
}

.setup-lede {
    margin: 0 0 1rem;
    font-size: 0.92rem;
    color: var(--text-muted);
}

.setup-steps {
    list-style: none;
    margin: 0 0 0.5rem;
    padding: 0;
    display: grid;
    gap: 0.6rem;
}

.setup-step {
    display: flex;
    align-items: flex-start;
    gap: 0.7rem;
    font-size: 0.88rem;
    color: var(--text-muted);
}

.setup-step-marker {
    flex: 0 0 auto;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 0.8rem;
    font-weight: 700;
    background-color: var(--surface-muted);
    border: 1px solid var(--border);
    color: var(--text-muted);
}

.setup-step.is-done .setup-step-marker {
    background-color: #10b981;
    border-color: #10b981;
    color: #ffffff;
}

.setup-step-text {
    display: flex;
    flex-direction: column;
    line-height: 1.45;
}

.setup-step-text strong {
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--text);
}

.setup-block {
    border-top: 1px solid var(--border);
    margin-top: 1.5rem;
    padding-top: 1.5rem;
}

.setup-heading {
    margin: 0 0 0.5rem;
    font-size: 1.02rem;
    font-weight: 700;
    color: var(--sky-800);
}

[data-bs-theme="dark"] .setup-heading {
    color: var(--sky-300);
}

.setup-field {
    margin-bottom: 0.9rem;
}

.setup-field label {
    display: block;
    margin-bottom: 0.3rem;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text);
}

.setup-field input,
.setup-field select {
    width: 100%;
    padding: 0.6rem 0.75rem;
    font-size: 0.95rem;
    color: var(--text);
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.55rem;
}

.setup-field input:focus,
.setup-field select:focus {
    outline: none;
    border-color: var(--sky-500);
    box-shadow: 0 0 0 0.2rem var(--ring);
}

.setup-note {
    border: 1px solid var(--border);
    border-left-width: 4px;
    border-radius: 0.55rem;
    padding: 0.75rem 0.9rem;
    margin-bottom: 1rem;
    font-size: 0.85rem;
}

.setup-note p {
    margin: 0;
}

.setup-note--warn {
    background-color: #fffbeb;
    border-left-color: #f59e0b;
    border-color: #fde68a;
    color: #78350f;
}

[data-bs-theme="dark"] .setup-note--warn {
    background-color: rgba(245, 158, 11, 0.12);
    border-color: rgba(245, 158, 11, 0.35);
    color: #fcd34d;
}

.setup-hint {
    display: block;
    margin-top: 0.3rem;
    font-size: 0.78rem;
    color: var(--text-muted);
}

.setup-optional {
    font-weight: 400;
    color: var(--text-muted);
}

.setup-check {
    list-style: none;
    margin: 0 0 1rem;
    padding: 0;
    display: grid;
    gap: 0.35rem;
}

.setup-check li {
    position: relative;
    padding-left: 1.5rem;
    font-size: 0.85rem;
    color: var(--text);
}

.setup-check li::before {
    position: absolute;
    left: 0;
    top: 0;
    font-weight: 700;
}

.setup-check li.is-ok::before {
    content: "\2713";
    color: #10b981;
}

.setup-check li.is-bad::before {
    content: "\2717";
    color: #dc2626;
}

.setup-block--done {
    text-align: center;
}

.setup-footnote {
    margin: 1.75rem 0 0;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
    font-size: 0.78rem;
    text-align: center;
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Accessibility
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    * {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
    }
}

/* --------------------------------------------------------------------------
   Results (Stage 12)
   -------------------------------------------------------------------------- */

.result-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.result-stat {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.75rem 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.75rem;
}

.result-stat-label {
    font-size: 0.76rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.result-stat-value {
    font-size: 1.2rem;
    font-weight: 700;
    color: var(--text);
}

/* Compact, touch-friendly number cells in the mark sheet. */
.marks-table .mark-input {
    width: 5rem;
    margin-left: auto;
    text-align: right;
}

.scale-input {
    width: 6rem;
}

.mark-max {
    font-size: 0.72rem;
    font-weight: 500;
    color: var(--text-muted);
}

.inline-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}

.inline-form .date-input {
    width: auto;
    min-width: 9.5rem;
}

.inline-form .note-input {
    flex: 1 1 10rem;
    width: auto;
    min-width: 8rem;
}

@media (max-width: 575.98px) {
    .marks-table .mark-input,
    .scale-input {
        width: 100%;
        margin-left: 0;
        text-align: left;
    }
}

/*
 * Grading scale on phones.
 *
 * The scale is a five-column table (Grade / Min / Max / Grade point / Remark).
 * That table cannot fit a phone width, so it used to make its container scroll
 * sideways and the Min/Max inputs sat off-screen or were clipped while a value
 * was being typed. Below the small breakpoint each band becomes a stacked,
 * labelled block and every input is full width, so a typed value is always
 * visible and nothing needs sideways scrolling. The column name is shown from
 * each cell's data-label. Desktop and tablet are untouched.
 */
@media (max-width: 575.98px) {
    .scale-table,
    .scale-table tbody,
    .scale-table tr,
    .scale-table th,
    .scale-table td {
        display: block;
        width: 100%;
    }

    .scale-table thead {
        display: none;
    }

    .scale-table tr {
        margin-bottom: 0.75rem;
        padding: 0.6rem 0.8rem;
        border: 1px solid var(--border);
        border-radius: 0.6rem;
    }

    .scale-table tr:last-child {
        margin-bottom: 0;
    }

    .scale-table th,
    .scale-table td {
        padding: 0.3rem 0;
        border: 0;
    }

    .scale-table th[scope="row"]::before,
    .scale-table td::before {
        content: attr(data-label);
        display: block;
        margin-bottom: 0.2rem;
        font-size: 0.72rem;
        font-weight: 700;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: var(--text-muted);
        text-align: left;
    }

    .scale-table .form-control {
        width: 100%;
        min-width: 0;
        margin-left: 0;
        text-align: left;
    }
}

/* ==========================================================================
   Stage 13 — landing page content and gallery
   ========================================================================== */

/* --------------------------------------------------------------------------
   Public landing page
   -------------------------------------------------------------------------- */

.public-page {
    min-height: 100vh;
    margin: 0;
    background: var(--surface-muted);
    color: var(--text);
}

.public-container {
    width: 100%;
    max-width: 1120px;
    margin: 0 auto;
    padding: 0 1.15rem;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 50;
    padding: 0.6rem 1rem;
    background: var(--sky-700);
    color: #fff;
    border-radius: 0 0 0.5rem 0;
    text-decoration: none;
}

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

.public-header {
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.public-header-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-top: 0.9rem;
    padding-bottom: 0.9rem;
}

.public-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
}

.public-brand-name {
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--sky-800);
}

.public-section {
    padding: 3rem 0;
    border-bottom: 1px solid var(--border);
    background: var(--surface);
}

.public-section:nth-of-type(even) {
    background: var(--surface-muted);
}

.public-section-title {
    margin: 0 0 1rem;
    font-size: clamp(1.35rem, 1rem + 1.4vw, 1.9rem);
    font-weight: 700;
    color: var(--sky-900);
}

.public-hero-title {
    margin: 0 0 0.75rem;
    font-size: clamp(1.6rem, 1.1rem + 2.2vw, 2.5rem);
    font-weight: 800;
    color: var(--sky-900);
}

.public-hero-text {
    max-width: 60ch;
    margin: 0;
    color: var(--text-muted);
    font-size: 1.05rem;
}

/* Sanitized administrator HTML: only the tags the sanitizer allows appear. */
.public-section-body {
    max-width: 72ch;
    font-size: 1.02rem;
    line-height: 1.7;
    color: var(--text);
}

.public-section-body > *:first-child { margin-top: 0; }
.public-section-body > *:last-child { margin-bottom: 0; }

.public-section-body h2,
.public-section-body h3,
.public-section-body h4 {
    margin: 1.6rem 0 0.6rem;
    color: var(--sky-900);
    font-weight: 700;
}

.public-section-body p { margin: 0 0 1rem; }

.public-section-body ul,
.public-section-body ol {
    margin: 0 0 1rem;
    padding-left: 1.4rem;
}

.public-section-body li { margin-bottom: 0.35rem; }

.public-section-body blockquote {
    margin: 1.2rem 0;
    padding: 0.75rem 1.1rem;
    border-left: 4px solid var(--sky-400);
    background: var(--sky-50);
    color: var(--sky-900);
    border-radius: 0 0.5rem 0.5rem 0;
}

.public-section-body a {
    color: var(--sky-700);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.public-section-body a:hover { color: var(--sky-800); }

.public-section-body code {
    padding: 0.1rem 0.35rem;
    background: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: 0.3rem;
    font-size: 0.92em;
}

.public-section-body pre {
    overflow-x: auto;
    padding: 0.9rem 1rem;
    background: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
}

.public-section-body pre code {
    padding: 0;
    border: 0;
    background: none;
}

.public-section--empty { text-align: left; }

.public-footer {
    padding: 1.6rem 0;
    background: var(--surface);
    border-top: 1px solid var(--border);
}

.public-footer-text {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.92rem;
}

/* --------------------------------------------------------------------------
   Public gallery carousel
   -------------------------------------------------------------------------- */

.public-section--gallery .public-section-title { margin-bottom: 1.25rem; }

.gallery {
    position: relative;
    border: 1px solid var(--border);
    border-radius: 0.9rem;
    background: var(--surface);
    overflow: hidden;
    box-shadow: var(--shadow-card);
}

.gallery-viewport {
    overflow: hidden;
    touch-action: pan-y;
    cursor: grab;
}

.gallery-viewport:focus-visible {
    outline: 3px solid var(--ring);
    outline-offset: -3px;
}

.gallery-viewport:active { cursor: grabbing; }

.gallery-track {
    display: flex;
    margin: 0;
    padding: 0;
    list-style: none;
    will-change: transform;
}

.gallery-slide {
    flex: 0 0 100%;
    min-width: 100%;
    margin: 0;
}

.gallery-figure {
    margin: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: clamp(220px, 42vh, 480px);
    background: var(--surface-muted);
}

/* object-fit: contain - the whole photograph is always visible, never cropped
   to a fashionable ratio, and never stretched. */
.gallery-image {
    display: block;
    width: 100%;
    height: auto;
    max-height: clamp(220px, 52vh, 560px);
    object-fit: contain;
    user-select: none;
    -webkit-user-drag: none;
}

.gallery-caption {
    padding: 0.7rem 1rem 0.9rem;
    margin: 0;
    font-size: 0.92rem;
    color: var(--text-muted);
    text-align: center;
}

.gallery-caption strong {
    display: block;
    color: var(--text);
}

.gallery-control {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 2.6rem;
    height: 2.6rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.9);
    color: var(--sky-800);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 10px rgba(12, 74, 110, 0.22);
}

.gallery-control:hover { background: #fff; }

.gallery-control:focus-visible {
    outline: 3px solid var(--ring);
    outline-offset: 2px;
}

.gallery-control--prev { left: 0.6rem; }
.gallery-control--next { right: 0.6rem; }

.gallery-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    padding: 0.7rem 1rem 0.9rem;
    border-top: 1px solid var(--border);
}

.gallery-dots {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
}

.gallery-dot {
    width: 0.65rem;
    height: 0.65rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--sky-200);
    cursor: pointer;
}

.gallery-dot.is-current { background: var(--sky-600); }

.gallery-dot:focus-visible {
    outline: 3px solid var(--ring);
    outline-offset: 2px;
}

/* --------------------------------------------------------------------------
   Admin: content sections
   -------------------------------------------------------------------------- */

.section-heading-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.cell-strong { font-weight: 600; }

.cell-truncate {
    max-width: 32ch;
    color: var(--text-muted);
    font-size: 0.9rem;
}

.inline-action { display: inline; }

.wysiwyg {
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    background: var(--surface);
    overflow: hidden;
}

.wysiwyg-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    padding: 0.45rem;
    background: var(--surface-muted);
    border-bottom: 1px solid var(--border);
}

.wysiwyg-btn {
    min-width: 2.1rem;
    height: 2.1rem;
    padding: 0 0.45rem;
    border: 1px solid var(--border);
    border-radius: 0.35rem;
    background: var(--surface);
    color: var(--text);
    font-size: 0.9rem;
    line-height: 1;
    cursor: pointer;
}

.wysiwyg-btn:hover { background: var(--sky-50); }

.wysiwyg-btn.is-active {
    background: var(--sky-100);
    border-color: var(--sky-400);
    color: var(--sky-800);
}

.wysiwyg-btn:focus-visible {
    outline: 3px solid var(--ring);
    outline-offset: 1px;
}

.wysiwyg-surface {
    min-height: 15rem;
    max-height: 32rem;
    overflow-y: auto;
    padding: 0.9rem 1rem;
    line-height: 1.65;
}

.wysiwyg-surface:focus {
    outline: none;
}

.wysiwyg-surface:focus-visible {
    outline: 3px solid var(--ring);
    outline-offset: -3px;
}

.wysiwyg-surface h2,
.wysiwyg-surface h3 { color: var(--sky-900); }

.wysiwyg-surface blockquote {
    margin: 0.8rem 0;
    padding: 0.5rem 0.9rem;
    border-left: 4px solid var(--sky-400);
    background: var(--sky-50);
}

/* --------------------------------------------------------------------------
   Admin: gallery
   -------------------------------------------------------------------------- */

.app-tab--external { opacity: 0.85; }

.gallery-admin-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: 1rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.gallery-admin-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border);
    border-radius: 0.75rem;
    background: var(--surface);
    overflow: hidden;
}

.gallery-admin-card.is-disabled { opacity: 0.72; }

/* Admin gallery thumbnail. A fixed 10rem box, so every card in the grid is the
   same height whatever shape the photograph is. Fitting is handled by the
   frame primitives in the uploaded-images section at the top of this file. */
.gallery-admin-thumb {
    position: relative;
    height: 10rem;
}

.gallery-admin-order {
    position: absolute;
    top: 0.4rem;
    left: 0.4rem;
    min-width: 1.6rem;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
    background: var(--sky-700);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    text-align: center;
}

.gallery-admin-meta {
    padding: 0.65rem 0.8rem 0.2rem;
}

.gallery-admin-title {
    margin: 0 0 0.25rem;
    font-weight: 600;
    word-break: break-word;
}

.gallery-admin-sub {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin: 0;
    font-size: 0.85rem;
}

.gallery-admin-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    padding: 0.65rem 0.8rem 0.8rem;
}

/* The edit form's thumbnail. A bounded 12rem box, so a full-size camera
   photograph shows at preview size instead of at its own dimensions. This one
   is its own frame - there is no wrapper element to carry a class - so it uses
   the bare .img-contain form. */
.gallery-edit-thumb {
    width: 100%;
    height: 12rem;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    background: var(--surface-muted);
}

/* The delete confirmation: a frame like any other, with a bounded width and a
   fixed height. */
.gallery-confirm-preview {
    height: 12rem;
    max-width: 22rem;
    margin-bottom: 1rem;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
}

[data-bs-theme="dark"] .public-section-body blockquote,
[data-bs-theme="dark"] .wysiwyg-surface blockquote {
    background: rgba(14, 165, 233, 0.14);
    color: var(--sky-100);
}

[data-bs-theme="dark"] .gallery-control {
    background: rgba(15, 23, 42, 0.82);
    color: var(--sky-100);
}

[data-bs-theme="dark"] .gallery-dot { background: var(--sky-800); }
[data-bs-theme="dark"] .gallery-dot.is-current { background: var(--sky-300); }

[data-bs-theme="dark"] .public-section-title,
[data-bs-theme="dark"] .public-hero-title,
[data-bs-theme="dark"] .public-section-body h2,
[data-bs-theme="dark"] .public-section-body h3,
[data-bs-theme="dark"] .public-section-body h4,
[data-bs-theme="dark"] .wysiwyg-surface h2,
[data-bs-theme="dark"] .wysiwyg-surface h3,
[data-bs-theme="dark"] .public-brand-name {
    color: var(--sky-200);
}

@media (max-width: 575.98px) {
    .public-section { padding: 2.2rem 0; }
    .gallery-control { width: 2.2rem; height: 2.2rem; font-size: 1.25rem; }
    .gallery-footer { justify-content: center; }
}

/* ==========================================================================
   Stage 15 - admissions and parents/guardians

   Small additions in the admin panel's existing language: sky blue only,
   no purple anywhere.
   ========================================================================== */

/* A confirmation that unfolds in place. A native <details>, so it needs no
   JavaScript - which is what keeps the Content-Security-Policy free of
   inline handlers. */
.confirm-block > summary {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    list-style: none;
    cursor: pointer;
}

.confirm-block > summary::-webkit-details-marker {
    display: none;
}

.confirm-block > summary::after {
    content: "";
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 5px solid currentColor;
    transition: transform 0.18s ease;
}

.confirm-block[open] > summary::after {
    transform: rotate(180deg);
}

.confirm-block > summary:focus-visible {
    outline: 3px solid var(--ring);
    outline-offset: 2px;
}

.confirm-block-body {
    margin-top: 0.9rem;
    padding: 1rem 1.1rem;
    background: var(--surface-muted);
    border: 1px solid var(--border);
    border-left: 4px solid var(--sky-400);
    border-radius: 0 0.6rem 0.6rem 0;
}

/* Pager for the admissions list. */
.pager ul {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin: 1.25rem 0 0;
    padding: 0;
    list-style: none;
}

.pager-status {
    color: var(--text-muted);
    font-size: 0.92rem;
    font-weight: 600;
}

/* ==========================================================================
   Stage 16 - permanent student Result PIN

   One read-only identity line on the student's own page.

   The accent is a border only, so it never has to carry contrast: the PIN
   itself is rendered in var(--text) on var(--surface-muted), both of which the
   [data-bs-theme="dark"] block redefines. The sky scale is NOT redefined for
   dark mode, so using --sky-700 as a text color here would be unreadable in
   dark mode; --bs-primary (sky-500 light / sky-400 dark) is used for the
   decorative accent instead, and it is correct in both themes.
   ========================================================================== */

.pin-card {
    border-top: 4px solid var(--bs-primary);
}

.pin-card--new {
    border-top-width: 6px;
}

.pin-line {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin: 0 0 0.85rem;
}

.pin-line-label {
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* Monospaced and spaced out, so a PIN read off the screen is unambiguous:
   no character can be mistaken for its neighbour. */
.pin-value {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
    font-size: 1.45rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    color: var(--text);
    background: var(--surface-muted);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.35rem 0.7rem;
    overflow-wrap: anywhere;
}

.pin-line-note {
    margin: 0 0 0.7rem;
    font-weight: 600;
    color: var(--text);
}

/* --------------------------------------------------------------------------
   Site Colors settings
   -------------------------------------------------------------------------- */

.color-settings-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
    margin-bottom: 1.5rem;
}

.color-settings-preview {
    min-width: 0;
    padding: 1.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.85rem;
}

/* On a wide screen the preview sits beside the fields and stays in view while
   the administrator works down the form. */
@media (min-width: 992px) {
    .color-settings-layout {
        grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
        align-items: start;
    }

    .color-settings-preview {
        position: sticky;
        top: 1.25rem;
    }
}

.color-field-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.1rem;
    margin-top: 1.25rem;
}

@media (min-width: 768px) {
    .color-field-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

.color-field {
    min-width: 0;
}

.color-field-controls {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.color-swatch-input {
    flex: 0 0 auto;
    width: 3rem;
    height: 2.6rem;
    padding: 0.15rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.6rem;
    cursor: pointer;
}

.color-swatch-input:focus-visible {
    outline: none;
    box-shadow: 0 0 0 0.22rem var(--ring);
}

.color-hex-input {
    flex: 1 1 6rem;
    min-width: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.color-revert {
    flex: 0 0 auto;
}

.color-field-hint {
    margin: 0.45rem 0 0;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--text-muted);
}

.color-settings-actions {
    position: sticky;
    bottom: 0;
    z-index: 5;
    margin: 0;
    padding: 0.85rem 0;
    background: var(--surface-muted);
    border-top: 1px solid var(--border);
}

.color-preview-note {
    margin: 1rem 0 0;
    font-size: 0.85rem;
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   The miniature public page shown in the preview
   -------------------------------------------------------------------------- */

/* Each of the seven choices is applied from JavaScript as a custom property on
   this element (property assignment, which the CSP permits - never a style
   attribute). The values below are only the starting point and match the
   built-in theme, so the preview is correct before anything is typed. */
.color-preview {
    --preview-primary: #0ea5e9;
    --preview-secondary: #0369a1;
    --preview-accent: #38bdf8;
    --preview-text: #0f172a;
    --preview-background: #f2f8fd;
    --preview-surface: #ffffff;
    --preview-on-primary: #ffffff;

    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: 0.75rem;
    font-size: 0.85rem;
}

.color-preview-hero {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    padding: 1.1rem;
    background: linear-gradient(155deg, var(--preview-secondary) 0%, var(--preview-primary) 100%);
    color: var(--preview-on-primary);
}

.color-preview-eyebrow {
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    opacity: 0.85;
}

.color-preview-title {
    font-size: 1.15rem;
    font-weight: 800;
    line-height: 1.2;
}

.color-preview-body {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    padding: 1.1rem;
    background: var(--preview-surface);
    color: var(--preview-text);
}

.color-preview-heading {
    font-size: 1rem;
    font-weight: 750;
    color: var(--preview-secondary);
}

/* The same short accent rule the real section headings use. */
.color-preview-heading::after {
    content: "";
    display: block;
    width: 2.25rem;
    height: 3px;
    margin-top: 0.4rem;
    border-radius: 3px;
    background: var(--preview-accent);
}

.color-preview-text {
    margin: 0;
    line-height: 1.6;
}

.color-preview-link {
    color: var(--preview-secondary);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.color-preview-button {
    align-self: flex-start;
    padding: 0.42rem 0.9rem;
    border-radius: 0.45rem;
    background: var(--preview-primary);
    color: var(--preview-on-primary);
    font-weight: 700;
}

.color-preview-card {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.7rem 0.85rem;
    background: var(--preview-background);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
}

.color-preview-footer {
    padding: 0.85rem 1.1rem;
    background: var(--preview-secondary);
    color: var(--preview-on-primary);
    font-size: 0.8rem;
}

/* --------------------------------------------------------------------------
   Logo settings
   -------------------------------------------------------------------------- */

.logo-settings-form {
    margin-top: 1.5rem;
}

.logo-settings-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.4rem;
    margin-top: 1.25rem;
}

@media (min-width: 768px) {
    .logo-settings-layout {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        align-items: start;
    }
}

/* The frame the logo is shown in, both for the stored one and for the pending
   preview. Fixed enough to keep the two comparable, flexible enough that a very
   wide or very tall logo is contained rather than spilling out. */
.logo-preview {
    gap: 0.75rem;
    min-height: 7rem;
    margin-top: 0.4rem;
    padding: 1rem;
    border: 1px dashed var(--border);
    border-radius: 0.7rem;
}

/* A logo is shown at its own proportions rather than being forced into a fixed
   shape: it takes the height it wants up to 7rem, and as much width as it needs
   up to the column. Nothing is cropped, nothing is stretched, and a very wide
   logo shrinks instead of pushing the card wider. The frame's min-height keeps
   the empty state and the filled state the same height. */
.logo-preview img {
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 7rem;
}

/* Nothing uploaded yet: the letter tile stands in for the logo the site is
   actually showing, so the empty state looks like the site rather than a gap. */
.logo-preview--empty {
    flex-direction: column;
    text-align: center;
    border-style: solid;
}

.logo-preview-note {
    max-width: 22rem;
    font-size: 0.82rem;
    line-height: 1.5;
    color: var(--text-muted);
}

.logo-optional {
    font-weight: 400;
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Hero slides
   -------------------------------------------------------------------------- */

.hero-scope-note {
    margin-bottom: 1.5rem;
}

/* The delete confirmation for a slide. A definite 16:9 box, so the page cannot
   reflow whatever shape the image is - and contained rather than covered: an
   administrator about to delete a photograph should see all of it, not a crop
   of it. The card preview above shows how it will actually be cropped. */
.hero-confirm-preview {
    max-width: 22rem;
    margin: 0 0 1.25rem;
    border: 1px solid var(--border);
    border-radius: 0.6rem;
}

.hero-slides-section {
    margin-top: 2rem;
}

.hero-slide-list {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.25rem;
    margin: 1rem 0 0;
    padding: 0;
    list-style: none;
}

@media (min-width: 768px) {
    .hero-slide-list {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1200px) {
    .hero-slide-list {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.hero-slide-card {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    min-width: 0;
    padding: 1rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.85rem;
    box-shadow: var(--shadow-card);
}

.hero-slide-card.is-disabled {
    opacity: 0.72;
}

/* An approximation of a hero: the image, with the slide's own text over a
   scrim. The ratio is fixed, so every card is the same shape whatever the
   uploaded image's proportions and the grid cannot jump as slides are added.
   Cover, because this is a backdrop with text laid over it - exactly how the
   real hero renders it, so the preview is honest. */
.hero-slide-preview {
    position: relative;
    border-radius: 0.6rem;
}

.hero-slide-preview-overlay {
    position: absolute;
    inset: auto 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    max-height: 100%;
    padding: 0.85rem 0.9rem 0.7rem;
    overflow: hidden;
    background: linear-gradient(180deg, rgba(8, 47, 73, 0) 0%, rgba(8, 47, 73, 0.88) 72%);
    color: #ffffff;
}

.hero-slide-preview-title {
    font-size: 1rem;
    font-weight: 750;
    line-height: 1.25;
    overflow-wrap: anywhere;
}

.hero-slide-preview-body {
    font-size: 0.82rem;
    line-height: 1.5;
    overflow-wrap: anywhere;
}

/* Administrator-written HTML is sanitized, but it can still be a heading or a
   list, so the preview flattens their spacing rather than inheriting it. */
.hero-slide-preview-body :is(p, ul, ol, blockquote) {
    margin: 0;
}

.hero-slide-preview-body :is(h2, h3, h4) {
    margin: 0;
    font-size: 0.9rem;
    color: inherit;
}

.hero-slide-preview-missing {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.hero-slide-order {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.6rem;
    height: 1.6rem;
    padding: 0 0.4rem;
    border-radius: 0.5rem;
    background: rgba(8, 47, 73, 0.78);
    color: #ffffff;
    font-size: 0.78rem;
    font-weight: 700;
}

.hero-slide-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    flex-wrap: wrap;
}

.hero-slide-note {
    font-size: 0.78rem;
    color: var(--text-muted);
}

.hero-badge--on {
    background-color: #0f7b52;
}

.hero-badge--off {
    background-color: #64748b;
}

.hero-slide-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
}

/* ==========================================================================
   Stage 18 - administrator profile settings and the account menu

   Two new pieces, both in the existing language: sky blue only, no purple, and
   every colour taken from the tokens the rest of the panel already uses, so
   they follow Day and Night without a second set of rules for each.

   The account menu is a <details>/<summary> pair like the sidebar groups, so
   almost all of its styling is about where the panel sits rather than about how
   it opens.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Profile pictures
   -------------------------------------------------------------------------- */

.avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    overflow: hidden;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    /* The brand ramp, but starting at sky-700 rather than sky-400: this tile
       carries white text, and white on sky-400 measures 2.14:1 and on sky-600
       4.10:1, both below the 4.5:1 that normal-size text needs. sky-700 is the
       first step of the ramp that clears it, at 5.93:1, and it is the same
       surface .btn-sky already uses for the same reason. */
    background: linear-gradient(160deg, var(--sky-700), var(--sky-800));
    color: #ffffff;
    font-weight: 700;
    font-size: 0.95rem;
    line-height: 1;
    /* A ring, not a border: it has to sit outside the picture without changing
       the box the layout reserved. */
    box-shadow: 0 0 0 1px var(--border);
    user-select: none;
}

/* The picture fills the circle and is cropped by it. cover rather than contain,
   which is the opposite of every other uploaded image in this panel: this one
   is a circle by design, so the crop IS the presentation. A contained picture
   inside a circular frame would letterbox into a shape nobody is showing. */
.avatar-image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

.avatar-initial {
    display: block;
    /* Optical centring: a capital letter drawn dead-centre sits a hair low. */
    transform: translateY(-0.02em);
}

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

.avatar--xl {
    width: 7rem;
    height: 7rem;
    font-size: 2.5rem;
    box-shadow: 0 0 0 1px var(--border), 0 10px 24px rgba(2, 132, 199, 0.18);
}

/* --------------------------------------------------------------------------
   Account menu, in the top bar
   -------------------------------------------------------------------------- */

.profile-menu {
    position: relative;
    flex: 0 0 auto;
}

.profile-summary {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    max-width: 15rem;
    padding: 0.3rem 0.55rem 0.3rem 0.35rem;
    border: 1px solid transparent;
    border-radius: 999px;
    background: transparent;
    cursor: pointer;
    list-style: none;
    transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
}

/* The disclosure triangle is replaced by the chevron below, so the default
   marker is suppressed in both engines. */
.profile-summary::-webkit-details-marker {
    display: none;
}

.profile-summary::marker {
    content: "";
}

.profile-summary:hover {
    background: var(--sky-50);
    border-color: var(--border);
}

.profile-summary:focus-visible {
    outline: 2px solid var(--sky-700);
    outline-offset: 2px;
}

.profile-summary-name {
    min-width: 0;
    font-size: 0.9rem;
    font-weight: 650;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.profile-chevron {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    opacity: 0.7;
    transform: rotate(90deg);
    transition: transform 0.18s ease-in-out;
}

.profile-menu[open] > .profile-summary .profile-chevron {
    transform: rotate(-90deg);
}

.profile-menu[open] > .profile-summary {
    background: var(--sky-50);
    border-color: var(--border);
}

/* The panel. Anchored to the right of the bar it hangs from, so on a narrow
   screen it grows towards the middle of the page instead of off the edge -
   and its width is a min() so it can never be wider than the viewport. */
.profile-dropdown {
    position: absolute;
    top: calc(100% + 0.4rem);
    right: 0;
    z-index: 1050;
    width: min(20rem, calc(100vw - 1.5rem));
    padding: 0.5rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 0.75rem;
    box-shadow: 0 18px 40px rgba(8, 47, 73, 0.22);
    animation: profile-dropdown-in 0.14s ease-out;
}

@keyframes profile-dropdown-in {
    from {
        opacity: 0;
        transform: translateY(-0.25rem);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.profile-dropdown-head {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
    padding: 0.5rem 0.55rem 0.75rem;
    border-bottom: 1px solid var(--border);
}

.profile-dropdown-identity {
    display: block;
    min-width: 0;
}

.profile-dropdown-name {
    display: block;
    font-size: 0.95rem;
    font-weight: 700;
    color: var(--text);
    /* The name and the address are both administrator-supplied, so both can be
       long or unbroken; they wrap rather than widening the panel. */
    overflow-wrap: anywhere;
}

.profile-dropdown-email {
    display: block;
    margin-top: 0.1rem;
    font-size: 0.82rem;
    color: var(--text-muted);
    overflow-wrap: anywhere;
}

.profile-dropdown-list {
    margin: 0.4rem 0 0;
    padding: 0;
    list-style: none;
}

/* The link and the sign-out button are one control with two elements: one
   navigates, one submits a form. Both are styled here so they cannot drift. */
.profile-dropdown-link {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    width: 100%;
    padding: 0.55rem 0.6rem;
    border: 0;
    border-radius: 0.5rem;
    background: transparent;
    color: var(--text);
    font-family: inherit;
    font-size: 0.9rem;
    font-weight: 600;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out;
}

.profile-dropdown-link:hover,
.profile-dropdown-link:focus {
    background: var(--sky-50);
    color: var(--sky-800);
}

.profile-dropdown-link:focus-visible {
    outline: 2px solid var(--sky-700);
    outline-offset: -2px;
}

.profile-dropdown-icon {
    flex: 0 0 auto;
    width: 1.05rem;
    height: 1.05rem;
    opacity: 0.85;
}

.profile-dropdown-signout {
    /* A button inside a form, so it needs the form to not add its own spacing
       and the button to inherit the link's box. */
    margin: 0;
}

.profile-dropdown form {
    margin: 0;
}

/* --------------------------------------------------------------------------
   Profile settings page
   -------------------------------------------------------------------------- */

.profile-section + .profile-section {
    margin-top: 1.5rem;
}

/* The password section is linked to directly by the account menu, so it must
   not be hidden under the sticky bar when it is jumped to. */
.profile-section[id] {
    scroll-margin-top: 5rem;
}

.avatar-settings-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.4rem;
    margin-top: 1.25rem;
}

@media (min-width: 768px) {
    .avatar-settings-layout {
        grid-template-columns: auto minmax(0, 1fr);
        align-items: start;
    }
}

.avatar-settings-current {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
    min-width: 0;
}

.avatar-settings-current .form-hint {
    max-width: 14rem;
    margin-bottom: 0;
}

.avatar-settings-upload {
    min-width: 0;
}

.avatar-pending {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
    margin-top: 1rem;
}

.profile-password-note {
    margin: 1rem 0 0;
}

/* --------------------------------------------------------------------------
   Night mode

   The picture and the menu are the last two surfaces that were written for a
   light page. Nothing here re-colours the Day rules: the tokens do most of the
   work already, and only the two fixed light values need replacing.
   -------------------------------------------------------------------------- */

/* --sky-50 is a near-white wash: invisible as a hover on a near-black panel,
   and the hover text colour is a dark sky that would be unreadable on it. */
[data-bs-theme="dark"] .profile-summary:hover,
[data-bs-theme="dark"] .profile-menu[open] > .profile-summary,
[data-bs-theme="dark"] .profile-dropdown-link:hover,
[data-bs-theme="dark"] .profile-dropdown-link:focus {
    background: rgba(56, 189, 248, 0.14);
    color: var(--sky-200);
    border-color: var(--border);
}

[data-bs-theme="dark"] .profile-summary:focus-visible,
[data-bs-theme="dark"] .profile-dropdown-link:focus-visible {
    outline-color: var(--sky-300);
}

/* A sky-blue glow reads as haze on a dark page, so night uses a plain dark
   shadow - the same reasoning as the cards. */
[data-bs-theme="dark"] .profile-dropdown {
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.55);
}

[data-bs-theme="dark"] .avatar--xl {
    box-shadow: 0 0 0 1px var(--border), 0 10px 24px rgba(0, 0, 0, 0.5);
}

@media (prefers-reduced-motion: reduce) {
    .profile-chevron,
    .profile-dropdown {
        transition: none;
        animation: none;
    }
}

/* On the narrowest screens the name beside the picture is the first thing that
   can go: it is repeated, in full, in the panel directly below it. */
@media (max-width: 575.98px) {
    .profile-summary {
        max-width: none;
        padding-right: 0.35rem;
    }

    .profile-summary-name {
        display: none;
    }
}

/* ==========================================================================
Website Content (Stage 20)

The admin screen for the item-based public sections: highlights, programmes,
facilities, student life, achievements, why-choose-us, news, testimonials,
contact and social links.

Every selector here is a new name introduced by that one screen, so nothing
can reach an existing admin component. Colours come only from the tokens
app.css already defines, which is what makes the whole screen follow the
administrator's Day/Night choice with no rules of its own for either theme.

Mobile first: the item row is a stack until there is room for three columns,
because a thumbnail, a heading and six buttons will not share 320px.
========================================================================== */

/* --- the section tabs ---------------------------------------------------- */

/* A count beside a tab label, so an administrator can see at a glance which
sections still have nothing in them. */
.app-tab-count {
display: inline-block;
min-width: 1.35rem;
margin-left: 0.35rem;
padding: 0.05rem 0.4rem;
border-radius: 999px;
background-color: var(--sky-100);
color: var(--sky-800);
font-size: 0.72rem;
font-weight: 700;
line-height: 1.5;
text-align: center;
}

/* The active tab is filled, so its count needs to read against that fill. */
.app-tab.is-active .app-tab-count {
background-color: rgba(255, 255, 255, 0.24);
color: #ffffff;
}

/* A section switched off keeps its tab, but says so: a dashed border reads as
"present but not currently shown" rather than "broken". */
.app-tab.is-hidden-section {
border-style: dashed;
color: var(--text-muted);
}

.app-tab.is-hidden-section.is-active {
color: #ffffff;
}

/* --- the section header card --------------------------------------------- */

.section-card-head {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-bottom: 1rem;
}

@media (min-width: 768px) {
.section-card-head {
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1.5rem;
}
}

.section-card-head .section-heading {
margin-bottom: 0.35rem;
}

/* The heading and blurb must be allowed to shrink, or a long blurb sets the
flex row's min-content width and the card overflows on a phone. */
.section-card-head > div:first-child {
min-width: 0;
}

.section-card-meta {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
flex: 0 0 auto;
}

.status-chip {
display: inline-block;
padding: 0.15rem 0.6rem;
border: 1px solid var(--border);
border-radius: 999px;
background-color: var(--surface-muted);
color: var(--text-muted);
font-size: 0.76rem;
font-weight: 700;
letter-spacing: 0.02em;
white-space: nowrap;
}

.status-chip--on {
border-color: var(--sky-200);
background-color: var(--sky-50);
color: var(--sky-800);
}

.status-chip--off {
border-color: var(--border);
background-color: var(--surface-muted);
color: var(--text-muted);
}

/* --- the row of small section-level forms -------------------------------- */

.section-actions {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
margin-bottom: 1.25rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid var(--border);
}

.section-actions form {
margin: 0;
}

/* --- the item list ------------------------------------------------------- */

.content-item-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin: 0;
padding: 0;
list-style: none;
}

.content-item {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 0.9rem;
border: 1px solid var(--border);
border-radius: 0.75rem;
background-color: var(--surface);
}

/* A hidden item stays listed - an administrator needs to find it again - but
is visibly stepped back rather than removed. */
.content-item.is-hidden-item {
background-color: var(--surface-muted);
border-style: dashed;
}

@media (min-width: 768px) {
.content-item {
    flex-direction: row;
    align-items: center;
    gap: 1rem;
}
}

.content-item-thumb {
flex: 0 0 auto;
width: 100%;
max-width: 12rem;
border-radius: 0.5rem;
overflow: hidden;
}

@media (min-width: 768px) {
.content-item-thumb {
    width: 8rem;
}
}

.content-item-body {
/* min-width: 0 is what lets a long heading wrap instead of forcing the row
   wider than the card. */
flex: 1 1 auto;
min-width: 0;
}

.content-item-title {
margin: 0;
font-weight: 700;
color: var(--text);
overflow-wrap: anywhere;
}

.content-item-sub {
margin: 0.15rem 0 0;
font-size: 0.86rem;
color: var(--text-muted);
overflow-wrap: anywhere;
}

.content-item-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.35rem;
flex: 0 0 auto;
}

.content-item-actions form {
margin: 0;
}

/* --- the image fields ---------------------------------------------------- */

.current-image {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 0.9rem;
}

.current-image-frame {
max-width: 16rem;
border-radius: 0.5rem;
overflow: hidden;
}

.image-preview {
max-width: 16rem;
margin-top: 0.75rem;
border-radius: 0.5rem;
overflow: hidden;
}

/* A label hint such as "(optional)", stepped back so it reads as a note
   rather than as part of the label. */
.form-optional {
    font-weight: 400;
    color: var(--text-muted);
}

/* ==========================================================================
   Staff accounts, roles and permissions (Stage 21)

   The administrator's staff-management screens, and the dashboard a staff
   member lands on. Every selector is a new name introduced by this stage, so
   nothing here can reach an existing component.

   Colours come only from tokens app.css already defines, which is what makes
   these screens follow the administrator's Day/Night choice with no rules of
   their own for either theme.

   Mobile first, like everything else: one column until there is room for more.
   ========================================================================== */

/* --- the administrator's account summary --------------------------------- */

.staff-summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
    gap: 0.85rem;
    margin-bottom: 1.25rem;
}

/* --- the staff member's dashboard ---------------------------------------- */

.staff-panel-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
    gap: 0.85rem;
    margin-bottom: 1.25rem;
}

.staff-panel {
    display: block;
    padding: 1.1rem 1.2rem;
    text-decoration: none;
}

.staff-panel-count {
    display: block;
    font-size: 1.8rem;
    font-weight: 700;
    line-height: 1.1;
    color: var(--text);
}

.staff-panel-label {
    display: block;
    margin-top: 0.15rem;
    font-weight: 700;
    color: var(--sky-800);
}

.staff-panel-unit {
    display: block;
    font-size: 0.82rem;
    color: var(--text-muted);
}

/* Permission keys, shown as chips. `overflow-wrap` is what stops a long key
   such as `admissions.create` from widening a narrow column. */
.staff-permission-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.staff-permission-list li {
    min-width: 0;
}

.staff-permission-list code {
    display: inline-block;
    padding: 0.15rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 0.4rem;
    background-color: var(--surface-muted);
    color: var(--text-muted);
    font-size: 0.78rem;
    overflow-wrap: anywhere;
}

/* --- the role editor's permission checkboxes ----------------------------- */

.permission-groups {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
    gap: 0.85rem;
    margin-top: 0.5rem;
}

.permission-group {
    margin: 0;
    padding: 0.9rem 1rem;
    border: 1px solid var(--border);
    border-radius: 0.75rem;
    background-color: var(--surface);
}

.permission-group-legend {
    padding: 0 0.35rem;
    font-size: 0.9rem;
    font-weight: 700;
    color: var(--sky-800);
}

/* The label wraps a bold name, the key, an optional chip and a description, so
   it has to be a block or the description runs alongside the name. */
.permission-group .form-check-label {
    display: block;
    min-width: 0;
}

.permission-key {
    display: inline-block;
    margin-left: 0.35rem;
    padding: 0.05rem 0.4rem;
    border-radius: 0.35rem;
    background-color: var(--surface-muted);
    color: var(--text-muted);
    font-size: 0.74rem;
    overflow-wrap: anywhere;
}

.permission-description {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.82rem;
    color: var(--text-muted);
}

/* --- role pickers and chips ---------------------------------------------- */

.role-picker {
    margin: 0;
    padding: 0.9rem 1rem;
    border: 1px solid var(--border);
    border-radius: 0.75rem;
    background-color: var(--surface-muted);
}

.role-picker .form-check-label {
    min-width: 0;
}

.role-chip-list {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* --- read-only detail rows (a staff member's own account) ---------------- */

.detail-list {
    margin: 0;
}

.detail-row {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border);
}

@media (min-width: 576px) {
    .detail-row {
        flex-direction: row;
        align-items: baseline;
        gap: 1rem;
    }

    .detail-row dt {
        flex: 0 0 11rem;
    }
}

.detail-row:last-child {
    border-bottom: 0;
}

.detail-row dt {
    margin: 0;
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.detail-row dd {
    margin: 0;
    /* min-width: 0 lets a long email address wrap instead of widening the row. */
    min-width: 0;
    overflow-wrap: anywhere;
    color: var(--text);
}

/* --- an inline search beside a heading ----------------------------------- */

.inline-search {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
    min-width: 0;
}

.inline-search .form-control {
    flex: 1 1 12rem;
    min-width: 0;
}

/* A card nested inside another, stepped back so the hierarchy reads. */
.form-card--inset {
    background-color: var(--surface-muted);
}

/* ==========================================================================
   Dashboard donut charts
   ==========================================================================

   Server-rendered SVG. There is no inline style anywhere in the chart markup,
   because the Content-Security-Policy is style-src 'self' and a style attribute
   would simply be refused by the browser. Geometry therefore lives in SVG
   presentation attributes (stroke-dasharray, stroke-dashoffset) and every colour
   is set here, off a segment's modifier class.

   Following the stat cards above them so the dashboard reads as one page: same
   surface, border and radius.                                                        */

.stat-chart {
    display: flex;
    flex-direction: column;
    height: 100%;
    margin: 0;
    padding: 1.25rem 1.35rem;
    background-color: var(--surface);
    border: 1px solid var(--border);
    border-left: 4px solid var(--sky-500);
    border-radius: 0.85rem;
    box-shadow: 0 6px 18px rgba(2, 132, 199, 0.08);
}

.stat-chart-head {
    margin-bottom: 1rem;
}

.stat-chart-title {
    margin: 0;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text);
}

.stat-chart-caption {
    margin: 0.35rem 0 0;
    font-size: 0.85rem;
    color: var(--text-muted);
}

.stat-chart-empty {
    margin: 0;
    padding: 1.5rem 0;
    font-size: 0.92rem;
    color: var(--text-muted);
}

.stat-chart-body {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.25rem 1.75rem;
}

/* The donut is capped rather than fluid: past about 12rem it stops reading as a
   chart and starts looking like an empty page. */
.stat-chart-donut {
    position: relative;
    flex: 0 0 auto;
    width: 12rem;
    max-width: 100%;
}

.donut {
    display: block;
    width: 100%;
    height: auto;
}

/* The ring the segments are drawn on. Track and segments share a radius and
   stroke width, so a segment always covers the track exactly. */
.donut-track,
.donut-seg {
    fill: none;
    stroke-width: 5;
}

.donut-track {
    stroke: var(--border);
}

/* Student types, chosen by position so a school with three or four types needs
   nothing added here. Shades of the one blue ramp, so adjacent segments always
   differ in tone. */
.donut-seg--a {
    stroke: var(--sky-600);
}

.donut-seg--b {
    stroke: var(--sky-300);
}

.donut-seg--c {
    stroke: var(--sky-800);
}

.donut-seg--d {
    stroke: var(--sky-200);
}

/* Fees: settled against unsettled. */
.donut-seg--paid {
    stroke: var(--sky-600);
}

.donut-seg--owing {
    stroke: var(--sky-300);
}

.donut-center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin: 0;
    padding: 0 2.25rem;
    text-align: center;
    line-height: 1.15;
}

.donut-total {
    font-size: 1.35rem;
    font-weight: 700;
    color: var(--text);
    overflow-wrap: anywhere;
}

.donut-total-label {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* The legend carries the actual figures, which is what makes the colour-only
   ring usable at all. min-width: 0 lets a long amount wrap inside the column
   instead of pushing the row wider than the card. */
.stat-legend {
    flex: 1 1 14rem;
    min-width: 0;
    margin: 0;
}

.stat-legend-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    padding: 0.4rem 0;
    border-bottom: 1px solid var(--border);
}

.stat-legend-row:last-child {
    border-bottom: 0;
}

.stat-legend-term {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
    font-weight: 600;
    color: var(--text);
}

.stat-legend-value {
    margin: 0;
    text-align: right;
    font-variant-numeric: tabular-nums;
    color: var(--text);
}

.stat-legend-share {
    display: block;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
}

/* The legend's colour key. A fixed square rather than an inline-block, so the
   label text wraps at the column edge and never tucks under it. */
.stat-swatch {
    flex: 0 0 auto;
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 0.2rem;
}

.stat-swatch--a,
.stat-swatch--paid {
    background-color: var(--sky-600);
}

.stat-swatch--b,
.stat-swatch--owing {
    background-color: var(--sky-300);
}

.stat-swatch--c {
    background-color: var(--sky-800);
}

.stat-swatch--d {
    background-color: var(--sky-200);
}

/* On a narrow screen the donut would leave the legend about four characters
   wide, so it shrinks and the pair sits one above the other instead. */
@media (max-width: 420px) {
    .stat-chart-body {
        gap: 1rem;
    }

    .stat-chart-donut {
        width: 9.5rem;
    }

    .stat-legend {
        flex-basis: 100%;
    }
}

