/* ===========================================================================
   Mabest Academy SRMS — interface
   ---------------------------------------------------------------------------
   Load order: fonts.css, tokens.css, then this file.

   Class names match the markup the application already uses, so every page
   picks these styles up. Page-level <style> blocks should shrink to nothing
   over time: anything here is the shared answer.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   1. Reset and base
   --------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  overflow-x: hidden;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--grad-canvas);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  letter-spacing: var(--tracking-snug);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* clip, not hidden.
     overflow-x: hidden on <body> makes it a scrolling box, and position:
     sticky resolves against the nearest scrolling ancestor — so the sticky
     .topbar was sticking to a container that never scrolls while the document
     scrolled underneath it. The header simply slid off the top and never came
     back, which is most obvious on a phone where it is the only navigation.
     clip keeps the horizontal overflow hidden without creating that box.
     The hidden line stays first as a fallback for browsers without clip. */
  overflow-x: hidden;
  overflow-x: clip;
  /* Lining, tabular figures: score columns line up digit for digit. */
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1, "cv05" 1;
}

h1, h2, h3, h4, h5 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--ink-800);
}
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }
h5 { font-size: var(--text-base); }

p { margin: 0 0 var(--space-3); }
p:last-child { margin-bottom: 0; }

a { color: var(--brand-500); text-decoration: none; transition: color var(--transition-fast); }
a:hover { color: var(--brand-600); }

img, svg, video { max-width: 100%; height: auto; display: block; }
strong, b { font-weight: 650; color: var(--ink-800); }
small { font-size: var(--text-xs); }
code, kbd, pre { font-family: var(--font-mono); font-size: 0.92em; }
code {
  background: var(--surface-3);
  padding: 2px 6px;
  border-radius: var(--r-xs);
  color: var(--ink-600);
}
hr { border: 0; border-top: 1px solid var(--border); margin: var(--space-6) 0; }

/* A single, visible focus style — keyboard users can always see where they are,
   and it never shows for plain mouse clicks. */
:focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--brand-100); color: var(--ink-900); }

/* Slim scrollbars that match the palette rather than the OS default. */
* { scrollbar-width: thin; scrollbar-color: var(--line-300) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--line-300);
  border-radius: var(--r-full);
  border: 3px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--ink-200); background-clip: content-box; }

/* Anything wider than its parent scrolls inside its own box instead of
   pushing the whole page sideways. */
.main-content, .card, section, .section-card, .info-card,
.dashboard-split, .cards-grid { min-width: 0; max-width: 100%; }

/* ---------------------------------------------------------------------------
   2. Icons
   The sprite is inlined once per page; every icon is a <use> reference, so
   there is no icon font, no third-party script and no per-icon request.
   --------------------------------------------------------------------------- */
/* Both fill and stroke follow the text colour. An outline icon is drawn as a
   stroked shape with no fill, and marks those shapes with the two classes
   below — which ionicons' own stylesheet used to define. Without them the
   outlines fill solid and a calendar renders as a plain square, so they are
   declared here now that the stylesheet is gone. */
.ico {
  display: inline-block;
  width: 1.15em;
  height: 1.15em;
  flex-shrink: 0;
  fill: currentColor;
  stroke: currentColor;
  vertical-align: -0.17em;
  pointer-events: none;
}
.ionicon-fill-none { fill: none; }
.ionicon-stroke-width { stroke-width: 32px; }

.ico-wrap { display: inline-flex; align-items: center; justify-content: center; line-height: 0; }
.ico-wrap > .ico { width: 1em; height: 1em; }

/* ---------------------------------------------------------------------------
   3. Application shell
   --------------------------------------------------------------------------- */
.app-layout {
  display: flex;
  min-height: 100vh;
  position: relative;
  padding-left: var(--sidebar-w);
  max-width: 100%;
  transition: padding-left var(--transition-slow);
}

.main-content {
  flex: 1;
  min-width: 0;
  padding: var(--space-6) var(--space-7) var(--space-8);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: var(--content-max);
}

/* --- Sidebar ------------------------------------------------------------- */
.sidebar {
  position: fixed;
  top: 0; left: 0;
  width: var(--sidebar-w);
  height: 100vh;
  z-index: var(--z-sidebar);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-4);
  background: var(--grad-sidebar);
  color: #fff;
  box-shadow: 1px 0 0 rgba(255,255,255,.08) inset, 10px 0 40px rgba(10, 40, 80, .10);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  transition: width var(--transition-slow), left var(--transition-slow), padding var(--transition-slow);
}
/* A soft highlight stops the flat gradient looking like a solid block. */
.sidebar::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 60% at 0% 0%, rgba(255,255,255,.14), transparent 60%);
  pointer-events: none;
}
.sidebar > * { position: relative; }

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-1);
  border-bottom: 1px solid rgba(255,255,255,.16);
}
.sidebar-brand img {
  width: 38px; height: 38px;
  object-fit: contain;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 6px rgba(0,0,0,.18));
}
.sidebar-brand .brand-text {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: .02em;
  text-transform: uppercase;
}
.sidebar-brand .brand-text-link { color: #fff; }
.sidebar-brand .brand-text-link:hover { color: #fff; opacity: .85; }

.sidebar-collapse-btn {
  width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0;
  border-radius: var(--r-sm);
  background: rgba(255,255,255,.14);
  color: #fff;
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--transition-fast), transform var(--transition-fast);
}
.sidebar-collapse-btn:hover { background: rgba(255,255,255,.26); }
.sidebar-collapse-btn:active { transform: scale(.94); }

.nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }

.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 10px var(--space-3);
  border-radius: var(--r-md);
  color: rgba(255,255,255,.86);
  font-size: var(--text-base);
  font-weight: 550;
  line-height: 1.35;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.nav-item:hover { background: rgba(255,255,255,.12); color: #fff; }
.nav-item.active { background: rgba(255,255,255,.18); color: #fff; font-weight: 650; }
/* The active marker sits outside the padding so it reads as a page indicator
   rather than part of the button. */
.nav-item.active::before {
  content: "";
  position: absolute;
  left: calc(var(--space-4) * -1);
  top: 50%;
  transform: translateY(-50%);
  width: 4px; height: 22px;
  border-radius: 0 var(--r-xs) var(--r-xs) 0;
  background: var(--warn-400);
}
.nav-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; }
.nav-icon .ico, .nav-icon ion-icon { width: 19px; height: 19px; }
.nav-label { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--space-2); }

.nav-divider {
  display: flex; align-items: center; gap: var(--space-2);
  margin: var(--space-4) var(--space-2) var(--space-1);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: rgba(255,255,255,.52);
}
.nav-divider::after { content: ""; flex: 1; height: 1px; background: rgba(255,255,255,.16); }

.notification-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px;
  padding: 0 6px;
  border-radius: var(--r-full);
  background: var(--danger-500);
  color: #fff;
  font-size: var(--text-2xs);
  font-weight: 700;
  line-height: 1;
  box-shadow: 0 0 0 2px rgba(255,255,255,.18);
}
@keyframes badge-bump {
  0% { transform: scale(1); } 40% { transform: scale(1.35); } 100% { transform: scale(1); }
}
.badge-bump { animation: badge-bump 420ms var(--ease); }

.sidebar-footer {
  margin-top: auto;
  padding-top: var(--space-4);
  border-top: 1px solid rgba(255,255,255,.16);
}
.sidebar-footer .small { font-size: var(--text-xs); color: rgba(255,255,255,.7); line-height: 1.5; }
.sidebar-footer strong { display: block; color: #fff; font-size: var(--text-base); font-weight: 650; }

.sidebar-logout {
  width: 100%;
  justify-content: center;
  background: rgba(255,255,255,.14) !important;
  color: #fff !important;
  border: 1px solid rgba(255,255,255,.2) !important;
  box-shadow: none !important;
}
.sidebar-logout:hover { background: rgba(255,255,255,.24) !important; transform: none !important; }

/* --- Collapsed sidebar (desktop) ----------------------------------------- */
body.app-layout.sidebar-collapsed { padding-left: var(--sidebar-collapsed-w); }
.sidebar.collapsed { width: var(--sidebar-collapsed-w); padding-left: var(--space-2); padding-right: var(--space-2); }
.sidebar.collapsed .brand-text,
.sidebar.collapsed .nav-label,
.sidebar.collapsed .sidebar-footer .small,
.sidebar.collapsed .nav-divider span,
.sidebar.collapsed .sidebar-logout span { display: none; }
.sidebar.collapsed .nav-item { justify-content: center; padding-inline: 0; }
.sidebar.collapsed .nav-item.active::before { left: -8px; }
.sidebar.collapsed .sidebar-brand { justify-content: center; }
.sidebar.collapsed .nav-divider { justify-content: center; margin-inline: 0; }

/* --- Mobile drawer -------------------------------------------------------- */
.sidebar-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 26, 48, .5);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  z-index: var(--z-scrim);
  transition: opacity var(--transition-slow), visibility var(--transition-slow);
}
.sidebar-overlay.active { opacity: 1; visibility: visible; }

.hamburger-btn {
  display: none;
  align-items: center; justify-content: center;
  width: 42px; height: 42px;
  border: 1px solid var(--line-200);
  border-radius: var(--r-md);
  background: var(--surface-0);
  color: var(--brand-500);
  font-size: 22px;
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.hamburger-btn:hover { background: var(--surface-3); border-color: var(--line-300); }

/* ---------------------------------------------------------------------------
   4. Topbar
   --------------------------------------------------------------------------- */
.topbar {
  position: sticky;
  top: var(--space-2);
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--r-lg);
  background: rgba(255,255,255,.78);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border: 1px solid rgba(255,255,255,.7);
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition-slow), opacity var(--transition-slow), box-shadow var(--transition-slow);
}
/* Hides on the way down, returns the moment you scroll up — main.js toggles it. */
body.topbar-hidden .topbar {
  transform: translateY(calc(-100% - 20px));
  opacity: 0;
  pointer-events: none;
}

.page-title {
  flex: 1;
  min-width: 160px;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 800;
  letter-spacing: var(--tracking-tight);
  color: var(--ink-800);
}
.top-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* ---------------------------------------------------------------------------
   5. Buttons
   --------------------------------------------------------------------------- */
.btn, .btn-ghost, .big-btn {
  --btn-bg: var(--grad-brand);
  --btn-fg: #fff;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 42px;
  padding: 0 var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  box-shadow: var(--shadow-xs);
  transition: transform var(--transition-fast), box-shadow var(--transition-fast),
              background var(--transition-fast), border-color var(--transition-fast), filter var(--transition-fast);
}
.btn:hover, .big-btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-sm); filter: saturate(1.08) brightness(1.03); }
.btn:active, .big-btn:active { transform: translateY(0); box-shadow: var(--shadow-xs); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .55; cursor: not-allowed; transform: none; filter: none; }

.btn.outline, .big-btn.outline, .btn-secondary {
  --btn-bg: var(--surface-0);
  --btn-fg: var(--brand-600);
  border-color: var(--line-300);
  box-shadow: var(--shadow-xs);
}
.btn.outline:hover, .big-btn.outline:hover, .btn-secondary:hover {
  background: var(--brand-50);
  border-color: var(--brand-200);
  filter: none;
}

.btn.danger { --btn-bg: linear-gradient(135deg, var(--danger-600), #f05252); }
.btn.success { --btn-bg: linear-gradient(135deg, var(--success-600), var(--success-500)); }
.btn.warn { --btn-bg: linear-gradient(135deg, var(--warn-600), var(--warn-500)); }

.btn-ghost {
  --btn-bg: transparent;
  --btn-fg: var(--ink-600);
  min-height: 38px;
  padding: 0 var(--space-3);
  border-color: transparent;
  box-shadow: none;
  font-weight: 550;
}
.btn-ghost:hover { background: var(--surface-3); color: var(--ink-800); transform: none; filter: none; }

.btn.sm, .btn-ghost.sm { min-height: 34px; padding: 0 var(--space-3); font-size: var(--text-sm); }
.btn.block { width: 100%; }
.button-group, .btn-group { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }

/* A plain inline action inside a table row. */
.link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 7px;
  border-radius: var(--r-xs);
  color: var(--ink-500);
  font-size: var(--text-sm);
  font-weight: 550;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.link:hover { background: var(--surface-3); color: var(--brand-600); }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 36px; padding: 0 10px;
  border: 1px solid var(--line-200);
  border-radius: var(--r-sm);
  background: var(--surface-0);
  color: var(--ink-500);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: all var(--transition-fast);
}
.icon-btn:hover { border-color: var(--brand-200); color: var(--brand-600); background: var(--brand-50); }

/* ---------------------------------------------------------------------------
   6. Surfaces
   --------------------------------------------------------------------------- */
.card, .section-card, .info-card {
  position: relative;
  padding: var(--space-5);
  border: 1px solid var(--line-200);
  border-radius: var(--r-xl);
  background: var(--surface-0);
  box-shadow: var(--shadow-sm);
}
.card > h3:first-child, .section-card > h3:first-child { margin-bottom: var(--space-4); }

.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--space-4);
  width: 100%;
}

/* --- Stat cards ------------------------------------------------------------
   The colour comes from the gradient; the icon is a large, low-contrast
   watermark so the number stays the loudest thing in the tile. */
.stat-card {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-height: 128px;
  padding: var(--space-5);
  border: 0;
  border-radius: var(--r-xl);
  background: var(--grad-brand);
  color: #fff;
  box-shadow: var(--shadow);
  transition: transform var(--transition-normal), box-shadow var(--transition-normal);
}
.stat-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.stat-card::after {
  content: "";
  position: absolute;
  right: -28px; bottom: -34px;
  width: 136px; height: 136px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,.10);
}
.stat-title {
  font-size: var(--text-xs);
  font-weight: 650;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: rgba(255,255,255,.88);
}
.stat-value {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: var(--tracking-tight);
}
.stat-value small { font-size: .5em; font-weight: 600; opacity: .8; }
.stat-icon {
  position: absolute;
  right: var(--space-4); bottom: var(--space-4);
  font-size: 40px;
  color: rgba(255,255,255,.26);
  line-height: 1;
}
.stat-icon .ico, .stat-icon ion-icon { width: 40px; height: 40px; }
.stat-action {
  display: inline-flex; align-items: center; gap: 5px;
  margin-top: auto;
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 650;
  opacity: .92;
}
.stat-action:hover { color: #fff; opacity: 1; gap: 9px; }

.stat-card.theme-cyan   { background: linear-gradient(135deg, var(--cyan-600), var(--cyan-500)); }
.stat-card.theme-purple { background: linear-gradient(135deg, var(--violet-700), var(--violet-400)); }
.stat-card.theme-amber  { background: linear-gradient(135deg, var(--warn-600), var(--warn-400)); }
.stat-card.theme-green  { background: linear-gradient(135deg, var(--success-600), var(--success-500)); }
.stat-card.theme-teal   { background: linear-gradient(135deg, var(--cyan-700), var(--cyan-400)); }

/* --- Hero banner ----------------------------------------------------------- */
.hero-banner {
  position: relative;
  overflow: hidden;
  display: flex;
  /* The avatar and the text are siblings here. Without a gap they touched —
     the initials circle sat flush against the name, reading as one smudge. */
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-7);
  border-radius: var(--r-2xl);
  background: var(--grad-hero);
  color: #fff;
  box-shadow: var(--shadow-md);
}
/* A soft highlight from the top-left stops the flat gradient looking like a
   solid block of colour behind the text. */
.hero-banner::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(90% 120% at 0% 0%, rgba(255,255,255,.22), transparent 60%),
    radial-gradient(70% 100% at 100% 100%, rgba(0,0,0,.14), transparent 55%);
  pointer-events: none;
}
.hero-banner.theme-blue  { background: linear-gradient(135deg, var(--brand-600), var(--cyan-500)); }
.hero-banner.theme-teal  { background: linear-gradient(135deg, var(--cyan-700), var(--cyan-400)); }
.hero-banner.theme-amber { background: linear-gradient(135deg, var(--warn-600), var(--warn-400)); }
.hero-banner.theme-green { background: linear-gradient(135deg, var(--success-700), var(--success-500)); }
.hero-content {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  width: 100%;
}
.hero-greeting {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: rgba(255,255,255,.82);
}
.hero-name {
  margin: 0;
  font-family: var(--font-display);
  /* Scales with the viewport: a long name plus an ID used to run past the
     edge of the banner at the fixed size. */
  font-size: clamp(var(--text-2xl), 3.2vw, var(--text-4xl));
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: var(--tracking-tight);
  color: #fff;
  overflow-wrap: anywhere;
  text-shadow: 0 1px 2px rgba(0,0,0,.12);
}
/* The ID after the name is secondary — same line, quieter. */
.hero-name span { font-weight: 650; opacity: .72; font-size: .72em; }
.hero-meta { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-4) 0 0; }
.hero-meta span {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 13px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,.16);
  border: 1px solid rgba(255,255,255,.22);
  backdrop-filter: blur(2px);
  font-size: var(--text-sm);
  font-weight: 550;
  white-space: nowrap;
}
.hero-meta span .ico { width: 15px; height: 15px; opacity: .85; }
.hero-illustration { flex-shrink: 0; opacity: .95; }
.hero-illustration svg { width: clamp(110px, 16vw, 190px); height: auto; }
.auth-decoration, .school-decoration { position: absolute; opacity: .16; pointer-events: none; }

/* ---------------------------------------------------------------------------
   7. Tables
   --------------------------------------------------------------------------- */
.table-wrap, .table-scroll, .scroll-x, .assessment-table-wrap {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line-200);
  border-radius: var(--r-lg);
  background: var(--surface-0);
}
.card > .table-wrap, .section-card > .table-wrap { box-shadow: none; }

.table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  min-width: 640px;
  font-size: var(--text-base);
}
.table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: var(--space-3) var(--space-4);
  background: var(--surface-1);
  border-bottom: 1px solid var(--line-200);
  color: var(--ink-400);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
}
.table tbody td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line-100);
  color: var(--ink-700);
  vertical-align: middle;
}
.table tbody tr { transition: background var(--transition-fast); }
.table tbody tr:hover { background: var(--surface-1); }
.table tbody tr:last-child td { border-bottom: 0; }
.table .actions { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.table td.numeric, .table th.numeric { text-align: right; font-variant-numeric: tabular-nums; }

/* Avatars in list rows */
.avatar, .avatar-fallback {
  width: 38px; height: 38px;
  min-width: 38px;
  border-radius: var(--r-full);
  object-fit: cover;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--text-sm);
  font-weight: 700;
}
.avatar-fallback { background: var(--grad-brand); color: #fff; }

/* In the student and teacher lists, "avatar" is the <td>, not the picture.
   The cell therefore took the 38px circle — which does nothing to a table
   cell — and the <img> inside it was left completely unstyled, so an uploaded
   photo rendered at its natural size: a small, square, uncropped thumbnail
   sitting beside colleagues shown as neat round initials. */
td.avatar {
  width: 56px;
  display: table-cell;         /* undo the inline-flex above for the cell */
  border-radius: 0;
}
.avatar > img {
  width: 38px; height: 38px;
  min-width: 38px;
  display: block;
  border-radius: var(--r-full);
  object-fit: cover;
  background: var(--surface-2);
}

/* ---------------------------------------------------------------------------
   8. Forms
   --------------------------------------------------------------------------- */
label { display: block; font-size: var(--text-sm); font-weight: 600; color: var(--ink-600); }

.form-group { margin-bottom: var(--space-4); }
.form-group > label { margin-bottom: 6px; }
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--space-4);
}
.form-section { margin-bottom: var(--space-6); }
.form-section > h3 { margin-bottom: var(--space-4); padding-bottom: var(--space-3); border-bottom: 1px solid var(--line-200); }

/* Matched by exclusion, not by listing every type. 37 inputs in this
   codebase carry no type attribute at all — they default to text but never
   matched input[type="text"], so they rendered as bare browser boxes beside
   properly styled selects. Excluding the handful that must keep their native
   appearance also covers any type added later.

   The exclusions sit inside :where() so they cost nothing in specificity. Ten
   :not() clauses would otherwise make this (0,10,1) — stronger than any sane
   override — and it silently beat four rules that were meant to win:
   the 38px left padding that keeps a field's leading icon off its text, the
   44px right padding that keeps the show-password eye off it, .pwd-input, and
   input[readonly]. This is a default; it must be the weakest rule here. */
input:where(:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]):not([type="hidden"]):not([type="range"]):not([type="color"])),
select, textarea {
  width: 100%;
  min-height: 42px;
  padding: 10px var(--space-3);
  border: 1px solid var(--line-300);
  border-radius: var(--r-md);
  background: var(--surface-0);
  color: var(--ink-700);
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: 1.4;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);
  appearance: none;
}
textarea { min-height: 96px; padding-top: var(--space-3); resize: vertical; line-height: var(--leading-normal); }

input:hover:not(:disabled), select:hover:not(:disabled), textarea:hover:not(:disabled) { border-color: var(--brand-200); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand-500);
  box-shadow: var(--ring);
}
input::placeholder, textarea::placeholder { color: var(--ink-200); }
input:disabled, select:disabled, textarea:disabled,
.readonly-field, input[readonly] {
  background: var(--surface-3);
  color: var(--ink-400);
  cursor: not-allowed;
  border-color: var(--line-200);
}

/* Native select needs its own chevron once appearance is stripped. */
select {
  padding-right: var(--space-7);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2364809b' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  cursor: pointer;
}

input[type="checkbox"], input[type="radio"] {
  width: 18px; height: 18px;
  min-width: 18px;
  accent-color: var(--brand-500);
  cursor: pointer;
}
input[type="file"] {
  width: 100%;
  padding: var(--space-3);
  border: 1.5px dashed var(--line-300);
  border-radius: var(--r-md);
  background: var(--surface-1);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}
input[type="file"]:hover { border-color: var(--brand-300); background: var(--brand-50); }

.field-note, .note, .hint {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
  padding: 10px var(--space-3);
  border: 1px solid var(--brand-100);
  border-radius: var(--r-md);
  background: var(--brand-50);
  color: var(--ink-600);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}
.field-note .ico, .note .ico { color: var(--brand-500); margin-top: 1px; }

/* These are flex so an icon can sit beside the message. That only works when
   the message is a single item: a sentence written loose with an <em> or a
   <strong> in it becomes one flex item per run, and the browser deals it out
   into narrow columns — "Complete = all 16 ratings filled" / "and" / "a
   teacher comment." each in its own.
   A note with no icon has nothing to lay out beside, so it is simply a block
   and the text flows. Where there is an icon, anything that is not the icon is
   told to take the remaining width. */
.field-note:not(:has(> .ico)):not(:has(> svg)),
.note:not(:has(> .ico)):not(:has(> svg)),
.hint:not(:has(> .ico)):not(:has(> svg)) { display: block; }
.field-note > :not(svg):not(.ico),
.note > :not(svg):not(.ico),
.hint > :not(svg):not(.ico) { min-width: 0; }

/* The legend under the assessment list: one quiet line, pushed to the end. */
.legend-note { margin-left: auto; margin-bottom: 0; }
.note.plain { border: 0; background: none; padding: 0; color: var(--muted); }

.upload-status { margin: 6px 0 0; font-size: var(--text-sm); }
.upload-status[data-tone="ok"]    { color: var(--success-600); }
.upload-status[data-tone="warn"]  { color: var(--warn-600); }
.upload-status[data-tone="error"] { color: var(--danger-600); }
.upload-status[data-tone="info"]  { color: var(--muted); }

/* Password reveal toggle sits inside the field. */
.pw-toggle-wrap, .password-wrapper { position: relative; }
.pw-toggle-wrap input, .password-wrapper input { padding-right: 44px; }
.pw-toggle, .password-toggle, .toggle-password {
  position: absolute;
  right: 6px; top: 50%;
  transform: translateY(-50%);
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-300);
  cursor: pointer;
  transition: color var(--transition-fast), background var(--transition-fast);
}
.pw-toggle:hover, .password-toggle:hover, .toggle-password:hover { color: var(--brand-500); background: var(--surface-3); }

/* ---------------------------------------------------------------------------
   9. Feedback
   --------------------------------------------------------------------------- */
.toast, .alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line-200);
  border-left: 3px solid var(--ink-300);
  border-radius: var(--r-md);
  background: var(--surface-0);
  color: var(--ink-700);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  box-shadow: var(--shadow-sm);
  transition: opacity var(--transition-slow), transform var(--transition-slow);
}
.toast.success, .alert.success { border-left-color: var(--success-600); background: var(--success-100); color: var(--success-700); border-color: #a7f3cf; }
.toast.error,   .alert.error   { border-left-color: var(--danger-600);  background: var(--danger-50);  color: var(--danger-700);  border-color: var(--danger-100); }
.toast.warn,    .alert.warn    { border-left-color: var(--warn-600);    background: var(--warn-50);    color: var(--warn-700);    border-color: var(--warn-100); }
.toast.info,    .alert.info    { border-left-color: var(--brand-500);   background: var(--brand-50);   color: var(--brand-800);   border-color: var(--brand-100); }
.undo-btn {
  margin-left: auto;
  border: 0; background: none;
  color: inherit;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}

.pill, .badge, .chip-count {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  color: var(--ink-600);
  font-size: var(--text-xs);
  font-weight: 650;
  line-height: 1.5;
  white-space: nowrap;
}
.pill.success { background: var(--success-100); color: var(--success-700); }
.pill.danger  { background: var(--danger-50);   color: var(--danger-700); }
.pill.warn    { background: var(--warn-50);     color: var(--warn-700); }
.pill.info    { background: var(--brand-50);    color: var(--brand-800); }

/* Grade badges keep the colours the report card already used. */
.grade-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 34px; padding: 3px 9px;
  border-radius: var(--r-sm);
  font-size: var(--text-xs);
  font-weight: 750;
  letter-spacing: .02em;
}
.grade-badge.a-star, .grade-badge.a { background: var(--success-50); color: var(--success-700); }
.grade-badge.b-plus, .grade-badge.b { background: #dbeafe; color: #1d4ed8; }
.grade-badge.c { background: #fef9c3; color: var(--warn-700); }
.grade-badge.d, .grade-badge.e { background: #ffedd5; color: var(--orange-600); }
.grade-badge.f { background: var(--danger-50); color: #b91c1c; }

/* Empty states: say what is missing and what to do about it. */
.no-results, .info-empty, .empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-5);
  color: var(--muted);
  text-align: center;
}
.no-results .ico, .info-empty .ico, .empty-state .ico { width: 44px; height: 44px; opacity: .32; }
.empty-state h4 { color: var(--ink-600); }

/* ---------------------------------------------------------------------------
   10. Modals and dialogs
   --------------------------------------------------------------------------- */
.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: rgba(10, 26, 48, .52);
  backdrop-filter: blur(3px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-normal), visibility var(--transition-normal);
}
.modal[aria-hidden="false"] { opacity: 1; visibility: visible; }
.modal-panel {
  /* Positioning context: some modals place their close button absolutely,
     and without this it anchors to the overlay and lands outside the card. */
  position: relative;
  width: 100%;
  max-width: 660px;
  max-height: 88vh;
  overflow-y: auto;
  padding: var(--space-6);
  border-radius: var(--r-xl);
  background: var(--surface-0);
  box-shadow: var(--shadow-xl);
  transform: translateY(14px) scale(.985);
  transition: transform var(--transition-normal);
}
.modal[aria-hidden="false"] .modal-panel { transform: none; }
.modal-panel header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--line-200);
}
.modal-panel header h3 { flex: 1; min-width: 0; }
.modal-panel > header .close-modal {
  width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 0; padding: 0;
  border-radius: var(--r-sm);
  font-size: 20px;
  line-height: 1;
}

.confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-confirm);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: rgba(10, 26, 48, .56);
  backdrop-filter: blur(3px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-normal), visibility var(--transition-normal);
}
.confirm-overlay.visible { opacity: 1; visibility: visible; }
.confirm-box {
  width: 100%;
  max-width: 420px;
  padding: var(--space-6);
  border-radius: var(--r-xl);
  background: var(--surface-0);
  box-shadow: var(--shadow-xl);
  text-align: center;
}
.confirm-box h4 { margin-bottom: var(--space-2); font-size: var(--text-lg); }
.confirm-box p { color: var(--ink-500); margin-bottom: var(--space-5); }
.confirm-box .btn-group { justify-content: center; }

/* ---------------------------------------------------------------------------
   11. Pagination
   --------------------------------------------------------------------------- */
.pagination-wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-5);
}
.pagination-summary { color: var(--muted); font-size: var(--text-sm); }
.pagination { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.pagination a, .pagination span {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 38px; height: 38px;
  padding: 0 10px;
  border: 1px solid var(--line-200);
  border-radius: var(--r-sm);
  background: var(--surface-0);
  color: var(--ink-600);
  font-size: var(--text-sm);
  font-weight: 600;
  transition: all var(--transition-fast);
}
.pagination a:hover { border-color: var(--brand-300); color: var(--brand-600); background: var(--brand-50); }
.pagination .current { background: var(--grad-brand); border-color: transparent; color: #fff; }
.pagination .gap { border: 0; background: none; color: var(--ink-200); min-width: 22px; }

/* ---------------------------------------------------------------------------
   12. Utilities
   --------------------------------------------------------------------------- */
.muted { color: var(--muted); }
.small { font-size: var(--text-sm); }
.text-right { text-align: right; }
.text-center { text-align: center; }
.nowrap { white-space: nowrap; }
.stack { display: flex; flex-direction: column; gap: var(--space-4); }
.row { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.spread { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
.hidden { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.app-footer {
  margin-top: auto;
  padding-top: var(--space-6);
  border-top: 1px solid var(--line-200);
  color: var(--muted);
  font-size: var(--text-xs);
  text-align: center;
}

/* Skeleton shimmer for content that is still loading. */
@keyframes skeleton { to { background-position-x: -200%; } }
.skeleton {
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--surface-3) 40%, var(--surface-4) 50%, var(--surface-3) 60%);
  background-size: 200% 100%;
  animation: skeleton 1.3s linear infinite;
  color: transparent !important;
}

/* Turbo's progress bar, in the brand colours. */
.turbo-progress-bar { height: 3px; background: var(--grad-brand); }

/* ---------------------------------------------------------------------------
   13. Responsive
   --------------------------------------------------------------------------- */
@media (max-width: 1200px) {
  :root { --sidebar-w: 248px; }
  .main-content { padding: var(--space-5) var(--space-5) var(--space-7); }
}

@media (max-width: 980px) {
  .hero-content { flex-direction: column; align-items: flex-start; }
  .hero-illustration { align-self: flex-end; margin-top: calc(var(--space-5) * -1); }
}

@media (max-width: 880px) {
  .app-layout, body.app-layout.sidebar-collapsed { padding-left: 0; }
  .sidebar { left: calc(var(--sidebar-w) * -1); box-shadow: var(--shadow-xl); }
  .sidebar.active { left: 0; }
  /* There is no collapsed rail on a phone. The sidebar is a drawer that is
     either open or shut, so the collapsed class is neutralised here rather
     than relied on never being set — it leaked through once and left a narrow
     strip with the labels wrapped over each other. */
  .sidebar.collapsed { width: var(--sidebar-w); min-width: var(--sidebar-w); padding: var(--space-5) var(--space-4); }
  .sidebar.collapsed .nav-icon { margin-inline: 0; }
  .sidebar.collapsed .sidebar-account-text,
  .sidebar.collapsed .sidebar-account-go { display: flex; }
  .sidebar.collapsed .sidebar-account { justify-content: flex-start; padding-inline: var(--space-3); }
  .sidebar.collapsed .brand-text,
  .sidebar.collapsed .nav-label,
  .sidebar.collapsed .sidebar-footer .small,
  .sidebar.collapsed .nav-divider span { display: revert; }
  .sidebar.collapsed .nav-item { justify-content: flex-start; padding-inline: var(--space-3); }
  .hamburger-btn { display: inline-flex; }
  .main-content { padding: var(--space-4) var(--space-4) var(--space-7); gap: var(--space-4); }
  .topbar { top: 0; border-radius: var(--r-md); }
  .page-title { font-size: var(--text-lg); min-width: 0; }
  .hero-banner { padding: var(--space-5); }
}

@media (max-width: 640px) {
  .cards-grid { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-3); }
  .stat-card { min-height: 108px; padding: var(--space-4); }
  .stat-icon { font-size: 30px; right: var(--space-3); bottom: var(--space-3); }
  .stat-icon .ico, .stat-icon ion-icon { width: 30px; height: 30px; }
  .card, .section-card, .info-card { padding: var(--space-4); border-radius: var(--r-lg); }
  .modal { padding: var(--space-3); }
  .modal-panel { padding: var(--space-5); max-height: 92vh; }
  .top-actions { width: 100%; }
  .top-actions .btn { flex: 1; }
}

@media (max-width: 420px) {
  .cards-grid { grid-template-columns: 1fr 1fr; }
  .hero-name { font-size: var(--text-2xl); }
  .hero-illustration { display: none; }
  .btn, .btn-ghost { font-size: var(--text-sm); }
}

/* Fingertips need a larger target than a mouse pointer. The halo grows the
   hit area without moving anything on screen. */
@media (pointer: coarse), (max-width: 880px) {
  .icon-btn, .stat-action, .link, .undo-btn, .close-modal,
  .sidebar-collapse-btn, .hamburger-btn { position: relative; }
  /* The password toggles are absolutely positioned inside their field. This
     block used to make them relative too, which dropped the eye out of the
     box and left it sitting under the field on every phone. They are already
     their own positioning context, so the halo below still anchors to them. */
  .password-toggle, .toggle-password, .pw-toggle, .toggle-eye { position: absolute; }
  .icon-btn::after, .password-toggle::after, .toggle-password::after, .pw-toggle::after,
  .stat-action::after, .link::after, .undo-btn::after, .close-modal::after,
  .sidebar-collapse-btn::after, .hamburger-btn::after, .toggle-eye::after {
    content: "";
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 100%; height: 100%;
    min-width: 44px; min-height: 44px;
  }
  input[type="checkbox"], input[type="radio"] { width: 24px; height: 24px; min-width: 24px; }
  .table tbody td, .table thead th { padding: var(--space-4) var(--space-3); }
}

/* ---------------------------------------------------------------------------
   14. Print — report cards and class lists
   --------------------------------------------------------------------------- */
@media print {
  .sidebar, .sidebar-overlay, .topbar, .hamburger-btn,
  .pagination-wrap, .quick-actions, .hero-banner, .app-footer, .no-print { display: none !important; }
  body { background: #fff; }
  .app-layout { padding-left: 0; }
  .main-content { padding: 0; max-width: none; }
  .card, .section-card, .table-wrap { border: 0; box-shadow: none; padding: 0; }
  .table thead th { position: static; background: #fff; color: #000; }
  a { color: #000; text-decoration: none; }
}

/* ===========================================================================
   15. Links that act as buttons
   ---------------------------------------------------------------------------
   Most actions in this app are <a> elements — Logout, New Account, Full audit
   log — so they arrived with the browser's underline. Anything that reads as
   a control is declared here so the underline never comes back, while real
   prose links keep theirs on hover.
   =========================================================================== */
.btn, .btn-ghost, .big-btn, .btn-secondary, .link, .nav-item, .stat-action,
.user-chip, .auth-btn, .icon-btn, .class-chip, .comment-suggestion, .sidebar-logout,
.pagination a, .quick-actions a, .brand-text-link {
  text-decoration: none !important;
}
.btn:hover, .btn-ghost:hover, .link:hover, .nav-item:hover,
.stat-action:hover, .pagination a:hover, .quick-actions a:hover { text-decoration: none; }

/* Prose links inside body copy still look like links. */
.card p > a:not(.btn):not(.link),
.field-note a, .note a, .toast a { text-decoration: underline; text-underline-offset: 2px; }

/* ===========================================================================
   16. Sidebar — expanded and folded
   ---------------------------------------------------------------------------
   Folded is a real state, not a squeezed version of the expanded one: labels
   go, icons centre, and the name appears as a tooltip on hover so the nav is
   still usable at 76px. The toggle is remembered per user in localStorage.
   =========================================================================== */

/* --- Expanded ------------------------------------------------------------- */
.sidebar { --nav-gap: var(--space-3); }

.sidebar-brand img { transition: width var(--transition-slow), height var(--transition-slow); }

/* --- Folded ---------------------------------------------------------------- */
.sidebar.collapsed {
  width: var(--sidebar-collapsed-w);
  padding-left: 10px;
  padding-right: 10px;
  align-items: stretch;
}
.sidebar.collapsed .sidebar-brand {
  flex-direction: column;
  gap: var(--space-2);
  padding-bottom: var(--space-3);
}
.sidebar.collapsed .sidebar-brand img { width: 34px; height: 34px; }
.sidebar.collapsed .sidebar-collapse-btn { width: 32px; height: 32px; }

.sidebar.collapsed .nav { gap: 4px; }
.sidebar.collapsed .nav-item {
  justify-content: center;
  padding: 11px 0;
  border-radius: var(--r-md);
}
.sidebar.collapsed .nav-icon { width: auto; }
.sidebar.collapsed .nav-icon .ico { width: 21px; height: 21px; }

/* The badge cannot sit beside a hidden label, so it rides the icon. */
.sidebar.collapsed .nav-label {
  position: absolute;
  top: 5px; right: 9px;
  width: auto;
}
.sidebar.collapsed .nav-label .notification-badge {
  min-width: 17px; height: 17px;
  padding: 0 4px;
  font-size: 10px;
}

/* The label returns as a tooltip, built from the same text via a data
   attribute set in main.js, so there is nothing extra to keep in sync. */
.sidebar.collapsed .nav-item[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  left: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%) translateX(-4px);
  z-index: var(--z-overlay);
  padding: 7px 11px;
  border-radius: var(--r-sm);
  background: var(--ink-900);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  box-shadow: var(--shadow-md);
  transition: opacity var(--transition-fast), transform var(--transition-fast), visibility var(--transition-fast);
}
.sidebar.collapsed .nav-item[data-tip]::before {
  /* The little arrow. It replaces the active marker, which is why the
     collapsed active state is drawn with an inset shadow instead. */
  content: "";
  position: absolute;
  left: calc(100% + 4px);
  top: 50%;
  transform: translateY(-50%) translateX(-4px);
  z-index: var(--z-overlay);
  border: 6px solid transparent;
  border-right-color: var(--ink-900);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-fast), transform var(--transition-fast), visibility var(--transition-fast);
  background: none;
  width: 0; height: 0;
  border-radius: 0;
}
.sidebar.collapsed .nav-item[data-tip]:hover::after,
.sidebar.collapsed .nav-item[data-tip]:focus-visible::after,
.sidebar.collapsed .nav-item[data-tip]:hover::before,
.sidebar.collapsed .nav-item[data-tip]:focus-visible::before {
  opacity: 1;
  visibility: visible;
  transform: translateY(-50%) translateX(0);
}
.sidebar.collapsed .nav-item.active {
  background: rgba(255,255,255,.2);
  box-shadow: inset 3px 0 0 var(--warn-400);
}

.sidebar.collapsed .nav-divider {
  margin: var(--space-3) 0 var(--space-1);
  justify-content: center;
}
.sidebar.collapsed .nav-divider::after { display: none; }
.sidebar.collapsed .nav-divider::before {
  content: "";
  width: 24px; height: 1px;
  background: rgba(255,255,255,.22);
}

.sidebar.collapsed .sidebar-footer { padding-top: var(--space-3); text-align: center; }
.sidebar.collapsed .sidebar-logout { padding: 0; min-height: 40px; }

/* ===========================================================================
   17. Components carried over from the previous stylesheet
   ---------------------------------------------------------------------------
   These class names appear in the page templates. They are rebuilt on the
   tokens above rather than copied across, so they share the spacing, radii
   and elevation of everything else.
   =========================================================================== */

/* --- Sign-in page ---------------------------------------------------------- */
.auth-layout {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: var(--space-6) var(--space-4);
  position: relative;
  overflow: hidden;
}
.auth-layout::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(42rem 30rem at 12% -10%, rgba(15,116,255,.16), transparent 60%),
    radial-gradient(34rem 26rem at 104% 112%, rgba(0,192,255,.18), transparent 62%);
  pointer-events: none;
}
.auth-card {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 430px;
  padding: var(--space-8) var(--space-7);
  border: 1px solid rgba(255,255,255,.7);
  border-radius: var(--r-2xl);
  background: rgba(255,255,255,.9);
  backdrop-filter: saturate(170%) blur(18px);
  -webkit-backdrop-filter: saturate(170%) blur(18px);
  box-shadow: var(--shadow-lg);
}
.auth-brand {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
  text-align: center;
}
.auth-brand img { width: min(190px, 64%); height: auto; }
.auth-brand h1 { font-size: var(--text-2xl); }
.auth-brand p { color: var(--muted); font-size: var(--text-sm); margin: 0; }
.auth-hint { margin-top: var(--space-5); text-align: center; color: var(--muted); font-size: var(--text-sm); line-height: var(--leading-snug); }
.auth-form-row { margin-bottom: var(--space-4); }
.auth-btn { width: 100%; min-height: 46px; margin-top: var(--space-2); font-size: var(--text-md); }
.auth-error {
  display: flex; align-items: flex-start; gap: var(--space-2);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--danger-100);
  border-left: 3px solid var(--danger-600);
  border-radius: var(--r-md);
  background: var(--danger-50);
  color: var(--danger-700);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  white-space: pre-line;
}
.input-wrap { position: relative; }
.input-wrap input { padding-right: 44px; }
.toggle-eye {
  position: absolute;
  right: 6px; top: 50%;
  transform: translateY(-50%);
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-300);
  cursor: pointer;
  transition: color var(--transition-fast), background var(--transition-fast);
}
.toggle-eye:hover { color: var(--brand-500); background: var(--surface-3); }
.auth-decoration { position: absolute; opacity: .13; pointer-events: none; }
.auth-decoration.d1 { top: 8%;  left: 6%;  width: 90px; }
.auth-decoration.d2 { top: 16%; right: 9%; width: 76px; }
.auth-decoration.d3 { bottom: 14%; left: 11%; width: 84px; }
.auth-decoration.d4 { bottom: 9%; right: 7%; width: 70px; }

/* --- Avatars in the topbar and hero ---------------------------------------- */
.user-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 5px 12px 5px 5px;
  border: 1px solid var(--line-200);
  border-radius: var(--r-full);
  background: var(--surface-0);
  color: var(--ink-700);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.user-chip:hover { border-color: var(--brand-200); box-shadow: var(--shadow-xs); color: var(--ink-800); }
.avatar-mini {
  width: 30px; height: 30px;
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden;
  border-radius: var(--r-full);
  background: var(--grad-brand);
  color: #fff;
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1;
}
.avatar-mini img { width: 100%; height: 100%; object-fit: cover; }
.user-meta { display: flex; flex-direction: column; line-height: 1.25; text-align: left; }
.user-meta strong { font-size: var(--text-sm); font-weight: 650; }
.user-meta small { color: var(--muted); font-size: var(--text-2xs); }

.hero-avatar-banner .hero-content { align-items: center; }
.hero-avatar {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.hero-avatar img,
.hero-avatar-fallback {
  width: 78px; height: 78px;
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden;
  border-radius: var(--r-full);
  /* A double ring — a translucent band inside a soft outer glow — so the
     avatar sits on the gradient instead of being cut out of it. */
  border: 2px solid rgba(255,255,255,.55);
  box-shadow: 0 0 0 5px rgba(255,255,255,.14), var(--shadow-md);
  background: rgba(255,255,255,.22);
  object-fit: cover;
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: 800;
  letter-spacing: .5px;
  color: #fff;
}

/* Narrow screens: the avatar goes above the text and everything centres,
   rather than the name being squeezed into a column beside a circle. */
@media (max-width: 720px) {
  .hero-avatar-banner { flex-direction: column; align-items: center; text-align: center; gap: var(--space-4); }
  .hero-avatar-banner .hero-content { justify-content: center; }
  .hero-avatar-banner .hero-meta { justify-content: center; }
  .hero-avatar img, .hero-avatar-fallback { width: 64px; height: 64px; }
}
.hero-decorations { position: absolute; inset: 0; pointer-events: none; opacity: .18; }

/* --- Quick actions and tinted tiles ----------------------------------------- */
.quick-actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--space-3); }
.quick-actions a {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--line-200);
  border-radius: var(--r-lg);
  background: var(--surface-0);
  color: var(--ink-700);
  font-weight: 600;
  box-shadow: var(--shadow-xs);
  transition: transform var(--transition-fast), box-shadow var(--transition-fast), border-color var(--transition-fast);
}
.quick-actions a:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); border-color: var(--brand-200); }
.quick-actions a .ico { width: 20px; height: 20px; color: var(--brand-500); }
.quick-actions a.alt-purple .ico { color: var(--violet-600); }
.quick-actions a.alt-teal   .ico { color: var(--cyan-700); }
.quick-actions a.alt-amber  .ico { color: var(--warn-600); }
.quick-actions a.alt-green  .ico { color: var(--success-600); }

.stat-card.blue  { background: var(--grad-brand); }
.stat-card.green { background: linear-gradient(135deg, var(--success-600), var(--success-500)); }
.stat-card.amber { background: linear-gradient(135deg, var(--warn-600), var(--warn-400)); }
.stat-card.theme-orange { background: linear-gradient(135deg, var(--orange-600), var(--orange-400)); }

.glass-card {
  border: 1px solid rgba(255,255,255,.7);
  border-radius: var(--r-xl);
  background: rgba(255,255,255,.72);
  backdrop-filter: saturate(170%) blur(14px);
  -webkit-backdrop-filter: saturate(170%) blur(14px);
  box-shadow: var(--shadow-sm);
}

/* --- Per-subject performance bars -------------------------------------------- */
.perf-list { display: flex; flex-direction: column; gap: var(--space-3); }
.perf-row {
  display: grid;
  grid-template-columns: minmax(96px, 1.3fr) 3fr auto;
  align-items: center;
  gap: var(--space-3);
}
.perf-name { font-size: var(--text-sm); font-weight: 600; color: var(--ink-600); }
.perf-bar { height: 9px; border-radius: var(--r-full); background: var(--surface-3); overflow: hidden; }
.perf-fill { height: 100%; border-radius: inherit; background: var(--grad-brand); transition: width var(--transition-slow); }
.perf-fill.a-star, .perf-fill.a { background: linear-gradient(90deg, var(--success-600), var(--success-500)); }
.perf-fill.b-plus, .perf-fill.b { background: linear-gradient(90deg, var(--brand-600), var(--brand-400)); }
.perf-fill.c { background: linear-gradient(90deg, var(--warn-600), var(--warn-400)); }
.perf-fill.d, .perf-fill.e { background: linear-gradient(90deg, var(--orange-600), var(--orange-400)); }
.perf-fill.f { background: linear-gradient(90deg, var(--danger-600), #f87171); }
.perf-score { min-width: 42px; text-align: right; font-size: var(--text-sm); font-weight: 700; color: var(--ink-700); }

/* --- Suggested report-card comments ------------------------------------------- */
.suggestion-group { display: flex; flex-direction: column; gap: 6px; margin-top: var(--space-3); }
.suggestion-group .comment-suggestion {
  justify-content: flex-start;
  min-height: 0;
  padding: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: var(--leading-snug);
  text-align: left;
  white-space: normal;
  word-break: break-word;
}
.suggestion-heading {
  display: flex; align-items: center; gap: 6px;
  margin: 0;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

/* --- Approved score locking ---------------------------------------------------- */
.score-input.is-locked {
  background: var(--surface-3);
  border-color: var(--line-300);
  color: var(--ink-400);
  cursor: not-allowed;
  padding-right: 26px;      /* room for the lock, so it never sits on the number */
}
/* The lock sits inside the score box.
   It is positioned against .score-field, so that wrapper has to be exactly as
   wide as the input. It was width:100% of the table cell while the input is
   capped at 92px, so "right: 8px" measured from the cell edge and the lock
   landed in the empty space beside the box instead of inside it. */
.score-field {
  position: relative;
  display: inline-block;
  width: 100%;
  min-width: 64px;
  max-width: 92px;          /* the same cap as .score-input */
}
.score-field .score-input {
  box-sizing: border-box;
  width: 100%;
  max-width: none;          /* the wrapper does the capping now */
}
.score-lock {
  position: absolute;
  right: 8px; top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  color: var(--ink-300);
  pointer-events: none;
}
.score-lock .ico { width: 13px; height: 13px; }

/* Narrow screens: the table scrolls rather than the boxes shrinking, so the
   lock keeps its place. */
@media (max-width: 640px) {
  .score-field { min-width: 60px; }
}
tr.has-approved td:first-child { box-shadow: inset 3px 0 0 var(--success-500); }

/* --- Odds and ends --------------------------------------------------------------- */
.section-title {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--ink-800);
}
.lead { font-size: var(--text-md); line-height: var(--leading-normal); color: var(--ink-500); }
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.info-list { display: flex; flex-direction: column; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
.info-list li {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--line-100);
  border-radius: var(--r-sm);
  font-size: var(--text-base);
  transition: background var(--transition-fast);
}
.info-list li:last-child { border-bottom: 0; }
.info-list li:hover { background: var(--surface-1); }

/* The row is: icon, what it is about, and its status.
   It used to be laid out with justify-content: space-between and nothing told
   to grow, so the spare width was shared out BETWEEN the three — the icon was
   shoved to the far left, the name floated somewhere in the middle, and the
   status pill sat on the right with a canyon between them. Giving the middle
   column the leftover space puts the text beside its icon, where it reads. */
.info-list li > div { flex: 1 1 auto; min-width: 0; }
.info-list li > .ico-wrap,
.info-list li > .pill,
.info-list li > .badge { flex: 0 0 auto; }

/* A long name truncates rather than pushing the status off the row. */
.info-list li > div > strong { overflow-wrap: anywhere; }
.info-list li > div > div { overflow-wrap: anywhere; }

/* The leading icon reads as a marker, not as stray punctuation. */
.info-list li > .ico-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px; height: 34px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.info-list li > .ico-wrap .ico { width: 17px; height: 17px; }

/* Label/value rows — two plain spans, no middle column — keep their old
   behaviour: the label on the left, the value pushed to the right. */
.info-list li > span:first-child:last-of-type { color: var(--muted); }
.info-list li > span + span:last-child { margin-left: auto; font-weight: 600; color: var(--ink-700); }

@media (max-width: 560px) {
  /* The status drops under the text instead of squeezing it to one word. */
  .info-list li { flex-wrap: wrap; row-gap: 6px; }
  .info-list li > div { flex-basis: calc(100% - 34px - var(--space-3)); }
  .info-list li > .pill { margin-left: calc(34px + var(--space-3)); }
}
.pagination-quiet { justify-content: center; }
.pg { display: inline-flex; align-items: center; gap: 4px; }
.class-chip { border-radius: var(--r-full); }
.class-chip.selected { background: var(--grad-brand); color: #fff; border-color: transparent; }
.chip-count {
  margin-left: 6px;
  padding: 1px 7px;
  background: rgba(255,255,255,.26);
  font-size: var(--text-2xs);
}
.class-chip:not(.selected) .chip-count { background: var(--surface-3); color: var(--ink-500); }

/* --- Sign-in field icons and decorations ------------------------------------
   A leading icon sits inside the field rather than above it, so the label,
   icon and input read as one control. */
.input-wrap .input-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 17px;
  height: 17px;
  color: var(--ink-200);
  pointer-events: none;
  z-index: 1;
}
.input-wrap:focus-within .input-icon { color: var(--brand-500); }
.input-wrap input { padding-left: 38px; }

/* The globe is the backdrop, the rest are small accents. Without a size the
   globe rendered at its natural dimensions and swamped the page. */
.auth-decoration.d5 {
  width: min(680px, 86vw);
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  opacity: .05;
  z-index: 0;
}
.auth-brand .lead { font-size: var(--text-sm); color: var(--muted); }

/* ===========================================================================
   18. Profile pages
   ---------------------------------------------------------------------------
   The same layout was written three times, once each for the Head of School,
   teacher and student profiles. It lives here now so all three stay in step.
   =========================================================================== */
.profile-container { display: flex; flex-direction: column; gap: var(--space-5); }

.profile-header {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-6);
  border-radius: var(--r-2xl);
  background: var(--grad-sidebar);
  color: #fff;
  box-shadow: var(--shadow-md);
}
.profile-header::after {
  content: "";
  position: absolute;
  right: -60px; top: -70px;
  width: 230px; height: 230px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,.09);
}
.profile-header > * { position: relative; }

.profile-avatar,
.profile-avatar-fallback {
  width: 92px; height: 92px;
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden;
  border-radius: var(--r-full);
  border: 3px solid rgba(255,255,255,.42);
  background: rgba(255,255,255,.18);
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 800;
  color: #fff;
}
.profile-avatar img { width: 100%; height: 100%; object-fit: cover; }

.profile-info { min-width: 0; }
.profile-info h2 {
  margin: 0 0 var(--space-1);
  font-size: var(--text-2xl);
  font-weight: 800;
  color: #fff;
}
.profile-info p { margin: 0; color: rgba(255,255,255,.82); font-size: var(--text-base); }
.profile-info .pill { background: rgba(255,255,255,.2); color: #fff; margin-top: var(--space-3); }

.profile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--space-5);
  align-items: start;
}
.image-preview { display: flex; gap: var(--space-4); align-items: center; margin-bottom: var(--space-4); }
.preview-item { text-align: center; }
.preview-item img {
  width: 80px; height: 80px;
  object-fit: cover;
  border-radius: var(--r-lg);
  border: 1px solid var(--line-200);
}
.preview-text { margin-top: 6px; font-size: var(--text-xs); color: var(--muted); }

/* ===========================================================================
   19. Assessment pages
   ---------------------------------------------------------------------------
   Shared by the class-teacher assessment, the Head of School's version and
   the per-student view. Sixteen trait ratings, a progress summary and the
   comment panel.
   =========================================================================== */
.assess-progress {
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--line-200);
  border-radius: var(--r-lg);
  background: var(--surface-0);
  box-shadow: var(--shadow-xs);
  margin-bottom: var(--space-4);
}
.assess-progress-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-600);
}
.assess-bar {
  height: 10px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  overflow: hidden;
}
.assess-bar-fill {
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--success-600), var(--success-500));
  transition: width var(--transition-slow);
}

.assess-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--ink-500);
}
.assess-legend .al { display: inline-flex; align-items: center; gap: 6px; }
.assess-legend .al-dot {
  width: 9px; height: 9px;
  border-radius: var(--r-full);
  background: var(--ink-200);
}
.al-complete .al-dot, .al-done .al-dot { background: var(--success-500); }
.al-partial .al-dot, .al-pending .al-dot { background: var(--warn-500); }
.al-none .al-dot { background: var(--line-300); }

.assess-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: var(--space-3);
}
.assess-card {
  display: block;
  padding: var(--space-4);
  border: 1px solid var(--line-200);
  border-radius: var(--r-lg);
  background: var(--surface-0);
  box-shadow: var(--shadow-xs);
  transition: transform var(--transition-fast), box-shadow var(--transition-fast), border-color var(--transition-fast);
}
.assess-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); border-color: var(--brand-200); }
.assess-card-name { font-weight: 650; color: var(--ink-800); }
.assess-card-meta { margin-top: 2px; font-size: var(--text-sm); color: var(--muted); }

.assessment-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line-200);
  border-radius: var(--r-lg);
  background: var(--surface-0);
}
.assessment-table { width: 100%; border-collapse: separate; border-spacing: 0; min-width: 540px; }
.assessment-table th,
.assessment-table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line-100);
  text-align: left;
}
.assessment-table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface-1);
  border-bottom: 1px solid var(--line-200);
  color: var(--ink-400);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
}
.assessment-table tbody tr:last-child td { border-bottom: 0; }
.assessment-table tbody tr:hover { background: var(--surface-1); }
.assessment-input, .score-select { max-width: 110px; }

.comment-layout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: var(--space-5);
  align-items: stretch;
  margin-top: var(--space-5);
}
/* min-width: 0 is the whole fix here.
   A grid item defaults to min-width: auto, meaning it refuses to shrink below
   its content. The suggestion buttons hold whole sentences and .btn sets
   white-space: nowrap, so the item's minimum came out at 745px — and the grid
   track grew to match inside a 290px card. Everything in the column then ran
   off the side of the phone, clipped and unreachable. */
.comment-layout > div { display: flex; flex-direction: column; min-width: 0; }

/* A suggested comment is a sentence, so it wraps. Buttons do not by default. */
.comment-suggestion, .suggestion-btn {
  white-space: normal;
  text-align: left;
  height: auto;
  line-height: var(--leading-snug);
  overflow-wrap: anywhere;
}
.suggestion-group { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.comment-box {
  width: 100%;
  min-height: 150px;
  flex: 1;
  resize: vertical;
}

/* ===========================================================================
   20. Remaining page components
   =========================================================================== */

/* Class and arm management */
.arm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--space-3); }
.arm-card {
  padding: var(--space-4);
  border: 1px solid var(--line-200);
  border-radius: var(--r-lg);
  background: var(--surface-0);
  box-shadow: var(--shadow-xs);
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.arm-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.arm-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--space-2); }
.arm-pill, .arm-btn, .ar-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px;
  border: 1px solid var(--line-200);
  border-radius: var(--r-full);
  background: var(--surface-2);
  color: var(--ink-600);
  font-size: var(--text-xs);
  font-weight: 650;
}
.arm-quickmove { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--space-3); }

/* Teacher subject assignments */
.assignment-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: var(--space-3); }
.assignment-card, .assignment-item {
  display: flex; flex-direction: column; gap: 4px;
  padding: var(--space-4);
  border: 1px solid var(--line-200);
  border-radius: var(--r-lg);
  background: var(--surface-0);
  box-shadow: var(--shadow-xs);
  transition: transform var(--transition-fast), box-shadow var(--transition-fast), border-color var(--transition-fast);
}
.assignment-card:hover, .assignment-item:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); border-color: var(--brand-200); }

/* Every card in a row is the same height, so the actions belong on a shared
   baseline. They used to follow straight after the title, which meant a
   subject whose name wrapped to a second line pushed its buttons lower than
   its neighbours' and the row read as ragged. */
.assignment-card-body { min-width: 0; }
.assignment-card-body strong { display: block; overflow-wrap: anywhere; }
.assignment-card-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: auto;              /* the pin: take up the slack above */
  /* A hairline across the full width of the card separates the actions from
     the text. The border sits on this element and the negative margins pull
     it out to the card's edges; an absolutely positioned pseudo-element was
     tried first and rendered as a stub beside the buttons instead. */
  margin-inline: calc(var(--space-4) * -1);
  padding: var(--space-3) var(--space-4) 0;
  border-top: 1px solid var(--line-100);
}
.assignment-card.selected, .assignment-item.selected { border-color: var(--brand-500); box-shadow: var(--ring); }
.assignment-list { display: flex; flex-direction: column; gap: var(--space-2); }

/* Student view rows */
.student-view-item {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--line-100);
}
.student-view-item:last-child { border-bottom: 0; }

/* Score entry inputs */
.score-input {
  width: 100%;
  min-width: 64px;
  max-width: 92px;
  min-height: 38px;
  padding: 6px 10px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.row-status {
  display: inline-flex; align-items: center;
  padding: 3px 9px;
  border-radius: var(--r-full);
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.row-status.new { background: var(--brand-50); color: var(--brand-800); }
.row-status.existing { background: var(--surface-3); color: var(--ink-500); }

/* Admin notice and audit detail */
/* The icon sits beside the message. Anything that is not the icon must be a
   single flex item, or a sentence gets dealt out into one column per text run
   — which is what happened when the text was written loose in this div. The
   rules below make that safe even if someone writes it loose again. */
.admin-notice > :not(svg) { flex: 1 1 auto; min-width: 0; margin: 0; }
.admin-notice > svg, .admin-notice > .ico { flex: 0 0 auto; margin-top: 1px; }
.admin-notice {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--warn-100);
  border-left: 3px solid var(--warn-500);
  border-radius: var(--r-md);
  background: var(--warn-50);
  color: var(--warn-700);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}
.audit-detail { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted); word-break: break-word; }
.audit-row-meta { font-size: var(--text-xs); color: var(--muted); }

/* Report card and print views */
.print-panel {
  max-width: 920px;
  margin: var(--space-6) auto;
  padding: var(--space-7);
  border-radius: var(--r-xl);
  background: var(--surface-0);
  box-shadow: var(--shadow-sm);
}
.print-head { text-align: center; margin-bottom: var(--space-5); }
.print-head h2 { letter-spacing: .04em; }
table.print { width: 100%; border-collapse: collapse; margin-top: var(--space-3); }
table.print th, table.print td { padding: 10px; border: 1px solid var(--line-200); text-align: left; }
table.print thead th { background: var(--surface-1); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-wide); color: var(--ink-400); }

/* Result table used across the report views */
.rt-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--r-lg); }
.rt-table { width: 100%; border-collapse: separate; border-spacing: 0; min-width: 760px; font-size: var(--text-sm); }
.rt-table th, .rt-table td { padding: 10px var(--space-3); border-bottom: 1px solid var(--line-100); }
.rt-table thead th {
  background: var(--surface-1);
  color: var(--ink-400);
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
}
.rt-table tbody tr:hover { background: var(--surface-1); }

/* Add-forms shown inline above a table */
.add-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-3);
  align-items: end;
  padding: var(--space-4);
  border: 1px solid var(--line-200);
  border-radius: var(--r-lg);
  background: var(--surface-1);
  margin-bottom: var(--space-4);
}

/* Filter bars */
.filter-row, .filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: flex-end;
  margin-bottom: var(--space-4);
}
.filter-row .form-group, .filters .form-group { margin-bottom: 0; }

@media (max-width: 640px) {
  .profile-header { flex-direction: column; text-align: center; padding: var(--space-5); }
  .profile-avatar, .profile-avatar-fallback { width: 76px; height: 76px; font-size: var(--text-2xl); }
  .comment-layout { grid-template-columns: 1fr; }
  .assess-grid { grid-template-columns: 1fr; }
  .add-form { grid-template-columns: 1fr; }
}

/* ===========================================================================
   21. Remaining page classes
   The rest of the names left in the markup were click handlers for JavaScript
   (edit-student, toggle-disable, expand-row and so on) and need no styling.
   =========================================================================== */

/* Result approval state, read straight from the row's status value. */
.status-badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  color: var(--ink-600);
  font-size: var(--text-xs);
  font-weight: 650;
  text-transform: capitalize;
  white-space: nowrap;
}
.status-badge.approved   { background: var(--success-100); color: var(--success-700); }
.status-badge.pending    { background: var(--warn-50);     color: var(--warn-700); }
.status-badge.disapproved{ background: var(--danger-50);   color: var(--danger-700); }

.count-pill {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; padding: 2px 8px;
  border-radius: var(--r-full);
  background: var(--brand-50);
  color: var(--brand-800);
  font-size: var(--text-xs);
  font-weight: 700;
}

.info-card-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--line-200);
}
.info-card-header h3, .info-card-header h4 { margin: 0; }

.subject-class-arm, .subject-subject, .inline-select, .student-select, .class-filter { max-width: 260px; }
.subject-class-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }

/* The expandable per-class rows under a subject. */
tr.group-details > td { background: var(--surface-1); padding: var(--space-3) var(--space-5); }

.student-card-wrapper, .teacher-view-card, .student-view-card { display: flex; flex-direction: column; gap: var(--space-4); }
.password-field, .current-pwd, .new-pwd, .confirm-pwd { position: relative; }

.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
  align-items: end;
}
.inline-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.info-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 11px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  color: var(--ink-600);
  font-size: var(--text-xs);
  font-weight: 600;
}
.welcome { font-size: var(--text-md); color: var(--ink-500); }
.reg { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--ink-500); }
.pwd-input { padding-right: 44px; }
.info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: var(--space-4); }
.info-item { display: flex; flex-direction: column; gap: 2px; }
.info-item .label, .info-item > span:first-child { font-size: var(--text-xs); color: var(--muted); text-transform: uppercase; letter-spacing: var(--tracking-wide); font-weight: 650; }
.info-item > span:last-child, .info-item strong { font-size: var(--text-base); font-weight: 600; color: var(--ink-700); }
.quick-actions a.alt-assess .ico { color: var(--violet-600); }

/* ===========================================================================
   22. Final polish
   =========================================================================== */

/* An empty panel was reserving as much height as a full one. Give it enough
   room to read as deliberate, not so much that the page looks broken. */
.no-results, .info-empty, .empty-state, .empty-block {
  padding: var(--space-6) var(--space-5);
  gap: var(--space-1);
}
.no-results p, .info-empty p, .empty-state p { margin: 0; font-size: var(--text-sm); }

/* Card headings carry an icon; keep it aligned with the text baseline and in
   the brand colour so headings read as one unit. */
.card > h3 .ico, .section-card > h3 .ico, .section-title .ico { color: var(--brand-500); }

/* Long names in a hero should wrap gracefully rather than overflow. */
.hero-name { overflow-wrap: anywhere; }

/* Sticky table headers need a solid backdrop when the body scrolls under. */
.table thead th { background-clip: padding-box; }

/* Give focus inside a table row somewhere visible to land. */
.table tbody tr:focus-within { background: var(--brand-50); }

/* Disabled controls should look disabled, including links acting as buttons. */
.btn.disabled, .btn[disabled], a.disabled {
  opacity: .5;
  pointer-events: none;
  filter: grayscale(.3);
}

/* Print: the report card is the point of this system, so it gets real care. */
@media print {
  .print-panel { margin: 0; padding: 0; box-shadow: none; }
  .table, .rt-table, table.print { min-width: 0 !important; font-size: 11pt; }
  .table-wrap, .rt-wrap, .table-scroll { overflow: visible !important; border: 0; }
  .pill, .grade-badge, .status-badge { border: 1px solid #999; background: none !important; color: #000 !important; }
  .stat-card { background: none !important; color: #000 !important; border: 1px solid #999; box-shadow: none; }
  .stat-card::after, .stat-icon { display: none; }
  tr, td, th { page-break-inside: avoid; }
}

/* ===========================================================================
   23. List-row controls
   =========================================================================== */

/* Row actions are a single run of icon buttons. They were wrapping onto a
   second line and doubling the height of every row. */
.table .actions, .row-actions, .class-tile-actions, .subj-actions {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 2px;
}
.table .actions > a,
.table .actions > button,
.table .actions > form > button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  min-height: 0;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-400);
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.table .actions > a:hover,
.table .actions > button:hover,
.table .actions > form > button:hover {
  background: var(--surface-3);
  border-color: var(--line-200);
  color: var(--brand-600);
}
/* Destructive actions read red on hover, not by default — a row of red icons
   makes every list look alarming. */
.table .actions a[data-confirm]:hover,
.table .actions button[data-confirm]:hover { background: var(--danger-50); border-color: var(--danger-100); color: var(--danger-600); }
.table .actions .ico { width: 16px; height: 16px; }

/* Filter chips: only the selected one is filled. They were all solid, so the
   list looked as though every class was active at once. */
.class-filter, .class-chip {
  min-height: 38px;
  padding: 0 var(--space-4) !important;
  border: 1px solid var(--line-300);
  border-radius: var(--r-full);
  background: var(--surface-0);
  background-image: none;
  color: var(--ink-600);
  font-size: var(--text-sm) !important;
  font-weight: 600 !important;
  box-shadow: none;
}
.class-filter:hover, .class-chip:hover {
  border-color: var(--brand-300);
  background: var(--brand-50);
  color: var(--brand-700);
  transform: none;
  filter: none;
}
.class-filter.selected, .class-chip.selected {
  border-color: transparent;
  background-image: var(--grad-brand);
  color: #fff;
  box-shadow: var(--shadow-xs);
}
.class-filter.selected:hover, .class-chip.selected:hover { color: #fff; background-image: var(--grad-brand); }

/* The account status cell is a badge, not a control. */
.table .status-badge, .table .row-status { pointer-events: none; }

/* Account status toggle. It shows the current state and is also the control
   that changes it, so it reads as a status pill that responds to hover.
   Note: .toggle-btn.disabled means "this account is disabled", not "this
   control is disabled" — it stays clickable, which is how access is restored. */
.toggle-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 0 11px;
  border: 1px solid transparent;
  border-radius: var(--r-full);
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 650;
  line-height: 1;
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}
.toggle-btn .ico { width: 14px; height: 14px; }
.toggle-btn.active {
  background: var(--success-100);
  border-color: #a7f3cf;
  color: var(--success-700);
}
.toggle-btn.active:hover { background: var(--success-50); border-color: var(--success-500); }
.toggle-btn.disabled {
  background: var(--danger-50);
  border-color: var(--danger-100);
  color: var(--danger-700);
  opacity: 1;
  pointer-events: auto;
  filter: none;
}
.toggle-btn.disabled:hover { background: #fde8e8; border-color: var(--danger-500); }

/* ===========================================================================
   24. Layout and action-button corrections
   =========================================================================== */

/* The content area was capped at 1480px and left-aligned, so on a wide
   monitor the page stopped well short of the right edge with dead space
   beside it. It now fills the width beside the sidebar, and only centres
   itself on displays wide enough for the cap to bite. */
.main-content {
  max-width: none;
  width: 100%;
  margin-inline: auto;
}
@media (min-width: 2100px) {
  /* Past this width a full-bleed table is harder to read than a centred one. */
  .main-content { max-width: 1960px; }
}

/* Row actions: icon-only buttons are square, buttons carrying a label size
   to their text. Forcing every one to 32px made "Reset password", "Disable"
   and "Delete" overlap each other. */
.table .actions > a,
.table .actions > button,
.table .actions > form > button {
  width: auto;
  min-width: 32px;
  height: auto;
  min-height: 32px;
  padding: 0 var(--space-2);
  gap: 5px;
  white-space: nowrap;
  font-size: var(--text-sm);
  font-weight: 550;
}
/* No special case for icon-only buttons: :only-child ignores text nodes, so
   it also matched buttons that carry a label and squeezed them to 32px.
   Padding plus min-width sizes both correctly — an icon alone lands at about
   32px, a labelled button grows to fit its text. */
/* Row actions stay on one line. Allowing them to wrap was meant to stop
   labelled buttons overlapping in a narrow column, but flex-shrink: 0 above
   already prevents that, and on a phone the Actions column is about 79px —
   enough for two icons — so four of them stacked vertically and made every
   row 173px tall. The table scrolls sideways instead, which is what
   .table-wrap { overflow-x: auto } is there for. */
.table .actions { flex-wrap: nowrap; row-gap: 4px; }

/* The sign-in page is a centred card with the footer pinned beneath it. The
   footer was a flex sibling of the card, so it sat beside it rather than
   under it, off to one side. */
.auth-layout { flex-direction: column; gap: var(--space-6); }
.auth-layout .auth-card { margin-top: auto; }
.auth-layout .app-footer {
  position: relative;
  z-index: 1;
  width: 100%;
  margin-top: auto;
  padding-top: var(--space-5);
  border-top: 0;
}

/* Action buttons must keep their natural width. In a cramped column flex
   was shrinking them to the 32px minimum, so a label like "Reset password"
   needed 75px and got 32 — the text spilled over its neighbour. They no
   longer shrink; the row wraps or the table scrolls instead. */
.table .actions > a,
.table .actions > button,
.table .actions > form,
.table .actions > form > button {
  flex-shrink: 0;
}

/* ===========================================================================
   25. Inline field validation
   Problems are reported beside the field that caused them, as soon as focus
   leaves it, rather than after the form has been submitted and the page lost.
   =========================================================================== */
.field-error {
  display: flex;
  align-items: flex-start;
  gap: 5px;
  margin: 6px 0 0;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--danger-600);
}
.field-error[hidden] { display: none; }
.field-error::before {
  content: "";
  flex-shrink: 0;
  width: 15px; height: 15px;
  margin-top: 1px;
  background: currentColor;
  -webkit-mask: var(--icon-warning) center / contain no-repeat;
  mask: var(--icon-warning) center / contain no-repeat;
}

input.is-invalid, select.is-invalid, textarea.is-invalid {
  border-color: var(--danger-500);
  background: #fffafa;
}
input.is-invalid:focus, select.is-invalid:focus, textarea.is-invalid:focus {
  border-color: var(--danger-600);
  box-shadow: 0 0 0 3px rgba(239, 68, 68, .22);
}
.input-wrap:has(input.is-invalid) .input-icon { color: var(--danger-500); }

.form-error-summary { margin-bottom: var(--space-4); }
.form-error-summary[hidden] { display: none; }

/* ===========================================================================
   26. Field layout corrections
   =========================================================================== */

/* Many forms write <label>Caption<input ...></label>. Without this the
   caption and the control sit on one line, the control keeps its intrinsic
   width, and the row reads as broken — as the Edit Class dialog did. The
   label becomes the stack, so the caption sits above a full-width control
   wherever that pattern is used. */
label > input:not([type="checkbox"]):not([type="radio"]),
label > select,
label > textarea {
  display: block;
  width: 100%;
  margin-top: 6px;
}
/* A checkbox or radio still belongs beside its caption. */
label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 500;
}

/* auto-fill keeps empty columns, so a section holding one card left it at the
   minimum 200px with the rest of the row blank. auto-fit collapses the empty
   tracks and lets the card use the space. */
.arm-grid, .assess-grid, .assignment-grid, .students-grid, .class-grid {
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}

/* The class-teacher card: let it wrap rather than overlap when narrow — the
   Remove button was printing on top of the teacher's ID. */
.ct-block { flex-wrap: wrap; }
.ct-block .meta { flex: 1 1 160px; min-width: 0; }
.ct-block .meta small { display: block; overflow-wrap: anywhere; }
.ct-block form { flex-shrink: 0; margin-left: auto; }

/* Inline save/edit rows inside tables keep their controls on one line. */
.subj-row-form, .inline-row, .arm-quickmove { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.subject-name-input { flex: 1; min-width: 140px; }

/* The class/arm page stacks three sections: the class teacher, the subjects
   offered and the students. Two of them already span the full row with
   grid-column:1/-1, so auto-fit had no empty tracks to collapse and left the
   class-teacher card alone in a 244px column with the row blank beside it.
   A single column is what the page actually wants. */
.arm-grid { grid-template-columns: 1fr; gap: var(--space-5); }

/* ===========================================================================
   27. Destructive actions are buttons
   Delete used to be a link — "students.php?delete=12". Links get followed, so
   a crawler or a prefetcher could delete records with nobody clicking; one did.
   The triggers are plain buttons now, which means the native button chrome has
   to be stripped wherever a button stands in for a link or an icon.
   =========================================================================== */
button.link, button.icon-btn {
  font: inherit;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
button.link {
  background: none;
  border: 0;
  font-size: var(--text-sm);
  font-weight: 550;
}
/* The row-action rules already size .actions > button, so a Delete button
   lines up with the view and edit icons beside it without further work. */

/* ===========================================================================
   28. Missing-score notice
   A blank means two things: a student who does not offer the subject, and a
   student the teacher has not got to yet. Only the second is a problem, so
   only the second is styled as a warning — the rest is a quiet line of fact.
   =========================================================================== */
.score-gap {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  margin: 0 0 var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--warn-200, #fde68a);
  border-left: 3px solid var(--warn-500, #f59e0b);
  border-radius: var(--r-md);
  background: var(--warn-50, #fffbeb);
  color: var(--ink-700);
  font-size: var(--text-sm);
  line-height: 1.55;
}
.score-gap .ico-wrap { display: inline-flex; flex-shrink: 0; color: var(--warn-600, #d97706); }
.score-gap .ico { width: 20px; height: 20px; }
.score-gap strong { color: var(--ink-800, var(--ink-700)); }
.score-gap-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}
.score-gap-list li {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 3px 9px;
  border-radius: var(--r-full);
  background: var(--surface-0);
  border: 1px solid var(--warn-200, #fde68a);
  font-weight: 600;
}
.score-gap-list li small { color: var(--muted); font-weight: 500; font-family: var(--font-mono); }
.score-gap-sub { margin: var(--space-3) 0 0; }
.score-gap-quiet { margin: 0 0 var(--space-4); color: var(--muted); font-size: var(--text-sm); }

/* The same flag beside the student's own row, so the teacher can see which
   line to fill without matching names against a list. */
.row-status.missing {
  background: var(--warn-50, #fffbeb);
  border: 1px solid var(--warn-200, #fde68a);
  color: var(--warn-700, #b45309);
}
.row-status.blank-saved {
  background: var(--danger-50);
  border: 1px solid var(--danger-100);
  color: var(--danger-600, #dc2626);
}

/* The teacher's across-all-subjects summary. */
.gap-summary { display: flex; flex-direction: column; gap: var(--space-2); margin: 0 0 var(--space-4); }
.gap-summary-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line-200);
  border-left: 3px solid var(--warn-500, #f59e0b);
  border-radius: var(--r-md);
  background: var(--surface-0);
  font-size: var(--text-sm);
}
.gap-summary-item .subject { font-weight: 650; color: var(--ink-700); }
.gap-summary-item .where { color: var(--muted); }
.gap-summary-item .count { margin-left: auto; color: var(--warn-700, #b45309); font-weight: 650; }
@media (max-width: 560px) {
  .gap-summary-item .count { margin-left: 0; width: 100%; }
}

/* ===========================================================================
   29. Promote / demote buttons
   These were five inline linear-gradients repeated down students.php, with
   emoji standing in for icons — the only place in the app that did either.
   The glyphs rendered at a different weight and baseline from every real icon
   beside them, which is what made the row look wrong.
   =========================================================================== */
.btn.promote,
.btn.promote-all {
  color: #fff;
  border: none;
}
.btn.promote { background: linear-gradient(90deg, var(--success-600), var(--success-500)); }
.btn.promote:hover { background: linear-gradient(90deg, var(--success-700), var(--success-600)); }
.btn.promote-all { background: linear-gradient(90deg, var(--warn-600), var(--warn-500)); }
.btn.promote-all:hover { background: linear-gradient(90deg, var(--warn-700), var(--warn-600)); }
.btn.promote .ico,
.btn.promote-all .ico { color: #fff; }
/* The compact pair inside the edit-student modal. */
.btn.promote.compact, .btn.outline.compact { padding: 8px 12px; font-size: var(--text-sm); }

/* The register / leavers switch on the Students page. */
.roll-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }
.roll-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  border: 1px solid var(--line-300);
  border-radius: var(--r-full);
  background: var(--surface-0);
  color: var(--ink-500);
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}
.roll-tab:hover { border-color: var(--brand-200); color: var(--brand-600); text-decoration: none; }
.roll-tab.selected { background: var(--brand-500); border-color: var(--brand-500); color: #fff; }
.roll-tab.selected .ico { color: #fff; }
.roll-tab .ico { width: 16px; height: 16px; }

/* Graduating is not promoting, so it does not wear the promote colour. */
.btn.graduate {
  background: linear-gradient(90deg, var(--violet-700), var(--violet-400));
  color: #fff;
  border: none;
}
.btn.graduate:hover { filter: brightness(1.07); }
.btn.graduate .ico { color: #fff; }

/* ===========================================================================
   30. Sign-in progress
   Checking a password is a round trip to the server. With no sign of it the
   page looked frozen, so people pressed the button again.
   =========================================================================== */
.auth-btn { position: relative; }
.auth-btn .auth-btn-busy { display: none; }
.auth-btn.is-busy .auth-btn-idle { display: none; }
.auth-btn.is-busy .auth-btn-busy { display: inline-flex; align-items: center; gap: 8px; }
.auth-btn[disabled] { opacity: .85; cursor: progress; }

.spinner {
  display: inline-block;
  width: 15px; height: 15px;
  border: 2px solid rgba(255, 255, 255, .45);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Honour a request for less motion: hold the ring still rather than remove it,
   so the button still reads as busy. */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; border-top-color: rgba(255, 255, 255, .9); }
}

.auth-status {
  min-height: 1.1em;
  margin: var(--space-2) 0 0;
  color: var(--muted);
  font-size: var(--text-sm);
  text-align: center;
}

/* ===========================================================================
   31. Sidebar account block
   The way into the profile page, for every role, sitting with Logout at the
   foot of the sidebar where people look for it.
   =========================================================================== */
.sidebar-footer { display: flex; flex-direction: column; gap: var(--space-2); }
.sidebar-account {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--r-md);
  border: 1px solid transparent;
  color: inherit;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}
.sidebar-account:hover,
.sidebar-account.active { background: var(--surface-2); border-color: var(--line-200); text-decoration: none; }
.sidebar-account-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  width: 36px; height: 36px;
  overflow: hidden;
  border-radius: var(--r-full);
  background: var(--grad-brand);
  color: #fff;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: .4px;
}
.sidebar-account-avatar img { width: 100%; height: 100%; object-fit: cover; }
.sidebar-account-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.sidebar-account-text strong {
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--ink-700);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-account-text small { color: var(--muted); font-size: var(--text-2xs); }
.sidebar-account-go { width: 15px; height: 15px; margin-left: auto; color: var(--ink-300); transform: rotate(180deg); }

/* Collapsed rail: the avatar alone, centred, with the name in the tooltip. */
.sidebar.collapsed .sidebar-account { justify-content: center; padding-inline: 0; }
.sidebar.collapsed .sidebar-account-text,
.sidebar.collapsed .sidebar-account-go { display: none; }

/* The details-card avatar: initials underneath, photo over the top.
   Stacking them means a photo that fails to load simply removes itself and
   the initials are already in place — no error-handling string to escape. */
.view-avatar { position: relative; width: 72px; height: 72px; flex-shrink: 0; }
.view-avatar > span { width: 100%; height: 100%; }
.view-avatar > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: 50%;
  border: 2px solid var(--brand-500);
  background: var(--surface-2);
}

/* The supporting line under a stat: the number is the headline, this gives it
   context — "4" subjects, across "1 class". */
.stat-sub {
  position: relative;
  z-index: 1;
  margin-top: 2px;
  font-size: var(--text-xs);
  font-weight: 600;
  opacity: .72;
}

/* --- Work the Head of School sent back ------------------------------------------ */
/* Styled like .score-gap, the other "something needs your attention" block, but in
   the danger colours: a gap is a reminder, a rejection is a decision already made. */
.rejection-alert {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  margin: 0 0 var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--danger-100);
  border-left: 3px solid var(--danger-500);
  border-radius: var(--r-md);
  background: var(--danger-50);
  color: var(--ink-700);
  font-size: var(--text-sm);
  line-height: 1.55;
}
.rejection-alert > .ico-wrap { color: var(--danger-500); flex: 0 0 auto; line-height: 0; }
.rejection-alert .ico-wrap .ico { width: 20px; height: 20px; }
.rejection-body { min-width: 0; flex: 1; }
.rejection-title { display: block; color: var(--danger-700); }
.rejection-sub { margin: 2px 0 var(--space-3); color: var(--ink-500); }

.rejection-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.rejection-list > li {
  padding: var(--space-3);
  border: 1px solid var(--danger-100);
  border-radius: var(--r-sm);
  background: var(--surface-0);
}
.rejection-link {
  display: flex; align-items: center; gap: var(--space-2);
  flex-wrap: wrap;
  color: var(--ink-700);
  font-weight: 650;
  text-decoration: none;
}
.rejection-link:hover .rejection-subject { text-decoration: underline; }
.rejection-subject { color: var(--danger-700); }
.rejection-where {
  padding: 1px 8px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  color: var(--ink-500);
  font-size: var(--text-2xs);
  font-weight: 700;
}
.rejection-count { color: var(--ink-400); font-size: var(--text-2xs); font-weight: 600; }
/* Pushed to the end so the arrow always sits on the right of the row. */
.rejection-go { margin-left: auto; color: var(--danger-500); line-height: 0; }
.rejection-go .ico { width: 16px; height: 16px; }
.rejection-reason {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--danger-100);
  color: var(--ink-600);
}
.rejection-reason span { font-weight: 700; color: var(--ink-500); }
.rejection-names { margin: 4px 0 0; color: var(--ink-400); font-size: var(--text-xs); }

/* The row itself, once they arrive at the score boxes. */
tr.is-rejected td { background: var(--danger-50); }
tr.is-rejected td:first-child { box-shadow: inset 3px 0 0 var(--danger-500); }
.row-status.rejected { background: var(--danger-100); color: var(--danger-700); }
.row-reason {
  display: block;
  margin-top: 4px;
  max-width: 220px;
  color: var(--ink-500);
  font-size: var(--text-2xs);
  line-height: 1.4;
  white-space: normal;
}

@media (max-width: 640px) {
  .rejection-link { gap: 6px; }
  .rejection-go { margin-left: 0; }
}
