:root {
  /* Surfaces */
  --bg: #f6f8fb;
  --panel: #ffffff;
  --panel-2: #fbfcfe;
  /* The school's navy, darkened at the foot so the sidebar still reads as a
     gradient rather than a flat slab. */
  --sidebar: #002a6d;
  --sidebar-2: #00194a;

  /* Text */
  --ink: #101828;
  --muted: #667085;
  --faint: #98a2b3;

  /* Lines */
  --line: #e4e8ee;
  --line-soft: #eef1f5;

  /* Accents — the school's own palette, taken from the crest artwork:
     green #008766, navy #003891, yellow #fff200.

     Two of those cannot be used as supplied. The green measures 4.51:1 on
     white, which passes AA by a hundredth and would fail the moment anyone
     nudges it, so --brand keeps the true green for fills and --brand-ink
     carries a darker one (5.85:1) for text and links. The yellow is 1.17:1 -
     unreadable as text in any size - so it is an accent and a background
     only, and --accent-ink is the dark olive to write on top of it. */
  --brand: #008766;
  --brand-ink: #007357;
  --brand-soft: #e6f5f0;
  --brand-2: #003891;
  --accent: #fff200;
  --accent-ink: #736d00;
  --warn: #b54708;
  --danger: #dc2626;

  /* Depth — layered rather than one heavy drop shadow */
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, .05);
  --shadow: 0 1px 3px rgba(16, 24, 40, .08), 0 1px 2px rgba(16, 24, 40, .04);
  --shadow-lg: 0 12px 28px rgba(16, 24, 40, .1), 0 4px 8px rgba(16, 24, 40, .04);

  --radius: 12px;
  --radius-sm: 8px;

  /* Status. One semantic set for every badge in the app. There used to be two
     parallel palettes - .status.* and .chip-* - with different greens for the
     same meaning, each hardcoded again for dark mode. Defining them here means
     a badge is themed once and every theme picks it up, and it is no longer
     possible for a status class to be forgotten in the dark block and keep a
     light background on a dark page. */
  --ok-bg: #ecfdf3;
  --ok-ink: #027a48;
  --pend-bg: #fffaeb;
  --pend-ink: #b54708;
  --bad-bg: #fef3f2;
  --bad-ink: #b42318;
  --info-bg: var(--brand-soft);
  --info-ink: var(--brand-ink);
}

/* Dark theme. Applied when the user picks it, or by system preference when
   they have not chosen. Only the tokens change - no component needs to know. */
:root[data-theme="dark"] {
  --bg: #0b1120;
  --panel: #131c2e;
  --panel-2: #18223a;
  --sidebar: #001231;
  --sidebar-2: #000b1f;
  --ink: #e8edf5;
  --muted: #94a3b8;
  --faint: #64748b;
  --line: #26324a;
  --line-soft: #1e2942;
  --brand: #16a37f;
  --brand-ink: #3fcfa6;
  --brand-soft: #0b2a24;
  --brand-2: #4d8bff;
  --accent: #fff200;
  --accent-ink: #4a4600;
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
  --shadow: 0 1px 3px rgba(0, 0, 0, .4);
  --shadow-lg: 0 12px 28px rgba(0, 0, 0, .5);
  --ok-bg: #06281c;
  --ok-ink: #6ee7b7;
  --pend-bg: #2e1f05;
  --pend-ink: #fcd34d;
  --bad-bg: #300f0f;
  --bad-ink: #fca5a5;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0b1120;
    --panel: #131c2e;
    --panel-2: #18223a;
    --sidebar: #001231;
    --sidebar-2: #000b1f;
    --ink: #e8edf5;
    --muted: #94a3b8;
    --faint: #64748b;
    --line: #26324a;
    --line-soft: #1e2942;
    --brand: #16a37f;
    --brand-ink: #3fcfa6;
    --brand-soft: #0b2a24;
    --brand-2: #4d8bff;
    --accent: #fff200;
    --accent-ink: #4a4600;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
    --shadow: 0 1px 3px rgba(0, 0, 0, .4);
    --shadow-lg: 0 12px 28px rgba(0, 0, 0, .5);
    --ok-bg: #06281c;
    --ok-ink: #6ee7b7;
    --pend-bg: #2e1f05;
    --pend-ink: #fcd34d;
    --bad-bg: #300f0f;
    --bad-ink: #fca5a5;
  }
}

* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  color: var(--ink);
  background: var(--bg);
  font-family: Inter, "Segoe UI", Arial, sans-serif;
}
button, input, select, textarea { font: inherit; }
button { cursor: pointer; }
.hidden { display: none !important; }
.muted { color: var(--muted); }
.error { color: var(--danger); min-height: 1.3em; }
.eyebrow {
  margin: 0 0 4px;
  color: var(--brand);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0;
  text-transform: uppercase;
}

.login-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(1100px 520px at 15% -10%, color-mix(in srgb, var(--brand) 22%, transparent), transparent 60%),
    radial-gradient(900px 500px at 100% 110%, color-mix(in srgb, var(--brand-2) 18%, transparent), transparent 62%),
    var(--bg);
}
.login-panel {
  width: min(960px, 100%);
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 28px;
  align-items: center;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: var(--shadow-lg);
  padding: 36px;
}
.login-panel h1 { margin: 0 0 10px; font-size: clamp(32px, 5vw, 56px); line-height: 1; }
.stack { display: grid; gap: 14px; }
label { display: grid; gap: 7px; color: var(--ink); font-size: 13px; font-weight: 750; }
input, select, textarea {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 11px 12px;
  background: var(--panel);
  color: var(--ink);
  /* Below 16px, iOS Safari zooms the whole page in on focus - this
     overrides the 13px inherited from the parent <label>. */
  font-size: 16px;
}
textarea { min-height: 96px; resize: vertical; }
input:focus, select:focus, textarea:focus {
  border-color: var(--brand);
  outline: 3px solid rgba(24, 100, 171, 0.13);
}
.button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 13px;
  background: var(--panel);
  color: var(--ink);
  font-weight: 600;
  font-size: 13.5px;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease, transform .06s ease, box-shadow .12s ease;
  text-decoration: none;
}
.button:hover { background: var(--panel-2); border-color: var(--faint); }
.button:active { transform: translateY(1px); }
.button:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.button:disabled { opacity: .55; cursor: not-allowed; transform: none; }
.button.primary { border-color: var(--brand); background: var(--brand); color: #fff; }
.button.primary:hover { background: var(--brand-ink); border-color: var(--brand-ink); }
.button.success { border-color: var(--brand-2); background: var(--brand-2); color: #fff; }
.button.success:hover { filter: brightness(.94); }
.button.danger { border-color: var(--danger); background: var(--danger); color: #fff; }
.button.danger:hover { filter: brightness(.94); }
.button.ghost { background: transparent; box-shadow: none; }

/* Compact icon-only button, used for the theme switch. */
.icon-button {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--muted);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
}
.icon-button:hover { color: var(--ink); background: var(--panel-2); }
.icon-button svg { width: 16px; height: 16px; }
/* Only one of the two glyphs shows, depending on the active theme. */
.icon-button .icon-moon { display: none; }
:root[data-theme="dark"] .icon-button .icon-sun { display: none; }
:root[data-theme="dark"] .icon-button .icon-moon { display: block; }
/* Password field with an in-field show/hide toggle. The input keeps its normal
   full-width sizing; the button floats over its right edge with padding so the
   dots never run under the eye. */
.password-field { position: relative; display: block; }
.password-field input { padding-right: 44px; }
.password-toggle {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 42px;
  display: grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  border-radius: 0 6px 6px 0;
}
.password-toggle:hover { color: var(--ink); }
.password-toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.password-toggle svg { width: 18px; height: 18px; }
/* Show the open eye by default; swap to the struck-through eye once revealed. */
.password-toggle .icon-eye-off { display: none; }
.password-toggle[aria-pressed="true"] .icon-eye { display: none; }
.password-toggle[aria-pressed="true"] .icon-eye-off { display: block; }

.login-help { font-size: 12.5px; margin: 2px 0 0; }

.app { min-height: 100vh; display: grid; grid-template-columns: 260px 1fr; }
.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  padding: 16px 14px;
  background: linear-gradient(180deg, var(--sidebar) 0%, var(--sidebar-2) 100%);
  color: #e8edf5;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, .18) transparent;
}
.sidebar::-webkit-scrollbar { width: 8px; }
.sidebar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 999px; }
.sidebar::-webkit-scrollbar-track { background: transparent; }
.brand { display: flex; gap: 12px; align-items: center; padding-bottom: 18px; border-bottom: 1px solid rgba(255,255,255,0.1); }
.brand-mark {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: 8px;
  background: var(--brand);
  color: #fff;
  font-weight: 800;
}
.brand small { display: block; color: #94a3b8; margin-top: 2px; font-size: 12px; }
nav { display: grid; gap: 2px; margin-top: 14px; }

/* ---------- Sidebar search ---------- */
.nav-search { position: relative; margin-bottom: 10px; }
.nav-search svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: #64748b; }
.nav-search input {
  width: 100%;
  padding: 9px 10px 9px 32px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 255, 255, .09);
  background: rgba(255, 255, 255, .05);
  color: #e8edf5;
  font: inherit;
  font-size: 13px;
}
.nav-search input::placeholder { color: #64748b; }
.nav-search input:focus { outline: none; border-color: var(--brand); background: rgba(255, 255, 255, .08); }
.nav-empty { color: #64748b; font-size: 13px; padding: 12px 4px; }

/* ---------- Collapsible groups ---------- */
.nav-group + .nav-group { margin-top: 1px; }
.nav-group-head {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  border: 0;
  background: transparent;
  color: #94a3b8;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  text-align: left;
}
.nav-group-head:hover { background: rgba(255, 255, 255, .05); color: #e8edf5; }
.nav-group-head span { flex: 1; }
.nav-group-head em {
  font-style: normal;
  font-size: 10px;
  color: #64748b;
  background: rgba(255, 255, 255, .06);
  padding: 1px 6px;
  border-radius: 999px;
}
.nav-icon { width: 16px; height: 16px; flex: none; }
.nav-chev { width: 13px; height: 13px; flex: none; transition: transform .18s ease; }
.nav-group.open .nav-chev { transform: rotate(180deg); }

/* Collapsed groups are hidden outright so the sidebar stays short. */
.nav-items { display: none; padding: 2px 0 6px 12px; margin-left: 11px; border-left: 1px solid rgba(255, 255, 255, .07); }
.nav-group.open .nav-items { display: grid; gap: 1px; }

.nav-button {
  width: 100%;
  border: 0;
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  background: transparent;
  color: #b6c2d4;
  text-align: left;
  font-weight: 500;
  font-size: 13.5px;
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.nav-button:hover { background: rgba(255, 255, 255, .06); color: #fff; }
.nav-button.active {
  background: var(--brand);
  color: #fff;
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
}
.workspace { min-width: 0; }
.topbar {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 24px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  border-bottom: 1px solid var(--line);
  backdrop-filter: blur(12px);
}
.topbar h2 { margin: 0; font-size: 20px; font-weight: 650; letter-spacing: -.02em; }
.eyebrow {
  margin: 0 0 1px;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--faint);
}
.top-actions { display: flex; align-items: center; gap: 10px; }
.pill {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 8px 12px;
  background: var(--panel);
  color: var(--muted);
  font-weight: 750;
}
.content { padding: 24px; display: grid; gap: 18px; }
.metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(160px, 1fr));
  gap: 14px;
}
.card, .table-card, .form-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.card { padding: 20px; }

/* Metric cards get a subtle accent bar so a row of them reads as a unit. */
.metrics article {
  position: relative;
  overflow: hidden;
  padding: 18px 20px;
  transition: box-shadow .16s ease, transform .16s ease;
}
.metrics article::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--brand);
  opacity: .85;
}
.metrics article:hover { box-shadow: var(--shadow-lg); transform: translateY(-1px); }
.metric-value {
  margin: 6px 0 2px;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.metric-label { margin: 0; color: var(--muted); font-size: 12.5px; font-weight: 500; }
.metrics article .metric-label:first-child {
  font-size: 11px;
  font-weight: 650;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--faint);
}

.toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
.toolbar h3 { font-size: 17px; font-weight: 650; letter-spacing: -.01em; margin: 0; }
.toolbar h4 { font-size: 14.5px; font-weight: 650; margin: 0; }

.table-card { overflow: auto; }
table { width: 100%; border-collapse: collapse; min-width: 760px; }
th, td { padding: 12px 16px; border-bottom: 1px solid var(--line-soft); text-align: left; vertical-align: middle; }
th {
  position: sticky;
  top: 0;
  background: var(--panel-2);
  color: var(--faint);
  font-size: 11px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .05em;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
td { font-size: 14px; }
tbody tr { transition: background .1s ease; }
tbody tr:hover { background: var(--panel-2); }
tr:last-child td { border-bottom: 0; }
/* Figures line up column-wise when they use tabular numerals. */
td strong { font-variant-numeric: tabular-nums; }

.status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border-radius: 999px;
  padding: 3px 9px;
  background: var(--brand-soft);
  color: var(--brand-ink);
  font-size: 11.5px;
  font-weight: 600;
  text-transform: capitalize;
  white-space: nowrap;
}
/* A leading dot carries the state without relying on colour alone. */
.status::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; opacity: .8; }
/* One rule per meaning, themed by token. `.chip` is an alias of `.status` so
   the two badge vocabularies in app.js render identically. */
.status.paid, .status.present, .status.active, .status.approved, .status.published,
.status.returned, .status.graded, .status.completed, .status.covered, .status.resolved,
.status.collected, .status.matched, .status.ready,
.chip-ok { background: var(--ok-bg); color: var(--ok-ink); }

.status.pending, .status.partial, .status.late, .status.draft, .status.submitted,
.status.in_progress, .status.planned, .status.issued, .status.leave,
.chip-warn { background: var(--pend-bg); color: var(--pend-ink); }

.status.absent, .status.overdue, .status.inactive, .status.rejected, .status.failed,
.status.closed, .status.fail, .status.cancelled, .status.expired, .status.void,
.chip-bad { background: var(--bad-bg); color: var(--bad-ink); }
.chip-req { background: var(--bad-bg); color: var(--bad-ink); }
.chip-opt { background: var(--pend-bg); color: var(--pend-ink); }

/* ---- Setup wizard ---- */
.setup-hero {
  display: flex;
  align-items: center;
  gap: 22px;
  flex-wrap: wrap;
}
/* Readiness ring: a conic-gradient wheel with a punched-out centre. --pct is
   set inline (0-100). Brand fill for the done portion, faint line for the rest. */
.setup-ring {
  --pct: 0;
  flex: none;
  width: 108px;
  height: 108px;
  border-radius: 50%;
  background: conic-gradient(var(--brand) calc(var(--pct) * 1%), var(--line) 0);
  display: grid;
  place-items: center;
}
.setup-ring-hole {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: var(--panel);
  display: grid;
  place-items: center;
}
.setup-ring-pct { font-size: 26px; font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }
.setup-ring-pct small { font-size: 13px; font-weight: 700; color: var(--muted); }
.setup-hero-text { flex: 1 1 260px; min-width: 240px; }
.setup-hero-text h3 { margin: 0 0 4px; }
.setup-hero-sub { margin: 0 0 8px; font-weight: 650; color: var(--ink); }
.setup-hero-warn { margin: 0 0 8px; color: var(--bad-ink); font-weight: 650; }
.setup-hero-ok { margin: 0 0 8px; color: var(--ok-ink); font-weight: 650; }
.setup-hero-note { margin: 0; font-size: 12.5px; }

.setup-list { display: grid; gap: 10px; }
.setup-step {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}
.setup-step.is-done { background: var(--panel-2); }
.setup-mark {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 800;
  font-size: 14px;
  margin-top: 1px;
}
.setup-mark.done { background: var(--ok-bg); color: var(--ok-ink); }
.setup-mark.crit { background: var(--bad-bg); color: var(--bad-ink); }
.setup-mark.todo { background: var(--pend-bg); color: var(--pend-ink); }
.setup-step-body { flex: 1 1 auto; min-width: 0; }
.setup-step-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 2px; }
.setup-step-head strong { font-size: 14.5px; }
.setup-detail { font-size: 13px; margin: 0; }
.setup-progress {
  margin-top: 8px;
  height: 6px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}
.setup-progress > span { display: block; height: 100%; background: var(--brand); border-radius: 999px; }
.setup-step > .button { flex: none; align-self: center; }
.setup-step-done { flex: none; align-self: center; color: var(--ok-ink); font-weight: 800; font-size: 18px; }
@media (max-width: 560px) {
  .setup-step { flex-wrap: wrap; }
  .setup-step > .button { margin-left: 39px; }
}

/* ---- Help centre ---- */
.help-hero h3 { margin: 0 0 4px; }
.help-hero > p { margin: 0 0 14px; max-width: 70ch; }
.help-search-wrap { position: relative; }
.help-search-wrap input { font-size: 15px; }
.help-cat { margin-top: 8px; }
.help-list { display: grid; gap: 8px; }
.help-article {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  overflow: hidden;
}
.help-article > summary {
  list-style: none;
  cursor: pointer;
  padding: 13px 16px;
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}
.help-article > summary::-webkit-details-marker { display: none; }
.help-article > summary::before {
  content: "＋";
  color: var(--muted);
  font-weight: 700;
  margin-right: 2px;
}
.help-article[open] > summary::before { content: "－"; }
.help-article[open] > summary { border-bottom: 1px solid var(--line); }
.help-q { font-weight: 650; font-size: 14.5px; }
.help-where { font-size: 12.5px; margin-left: auto; }
.help-body { padding: 4px 16px 16px; }
.help-body ol { margin: 8px 0 14px; padding-left: 20px; display: grid; gap: 7px; }
.help-body li { color: var(--ink); font-size: 13.5px; line-height: 1.5; }
.help-empty { padding: 20px; text-align: center; }
@media (max-width: 560px) {
  .help-where { margin-left: 0; flex-basis: 100%; }
}

.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.list { display: grid; gap: 10px; }
.notice { padding: 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); }
.notice h4 { margin: 0 0 6px; }
.notice p { margin: 0; color: var(--muted); }
.modal {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(16, 25, 35, 0.5);
  /* Lets the card scroll within the overlay when a form is taller than
     the screen, instead of clipping the Save button off-screen. */
  overflow-y: auto;
}
.modal-card {
  width: min(560px, 100%);
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  display: grid;
  gap: 14px;
  background: var(--panel);
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 20px 80px rgba(0,0,0,0.25);
}
.modal-card h3 { margin: 0; }

/* A result worth confirming but not worth stopping the admin's hands for -
   see toast() in app.js. Sits above modals (z-index 40) since it can follow
   a modal closing. */
.toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  z-index: 500;
  transform: translate(-50%, 12px);
  opacity: 0;
  transition: transform 0.2s ease, opacity 0.2s ease;
  background: var(--ok-bg);
  color: var(--ok-ink);
  border: 1px solid var(--ok-ink);
  border-radius: 8px;
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 600;
  box-shadow: 0 12px 40px rgba(0,0,0,0.2);
  max-width: min(420px, calc(100vw - 40px));
  text-align: center;
}
.toast.in { transform: translate(-50%, 0); opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .toast { transition: none; }
}
/* Pinned to the bottom of the scrolling card so Save and Cancel are always in
   view. On a long form (the student record runs to dozens of fields) the
   buttons otherwise sit hundreds of pixels below the fold, with nothing to
   show they are there - which read as "the Save option is missing". The
   negative margins let the bar span the card's own 20px padding, and the top
   border separates it from the fields scrolling underneath. */
/* "Apply this to these records" - a scrollable list of tick boxes rather than
   a <select multiple>, which is unusable on a phone and hides how much is
   selected behind a scrollbar. */
.check-list {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 12px;
  margin: 0 0 12px;
  max-height: 210px;
  overflow-y: auto;
}
.check-list legend {
  padding: 0 6px;
  font-size: 12.5px; font-weight: 650; text-transform: uppercase;
  letter-spacing: .04em; color: var(--faint);
}
.check-list .check-row {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 2px;
  font-size: 14px;
}
.check-list .check-row input { width: auto; margin: 0; flex: 0 0 auto; }
.check-list .check-all {
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: 4px; padding-bottom: 8px;
  position: sticky; top: -10px;
  background: var(--panel);
}

.modal-actions {
  display: flex; justify-content: flex-end; gap: 10px;
  position: sticky; bottom: -20px;
  margin: 6px -20px -20px; padding: 14px 20px;
  background: var(--panel);
  border-top: 1px solid var(--line);
  border-radius: 0 0 8px 8px;
}

/* Separates the optional "portal login" half of a form from the record
   itself, so it reads as a distinct decision rather than more fields. */
.form-note {
  margin: 6px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 13px;
}
.check-row { display: flex; align-items: center; gap: 9px; font-size: 14px; }
.check-row input { width: auto; margin: 0; }

/* Read-only modal content (class history) can outgrow the viewport. */
.modal-body { max-height: 60vh; overflow: auto; }
.mini-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.mini-table th, .mini-table td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
.mini-table th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }

/* Rows of small buttons inside toolbars and table cells */
.btn-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.btn-row .button { padding: 7px 10px; font-size: 13px; }
a.button { display: inline-flex; align-items: center; text-decoration: none; }

/* Filter bars above reports */
.filter-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; }
.filter-row label { flex: 1 1 180px; min-width: 150px; }
.filter-row .button { flex-shrink: 0; }

/* Profile photos */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 999px;
  object-fit: cover;
  background: var(--panel-2);
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  border: 1px solid var(--line);
}
.avatar-fallback { border: 1px solid var(--line); }

.notice-warn { border-left: 4px solid var(--warn); }
.mono-cell { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: 12px; }
h4 { margin: 6px 0; }

/* Printable documents (report card, certificate, ID card) */
.doc-header { text-align: center; margin-bottom: 18px; }
.doc-header h2 { margin: 0 0 4px; }
.doc-header h3 { margin: 10px 0 0; }
.doc-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px 18px;
  margin: 16px 0;
}
.doc-body { line-height: 1.9; }
.doc-signatures {
  display: flex;
  justify-content: space-between;
  margin-top: 46px;
  padding-top: 10px;
  color: var(--muted);
}
.doc-signatures span { border-top: 1px solid var(--ink); padding-top: 6px; min-width: 160px; text-align: center; }
.document-card { max-height: calc(100vh - 40px); overflow-y: auto; }

.id-card {
  width: 320px;
  max-width: 100%;
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
}
.id-card-header { padding: 12px; background: var(--brand); color: #fff; text-align: center; }
.id-card-header small { display: block; opacity: 0.85; }
.id-card-body { display: flex; gap: 12px; padding: 14px; }
.id-photo {
  display: grid;
  place-items: center;
  width: 84px;
  height: 96px;
  flex-shrink: 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: #f2f5f8;
  font-size: 11px;
  overflow: hidden;
}
.id-photo img { width: 100%; height: 100%; object-fit: cover; }
.id-details h4 { margin: 0 0 6px; }
.id-details p { margin: 2px 0; font-size: 13px; }
.id-card-footer { padding: 8px; background: #f2f5f8; text-align: center; font-size: 12px; color: var(--muted); }

/* A sheet of ID cards for a whole class. */
.id-card-sheet { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; }
.id-card-sheet .id-card { margin: 0; }
@media print {
  /* Keep a card from being split across two pages. */
  .id-card-sheet .id-card { break-inside: avoid; page-break-inside: avoid; }
}

/* A label wrapping its own control, used in toolbars. */
.inline-field { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; color: var(--muted); }
.inline-field input { margin: 0; }

/* Global search in the top bar. */
.global-search { position: relative; display: flex; align-items: center; }
.global-search > svg {
  position: absolute; left: 11px; width: 16px; height: 16px;
  color: var(--faint); pointer-events: none;
}
.global-search input {
  width: 260px;
  padding: 9px 12px 9px 34px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel-2);
  color: var(--ink);
  font-size: 14px;
}
.global-search input:focus {
  outline: none;
  border-color: var(--brand);
  background: var(--panel);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
.search-results {
  position: absolute;
  top: calc(100% + 7px);
  right: 0;
  width: 420px;
  max-width: 90vw;
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  z-index: 60;
}
.search-group + .search-group { margin-top: 4px; border-top: 1px solid var(--line-soft); padding-top: 4px; }
.search-group-head {
  margin: 0; padding: 7px 10px 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--faint);
}
.search-hit {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1px 10px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.search-hit:hover, .search-hit:focus-visible { background: var(--brand-soft); outline: none; }
.search-hit-title { font-size: 14px; font-weight: 600; }
.search-hit-meta { grid-row: 1 / span 2; align-self: center; font-size: 12px; color: var(--muted); white-space: nowrap; }
.search-hit-sub { grid-column: 1; font-size: 12.5px; color: var(--muted); }
.search-empty { margin: 0; padding: 16px; text-align: center; color: var(--muted); font-size: 13.5px; }

@media (max-width: 900px) {
  .global-search input { width: 150px; }
  .search-results { width: 300px; }
}

/* Dashboard "needs your attention" queues. */
.attention-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; }
.attention-card {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 15px 16px;
  text-align: left;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 4px solid var(--brand);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition: transform .12s ease, box-shadow .16s ease;
}
.attention-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.attention-card.warn { border-left-color: var(--warn, #b54708); }
.attention-count { font-size: 26px; font-weight: 700; line-height: 1; color: var(--brand); }
.attention-card.warn .attention-count { color: var(--warn, #b54708); }
.attention-label { font-size: 13.5px; color: var(--muted); }
.attention-clear { color: var(--muted); }
.attention-note { margin: 10px 0 0; font-size: 13px; }

/* Birthday chips. */
.birthday-row { display: flex; flex-wrap: wrap; gap: 10px; }
.birthday-chip {
  display: flex;
  flex-direction: column;
  padding: 9px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  font-size: 13px;
}
.birthday-chip small { color: var(--muted); font-size: 11.5px; }
.birthday-chip.today { border-color: var(--brand); background: var(--brand-soft); }

/* Fee receipt handed to a parent at the counter. */
.receipt { width: 640px; max-width: 100%; margin: 0 auto; padding: 4px 2px; }
.receipt-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; padding-bottom: 14px; border-bottom: 2px solid var(--ink); }
.receipt-head h3 { margin: 0 0 4px; }
.receipt-head p { margin: 1px 0; font-size: 12.5px; }
.receipt-stamp { text-align: right; flex: none; }
.receipt-stamp strong { display: block; letter-spacing: .08em; font-size: 13px; }
.receipt-stamp span { font-size: 12.5px; color: var(--muted); }

.receipt-meta { width: 100%; border-collapse: collapse; margin: 14px 0; font-size: 13.5px; }
.receipt-meta th, .receipt-meta td { padding: 5px 8px; text-align: left; vertical-align: top; }
.receipt-meta th { color: var(--muted); font-weight: 600; width: 15%; white-space: nowrap; }

.receipt-lines { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.receipt-lines th, .receipt-lines td { padding: 8px; border-bottom: 1px solid var(--line); text-align: left; }
.receipt-lines thead th { background: var(--panel-2); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.receipt-lines .right { text-align: right; }
.receipt-lines tfoot th, .receipt-lines tfoot td { border-bottom: none; padding: 5px 8px; }
.receipt-lines tfoot tr:first-child th { border-top: 1px solid var(--line); }
.receipt-due th { color: #b42318; border-top: 2px solid var(--ink) !important; }
.receipt-clear th { color: #027a48; border-top: 2px solid var(--ink) !important; }

.receipt-foot { display: flex; justify-content: space-between; align-items: flex-end; gap: 24px; margin-top: 26px; font-size: 13px; }
.receipt-foot p { margin: 2px 0; }
.receipt-sign { text-align: center; color: var(--muted); font-size: 12px; }
.receipt-sign span { display: block; width: 180px; border-top: 1px solid var(--ink); margin-bottom: 5px; }

/* Printing must not carry the app's dark theme onto paper. */
@media print {
  .receipt, .receipt * { color: #000 !important; background: transparent !important; }
  .receipt-lines thead th { background: #eee !important; }
}

@media print {
  /* Printing one document: hide everything with `visibility` (which the
     target can override even though its ancestors stay hidden) rather than
     `display`, since the print target may sit inside a modal overlay. */
  body.printing * { visibility: hidden; }
  body.printing .print-target,
  body.printing .print-target * { visibility: visible; }
  body.printing .print-target {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: var(--panel);
  }
  /* The dimmed overlay behind a modal would otherwise print as grey. */
  body.printing .modal { background: none; padding: 0; display: block; }
  .no-print { display: none !important; }
  .table-card { overflow: visible; box-shadow: none; }
  table { min-width: 0; }
  /* A printed list has to say what it is and when it was run; on screen the
     page heading already does, so the caption only exists on paper. */
  .print-only { display: table-caption !important; }
}
/* Hidden on screen. Declared outside @media print so it applies by default and
   the print block above is what turns it on. */
.print-only { display: none; }

.menu-toggle { display: none; }
.topbar-left { display: flex; align-items: center; gap: 12px; }
.sidebar-backdrop { display: none; }

@media (max-width: 900px) {
  .app { grid-template-columns: 1fr; }

  /* The nav has 20+ items - stacking it above the page content (the old
     behavior) forced mobile users to scroll past the whole menu before
     seeing anything. Instead it becomes a slide-out drawer. */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 280px;
    max-width: 85vw;
    height: 100vh;
    z-index: 50;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    box-shadow: 24px 0 48px rgba(0, 0, 0, 0.3);
  }
  .sidebar.open { transform: translateX(0); }
  .sidebar-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 45;
    background: rgba(16, 25, 35, 0.5);
  }
  .sidebar-backdrop.visible { display: block; }
  .menu-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 42px;
    height: 42px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--panel);
    color: var(--ink);
    font-size: 19px;
  }
  nav { grid-template-columns: 1fr; }
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .login-panel, .two-col { grid-template-columns: 1fr; }

  /* Title + badge text were wrapping onto multiple lines and inflating
     the topbar's height - force single-line with an ellipsis instead. */
  .topbar-left { min-width: 0; flex: 1; }
  .topbar-left > div { min-width: 0; }
  .topbar h2 { font-size: 19px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .topbar .eyebrow { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .top-actions { flex-shrink: 0; }
  #userBadge { max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
@media (max-width: 560px) {
  .content, .topbar { padding: 16px; }
  .top-actions { gap: 8px; }
  #userBadge { display: none; }
  .metrics { grid-template-columns: 1fr; }
  .login-panel { padding: 22px; }
}

/* ---------- Report filter bar ---------- */
.inline-field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
}
.inline-field select {
  width: auto;
  min-width: 130px;
  margin: 0;
}

/* A mark below the subject's passing threshold. */
.fail-mark { color: var(--danger, #c0392b); font-weight: 700; }

/* ---------- Admit card ---------- */
.admit-card .doc-meta,
.admit-card .doc-table { width: 100%; border-collapse: collapse; margin-bottom: 14px; }
.admit-card .doc-meta td { padding: 6px 8px; border: 1px solid var(--line, #ddd); font-size: 0.9rem; }
.admit-card .doc-table th,
.admit-card .doc-table td { padding: 7px 9px; border: 1px solid var(--line, #ddd); font-size: 0.88rem; text-align: left; }
.admit-card .doc-table th { background: rgba(0, 0, 0, 0.04); }
.doc-signature { margin-top: 34px; text-align: right; }
.doc-signature span { border-top: 1px solid var(--ink); padding-top: 6px; display: inline-block; min-width: 190px; text-align: center; font-size: 0.85rem; }

/* ---------- Settings screens ---------- */
.settings-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.settings-grid .modal-actions { grid-column: 1 / -1; }
.weekend-grid { flex-wrap: wrap; gap: 14px; }
.check-item { display: inline-flex; align-items: center; gap: 7px; font-size: 0.9rem; font-weight: 600; }
.check-item input { width: auto; margin: 0; }

/* ---------- Reports hub ---------- */
.report-picker { display: grid; gap: 14px; }
.report-group h5 { margin: 0 0 7px; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }
.report-group .btn-row { flex-wrap: wrap; gap: 7px; }

/* ---------- Online exam ---------- */
.exam-timer {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 1.05rem;
  padding: 5px 12px;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.06);
}
.exam-timer.urgent { background: var(--danger, #c0392b); color: #fff; }
.exam-question .check-item { display: flex; margin: 5px 0; }
.exam-question textarea { width: 100%; }

/* ---------- Teacher workspace ---------- */
.grid-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
.class-card .toolbar { margin-bottom: 6px; }
.class-card h4 { margin: 0; }
.class-card .btn-row { margin-top: 12px; }

/* ============================================================
   Visual refresh — typography, inputs, notices, empty states
   ============================================================ */

body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-size: 15px;
}
h1, h2, h3, h4 { letter-spacing: -.015em; }
.muted { color: var(--muted); font-size: 13.5px; }

/* Inputs share one look so forms read as a set. */
input, select, textarea {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 8px 11px;
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  transition: border-color .12s ease, box-shadow .12s ease;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 16%, transparent);
}
label { font-size: 13px; font-weight: 600; color: var(--ink); }

/* Notices / list cards */
.notice {
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  transition: box-shadow .14s ease;
}
.notice:hover { box-shadow: var(--shadow); }
.notice h4 { margin: 0 0 6px; font-size: 14.5px; font-weight: 650; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.notice p { margin: 0 0 4px; color: var(--muted); font-size: 13.5px; }

/* An empty table or list should read as a calm state, not a blank gap. */
.card.muted, td.muted {
  color: var(--muted);
  font-size: 13.5px;
}
td.muted { text-align: center; padding: 28px 16px; }

/* Section headings inside a page */
.content h4 {
  margin: 6px 0 -4px;
  font-size: 12px;
  font-weight: 650;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--faint);
}

/* Buttons sitting together in a table cell or toolbar */
.btn-row { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }

/* The user pill in the top bar */
.pill {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  box-shadow: var(--shadow-sm);
}

/* Modal */
.modal-card {
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--line);
  background: var(--panel);
}

/* Respect a reduced-motion preference. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

/* ============================================================
   Filter bar & pagination
   ============================================================ */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  padding: 14px 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.f-search { position: relative; flex: 1 1 260px; min-width: 200px; }
.f-search svg { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); width: 15px; height: 15px; color: var(--faint); }
.f-search input { width: 100%; padding-left: 34px; }
/* The browser's own clear button on type=search sits oddly with our padding. */
.f-search input::-webkit-search-cancel-button { cursor: pointer; }

.f-field { display: grid; gap: 4px; font-size: 11px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase; color: var(--faint); }
.f-field select, .f-field input { min-width: 130px; font-size: 13.5px; text-transform: none; letter-spacing: normal; font-weight: 500; color: var(--ink); }

.f-toggle { display: inline-flex; align-items: center; gap: 7px; font-size: 13.5px; font-weight: 500; padding-bottom: 8px; white-space: nowrap; }
.f-toggle input { width: auto; margin: 0; }
.f-clear { margin-left: auto; align-self: center; }

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 4px 0;
}
.pager .button { padding: 6px 11px; font-size: 12.5px; }
.pager-now { font-size: 12.5px; font-weight: 600; color: var(--muted); padding: 0 6px; white-space: nowrap; }
.pager select { padding: 5px 8px; font-size: 12.5px; }

@media (max-width: 640px) {
  .filter-bar { padding: 12px; }
  .f-field select, .f-field input { min-width: 0; width: 100%; }
  .f-field { flex: 1 1 140px; }
  .f-clear { margin-left: 0; }
  .pager { justify-content: center; }
}

/* ---------- Bulk import report ---------- */
.import-summary { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.pill {
  display: inline-block;
  padding: 4px 11px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 600;
}
.pill.ok { background: #ecfdf3; color: #027a48; border-color: #abefc6; }
.pill.bad { background: #fef3f2; color: #b42318; border-color: #fecdca; }
:root[data-theme="dark"] .pill.ok { background: #06281c; color: #6ee7b7; border-color: #14532d; }
:root[data-theme="dark"] .pill.bad { background: #2b0f0f; color: #fca5a5; border-color: #7f1d1d; }

/* A row that cannot be imported, or a mark outside the allowed range. */
.row-bad > td { background: #fef3f2; }
:root[data-theme="dark"] .row-bad > td { background: #2b0f0f; }

input[type="file"] {
  display: block;
  margin-top: 10px;
  padding: 10px;
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  background: var(--panel-2);
  width: 100%;
}

/* ---------- Marks sheet ---------- */
.marks-input { width: 100%; text-align: right; }
.marks-grade { font-weight: 700; }

/* ---------- Printable credential slips ---------- */
.credential-sheet .slips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
  margin-top: 14px;
}
.slip {
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  break-inside: avoid;
}
.slip strong { display: block; font-size: 15px; }
.slip small { color: var(--muted); }
.slip dl { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 10px 0 8px; font-size: 13px; }
.slip dt { color: var(--muted); }
.slip dd { margin: 0; word-break: break-all; }
.slip dd.pw { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-weight: 700; letter-spacing: .04em; }
.slip em { color: var(--muted); font-size: 11.5px; }

@media print {
  .slip { border-color: #999; }
  .no-print { display: none !important; }
}

/* ---------- Row selection ---------- */
.sel-col { width: 38px; text-align: center; }
.sel-col input { width: auto; margin: 0; }

/* Appears only while rows are selected, so it never occupies space on a
   screen nobody is selecting on. */
.selection-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
  padding: 10px 14px;
  border: 1px solid var(--brand);
  border-radius: var(--radius-sm);
  background: var(--brand-soft);
  color: var(--ink);
}
.selection-bar strong { font-size: 14px; }

/* One report card per printed page. */
.report-card-page { break-after: page; padding-bottom: 18px; margin-bottom: 18px; border-bottom: 1px solid var(--line); }
.report-card-page:last-child { break-after: auto; border-bottom: 0; }
.report-card-page .mini-table tfoot th { border-top: 2px solid var(--line); font-weight: 800; }

/* ---------- Fee challan & payslip documents ---------- */
.challan-lines .num, .mini-table .num { text-align: right; white-space: nowrap; }
.challan-lines tfoot th, .challan-lines tfoot td { border-top: 1px solid var(--line); padding-top: 8px; }
.challan-lines .balance-row th { border-top: 2px solid var(--ink); font-size: 15px; }
.discount-line td { color: var(--brand-2); }
.fine-line td { color: var(--danger); }
.reversed-row td { text-decoration: line-through; color: var(--muted); }
.void-stamp {
  margin-top: 16px;
  padding: 10px 14px;
  border: 2px dashed var(--danger);
  border-radius: var(--radius-sm);
  color: var(--danger);
  font-weight: 800;
  letter-spacing: .08em;
  text-align: center;
}
.payslip-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 12px; }
.payslip-net {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 2px solid var(--ink);
  font-size: 17px;
}
.payslip-net strong { font-size: 21px; }

/* Salary breakdown rows in the modal */
.component-row {
  display: grid;
  grid-template-columns: 2fr 1.1fr 1fr;
  gap: 8px;
  align-items: end;
  padding-bottom: 6px;
  border-bottom: 1px dashed var(--line-soft);
}

@media (max-width: 700px) {
  .payslip-columns, .component-row { grid-template-columns: 1fr; }
}

/* ---------- Tabbed forms ---------- */
.tab-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 4px 0 14px;
  border-bottom: 1px solid var(--line);
}
.tab-button {
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  padding: 8px 12px;
  color: var(--muted);
  font-weight: 650;
  font-size: 13.5px;
  cursor: pointer;
}
.tab-button:hover { color: var(--ink); }
.tab-button.active { color: var(--brand); border-bottom-color: var(--brand); }
.tab-pane { display: grid; gap: 14px; }

/* Possible-duplicate warning inside the admission form */
.dup-warning { display: grid; gap: 10px; }
.dup-warning strong { color: var(--warn); }

/* ---------- Automation ---------- */
.job-desc { font-size: 12.5px; margin-top: 2px; max-width: 46ch; }

/* Setup checklist on the dashboard: each gap with its fix beside it. */
.setup-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* ---- Completeness pass: chips, bars, remark box, trend arrows ---- */
/* A chip is a status badge for words outside .status's fixed workflow
   vocabulary. It shares the pill shape and the semantic colours, which are
   declared once with .status above - not repeated here. */
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; line-height: 1.6; white-space: nowrap;
}
.chip::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; opacity: .8;
}

/* Coverage bar. There is no charting library in this app and pulling one in
   for a percentage would be a lot of weight for very little. */
/* --border and --accent are not tokens in this stylesheet, so these var()
   calls always fell through to their hardcoded fallbacks and the bar stayed
   light-grey/blue in dark mode. They use the real tokens now. */
.bar {
  display: inline-block; width: 90px; height: 8px; border-radius: 999px;
  background: var(--line); overflow: hidden; vertical-align: middle;
  margin-right: 6px;
}
.bar-fill {
  display: block; height: 100%; border-radius: 999px;
  background: var(--brand); transition: width .35s ease;
}
/* Class strength. The colour carries the reading - a class at 37/40 needs
   watching and one over capacity needs acting on - so it comes from the same
   status tokens as every other signal rather than a private palette. */
.bar-fill.is-tight { background: var(--pend-ink); }
.bar-fill.is-over  { background: var(--bad-ink); }
.strength-figure { font-variant-numeric: tabular-nums; font-weight: 600; }
/* Several chips in one cell. Each chip is nowrap by design, so without this
   they demand a column as wide as all of them laid end to end. */
.chip-stack { display: flex; flex-wrap: wrap; gap: 4px; }

/* Search-over-a-list picker. A floating dropdown would have to be positioned,
   clipped and keyboard-handled by hand; a sized <select> under a search box is
   the browser doing all of that already. */
.picker-search { margin-bottom: 6px; }
.picker-list {
  width: 100%;
  font-family: inherit;
  /* The option text carries the phone number and the other children, so it is
     long on purpose - and must not be cut off at the edge of the dialog. */
  overflow-x: auto;
}
.picker-list option {
  padding: 6px 8px;
  border-bottom: 1px solid var(--line-soft);
  white-space: nowrap;
}
.picker-list option:last-child { border-bottom: none; }
.picker-list option:disabled { color: var(--muted); font-style: italic; }

/* The report card had two signature lines and nowhere to write above them. */
.remark-box {
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 10px 12px; margin: 12px 0; min-height: 56px;
}
.remark-box p { margin: 4px 0 0; }

.trend-up   { color: var(--ok-ink); font-weight: 600; }
.trend-down { color: var(--bad-ink); font-weight: 600; }

@media print {
  .bar { border: 1px solid #999; }
  .bar-fill { background: #555; }
}

/* ---- Classroom poster + Raast payment block ---- */
.poster { text-align: center; max-width: 640px; margin: 0 auto; }
.poster-class { font-size: 40px; margin: 6px 0 2px; letter-spacing: -0.5px; }
.poster-qr { margin: 18px auto 10px; line-height: 0; }
.poster-qr svg { width: min(340px, 70vw); height: auto; }
.poster-body h3 { margin: 4px 0; font-size: 20px; }
.poster-url {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; word-break: break-all; margin: 10px 0;
}

/* The QR sits beside its instructions so the challan stays one page. */
.challan-pay {
  display: flex; gap: 14px; align-items: flex-start;
  margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--line);
}
.challan-pay .raast-qr { flex: 0 0 auto; line-height: 0; }
.challan-pay .raast-qr svg { width: 132px; height: 132px; }
.challan-pay p { margin: 3px 0; font-size: 12.5px; }
.challan-pay strong { font-size: 14px; }

@media print {
  /* A QR printed in anything but true black loses contrast on a cheap
     photocopier, which is how most challans reach a parent. */
  .poster-qr svg path, .challan-pay .raast-qr svg path { fill: #000 !important; }
  .poster-qr svg rect, .challan-pay .raast-qr svg rect { fill: #fff !important; }
  .poster { page-break-inside: avoid; }
  .challan-pay { page-break-inside: avoid; }
}

/* ---- Module licensing ---- */
.licence-bundle .licence-modules {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 6px 14px; margin-top: 8px;
}
.licence-module { display: flex; align-items: center; gap: 7px; font-size: 13.5px; }
.licence-module input { margin: 0; }

/* A withheld module in "greyed" mode: still listed, obviously not included. */
.nav-button.nav-locked { opacity: .55; }
.nav-button.nav-locked:hover { opacity: .75; }
.nav-padlock { font-size: 11px; opacity: .8; }

.upgrade-panel { text-align: center; padding: 40px 24px; max-width: 520px; margin: 24px auto; }
.upgrade-panel .upgrade-lock { font-size: 40px; line-height: 1; margin-bottom: 10px; }
.upgrade-panel h3 { margin: 0 0 8px; }

/* ==================== TIMETABLE GRID ==================== */
/* A timetable is read down a day or across a period, so it is drawn as a
   grid. Wide grids scroll inside their own box rather than pushing the page
   sideways. */
.tt-wrap { overflow-x: auto; }
.tt-grid { width: 100%; border-collapse: separate; border-spacing: 4px; min-width: 640px; }
.tt-grid th, .tt-grid td { vertical-align: top; }
.tt-grid thead th {
  text-align: center; font-size: 12.5px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); padding: 4px 0;
}
.tt-grid .tt-time {
  width: 74px; text-align: right; padding-right: 8px; white-space: nowrap;
  font-variant-numeric: tabular-nums; font-weight: 600; font-size: 13px;
}
.tt-grid .tt-time span { display: block; }
.tt-grid .tt-time small { display: block; font-weight: 400; color: var(--muted); font-size: 11.5px; }

.tt-cell {
  border-radius: 8px; padding: 7px 9px; line-height: 1.35;
  border: 1px solid transparent; min-width: 118px;
}
.tt-cell strong { display: block; font-size: 13.5px; }
.tt-cell span { display: block; font-size: 11.5px; opacity: .85; }
.tt-cell .tt-room { font-variant-numeric: tabular-nums; opacity: .7; }
.tt-cell[data-action] { cursor: pointer; }
.tt-cell[data-action]:hover { filter: brightness(.96); }

.tt-empty { border: 1px dashed var(--line); border-radius: 8px; min-height: 54px; }
.tt-empty[data-action] { cursor: pointer; }
.tt-empty[data-action]:hover { background: var(--bg, #f6f8fb); }
.tt-empty[data-action]::after {
  content: "+"; display: block; text-align: center; color: var(--muted);
  opacity: 0; font-size: 18px; line-height: 54px;
}
.tt-empty[data-action]:hover::after { opacity: .5; }

/* Each subject keeps the same colour everywhere it appears. */
.tone-1 { background: #e7f0ff; color: #12386e; }
.tone-2 { background: #e7f6ec; color: #14532d; }
.tone-3 { background: #fdf3e2; color: #7a4a00; }
.tone-4 { background: #fdeaf1; color: #7a1236; }
.tone-5 { background: #efe9fb; color: #3c2a72; }
.tone-6 { background: #e4f5f7; color: #0d4f57; }
.tone-7 { background: #fbeee4; color: #7a3610; }
.tone-8 { background: #eef0f2; color: #33404d; }

.tt-today { border-left: 3px solid var(--brand); }
.tt-print-head { margin: 0 0 8px; }

@media (prefers-color-scheme: dark) {
  .tone-1 { background: #16304f; color: #cfe2ff; }
  .tone-2 { background: #16341f; color: #cdeed7; }
  .tone-3 { background: #3a2c12; color: #f3dfb4; }
  .tone-4 { background: #3d1a26; color: #f6ccd9; }
  .tone-5 { background: #2b2350; color: #ded4f8; }
  .tone-6 { background: #123338; color: #c8e9ee; }
  .tone-7 { background: #3a2416; color: #f2d6c2; }
  .tone-8 { background: #262d35; color: #dbe2ea; }
}

/* ==================== CALENDAR MONTH GRID ==================== */
.cal-nav { align-items: center; gap: 10px; }
.cal-title { font-size: 16px; min-width: 170px; text-align: center; }

.cal-grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
}
.cal-head {
  text-align: center; font-size: 12px; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted); padding: 4px 0;
}
.cal-day {
  min-height: 82px; border: 1px solid var(--line); border-radius: 8px;
  padding: 4px 5px; display: flex; flex-direction: column; gap: 2px; overflow: hidden;
}
.cal-blank { border: 0; }
.cal-weekend { background: var(--bg, #f6f8fb); }
.cal-today { border-color: var(--brand); border-width: 2px; }
.cal-date { font-size: 12px; font-weight: 600; color: var(--muted); }
.cal-today .cal-date { color: var(--brand); }
.cal-event {
  font-size: 11px; line-height: 1.3; padding: 2px 5px; border-radius: 4px;
  background: #e7f0ff; color: #12386e; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; cursor: default;
}
.cal-holiday { background: #fdeaea; color: #b3261e; }
.cal-exam    { background: #fdf3e2; color: #9a6300; }
.cal-event_  { background: #e7f0ff; }
.cal-more { font-size: 10.5px; color: var(--muted); }

@media (prefers-color-scheme: dark) {
  .cal-event   { background: #16304f; color: #cfe2ff; }
  .cal-holiday { background: rgba(179,38,30,.22); color: #f5928c; }
  .cal-exam    { background: rgba(154,99,0,.22); color: #f0c069; }
}

/* On a phone a month grid becomes unreadable, so it scrolls as a unit. */
@media (max-width: 640px) {
  .cal-grid { min-width: 560px; }
  .printable { overflow-x: auto; }
}

@media print {
  .tt-grid { min-width: 0; }
  .tt-cell { border: 1px solid #999 !important; }
  .cal-day { min-height: 64px; }
  .cal-grid { min-width: 0; }
}

/* ==================== ATTENDANCE REGISTER ==================== */
/* One tap per pupil. A dropdown per row meant open-scroll-select forty times. */
.att-switch { display: inline-flex; gap: 3px; }
.att-opt {
  min-width: 38px; padding: 5px 0; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--line); background: transparent;
  color: var(--muted); font-weight: 600; font-size: 13px; line-height: 1.2;
}
.att-opt:hover { background: var(--bg, #f2f5f9); }
.att-opt.on { color: #fff; border-color: transparent; }
.att-present.on { background: #1a7f3c; }
.att-absent.on  { background: #b3261e; }
.att-late.on    { background: #9a6300; }
.att-leave.on   { background: #4b5563; }

.att-table td { vertical-align: middle; }
.att-roll { font-variant-numeric: tabular-nums; white-space: nowrap; }
.att-table input[data-field="remarks"] { width: 100%; min-width: 120px; }

.att-tally { display: flex; gap: 16px; flex-wrap: wrap; align-items: baseline; padding: 10px 14px; }
.att-count strong { font-size: 17px; margin-right: 4px; }
.att-count.att-present strong { color: var(--ok-ink); }
.att-count.att-absent strong  { color: var(--bad-ink); }
.att-count.att-late strong    { color: var(--pend-ink); }
.att-count.att-leave strong   { color: var(--muted); }

/* ==================== TIMETABLE CLASHES ==================== */
.tt-clash { outline: 2px solid var(--bad-ink); outline-offset: -2px; }
.tt-warn { font-size: 12px; }
.tt-health { border-left: 3px solid var(--bad-ink); }

@media (max-width: 640px) {
  .att-opt { min-width: 34px; }
}

@media print {
  .att-switch, .att-tally { display: none; }
  .tt-clash { outline: 2px solid #000; }
}

/* ==================================================================
   PORTAL & READABILITY PASS
   Aimed at the people who did not choose to use this software:
   students, parents and office staff. Bigger targets, plainer
   language, money that lines up, and tables that stop being tables
   on a phone.
   ================================================================== */

/* ---------- Money and figures ---------- */
/* Right-aligned with tabular numerals so amounts line up digit-for-digit
   down a column and can be compared without reading them. */
.num, td.num, th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.money { font-variant-numeric: tabular-nums; white-space: nowrap; }
.money-strong { font-weight: 650; font-variant-numeric: tabular-nums; }
/* An amount that is owed. Colour alone never carries the meaning - the
   figure is always beside a label saying what it is. */
.money-due { color: var(--bad-ink); font-weight: 650; }
.money-paid { color: var(--ok-ink); font-weight: 650; }

/* ---------- Focus ---------- */
/* Keyboard users could not see where they were. :focus-visible keeps this
   off mouse clicks so nothing changes for everyone else. */
:where(button, a, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---------- Friendlier empty states ---------- */
.blank-slate {
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: 40px 24px;
  text-align: center;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.blank-slate .blank-icon {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--brand-soft);
  color: var(--brand-ink);
  font-size: 20px;
}
.blank-slate h4 { margin: 0; font-size: 15.5px; }
.blank-slate p { margin: 0; color: var(--muted); max-width: 42ch; font-size: 13.5px; }

/* ---------- Loading skeleton ---------- */
/* Replaces the jump from an empty screen to a full one, which reads as a
   stall on a slow connection. */
.skeleton-row {
  height: 14px; border-radius: 6px; margin: 12px 0;
  background: linear-gradient(90deg, var(--line-soft) 25%, var(--line) 37%, var(--line-soft) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.3s ease-in-out infinite;
}
@keyframes shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
@media (prefers-reduced-motion: reduce) {
  .skeleton-row { animation: none; }
  .bar-fill { transition: none; }
}

/* ---------- Row actions that no longer eat the table ---------- */
/* Six always-visible buttons took 28% of the students table width and
   pushed rows to 98px tall. The common action stays inline, the rest
   fold into this menu. */
.row-actions { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }
.more-menu { position: relative; display: inline-block; }
.more-toggle {
  border: 1px solid var(--line); background: var(--panel); color: var(--muted);
  border-radius: var(--radius-sm); cursor: pointer;
  width: 32px; height: 32px; line-height: 1; font-size: 16px;
}
.more-toggle:hover { background: var(--panel-2); color: var(--ink); }
.more-list {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 40;
  min-width: 172px; padding: 6px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  display: none;
}
.more-menu.open .more-list { display: grid; gap: 2px; }
.more-list button {
  width: 100%; text-align: left; border: 0; background: transparent;
  padding: 8px 10px; border-radius: var(--radius-sm);
  font: inherit; font-size: 13.5px; color: var(--ink); cursor: pointer;
}
.more-list button:hover { background: var(--brand-soft); color: var(--brand-ink); }
.more-list button.danger-item { color: var(--danger); }
.more-list button.danger-item:hover { background: var(--bad-bg); }

/* ---------- Portal hero ---------- */
/* Same band as the staff hub's, so a parent and a principal are looking at one
   product. It was a pale tint with dark text while the hub band was saturated
   with white text, which read as two different systems sharing a login. */
.portal-hero {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 16px; padding: 22px 24px; margin-bottom: 16px;
  border-radius: var(--radius);
  color: #fff;
  background: linear-gradient(120deg, #00194a 0%, #003891 45%, #008766 100%);
  box-shadow: var(--shadow);
}
.portal-hero h2 { margin: 0 0 4px; font-size: 21px; font-weight: 650; letter-spacing: -.01em; }
.portal-hero p { margin: 0; color: rgba(255, 255, 255, .9); font-size: 13.5px; }
.hero-date {
  text-align: right; color: rgba(255, 255, 255, .78); font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* ---------- Big, plain stat tiles for the portals ---------- */
/* The admin metric card is dense by design. Parents and students see four
   numbers in total, so those can be large and unambiguous. */
.portal-stats {
  display: grid; gap: 14px; margin-bottom: 18px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.stat-tile {
  display: flex; align-items: center; gap: 14px;
  padding: 16px 18px; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.stat-ico {
  flex: 0 0 auto; width: 42px; height: 42px; border-radius: 12px;
  display: grid; place-items: center; font-size: 19px;
  background: var(--brand-soft); color: var(--brand-ink);
}
.stat-tile.is-ok  .stat-ico { background: var(--ok-bg);   color: var(--ok-ink); }
.stat-tile.is-due .stat-ico { background: var(--bad-bg);  color: var(--bad-ink); }
.stat-tile.is-warn .stat-ico { background: var(--pend-bg); color: var(--pend-ink); }
.stat-body { min-width: 0; }
.stat-value {
  font-size: 24px; font-weight: 680; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; line-height: 1.15;
}
.stat-label { font-size: 12.5px; color: var(--muted); margin-top: 2px; }

/* ---------- Attendance ring ---------- */
/* A percentage is easier to judge as a filled circle than as digits. */
.ring { --pct: 0; position: relative; width: 96px; height: 96px; flex: 0 0 auto; }
.ring::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(var(--ring-color, var(--brand)) calc(var(--pct) * 1%), var(--line) 0);
}
.ring::after {
  content: ""; position: absolute; inset: 9px; border-radius: 50%;
  background: var(--panel);
}
.ring-text {
  position: absolute; inset: 0; display: grid; place-items: center;
  z-index: 1; font-weight: 680; font-size: 19px;
  font-variant-numeric: tabular-nums;
}
.ring.good { --ring-color: var(--brand-2); }
.ring.mid  { --ring-color: var(--warn); }
.ring.low  { --ring-color: var(--danger); }

/* ---------- Sparkline / mini bar chart ---------- */
.spark { display: block; width: 100%; height: 60px; overflow: visible; }
.spark-fill { fill: var(--brand); opacity: .12; }
.spark-line { fill: none; stroke: var(--brand); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.mini-bars { display: flex; align-items: flex-end; gap: 4px; height: 64px; }
.mini-bar { flex: 1; border-radius: 4px 4px 0 0; background: var(--brand); min-height: 3px; opacity: .85; }
.mini-bar.is-low { background: var(--danger); }
.chart-card { padding: 16px 18px; }
.chart-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); margin-top: 10px; }
.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend i { width: 9px; height: 9px; border-radius: 3px; display: inline-block; background: var(--brand); }

/* ---------- Record profile hub (student/teacher/etc detail pages) ---------- */
/* One consistent "hub" shell for a single record: identity + quick actions up
   top, then a summary grid before the full detail. Built entirely from the
   tokens and components already above - card, stat-tile, chip, table-card -
   so it themes for free and doesn't fork the design language. */
.profile-hero {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; flex-wrap: wrap; padding: 20px 22px;
}
.profile-hero-id { display: flex; align-items: center; gap: 16px; min-width: 0; }
.avatar-xl {
  width: 76px; height: 76px; font-size: 26px; flex: 0 0 auto;
  border: 2px solid var(--panel-2);
}
.profile-hero-name { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.profile-hero-name h2 { margin: 0; font-size: 21px; letter-spacing: -.01em; }
.profile-hero-sub { margin: 4px 0 0; color: var(--muted); font-size: 13.5px; }
.profile-hero-actions { flex: 0 0 auto; }

/* Two grid tiers: a 3-up row of context panels, then a 4-up row of summary
   cards. Same auto-fit/minmax approach as the rest of the app so both collapse
   to one column on a phone without a separate breakpoint rule. */
.profile-grid-3 { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.profile-grid-4 { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.profile-panel { display: flex; flex-direction: column; gap: 10px; height: 100%; }
.profile-panel h4 { margin: 0; }

/* A stat tile that also carries a trend line beneath the figure - the ring/
   sparkline pairing everywhere else in the app is horizontal; this is the one
   spot small enough to need it stacked instead. */
.stat-tile-trend { flex-direction: column; align-items: stretch; gap: 10px; }
.stat-tile-row { display: flex; align-items: center; gap: 14px; }
.spark-mini { display: block; width: 100%; height: 26px; }
.spark-mini .spark-line { fill: none; stroke: var(--brand); stroke-width: 2.4; stroke-linejoin: round; stroke-linecap: round; }
.spark-mini .spark-fill { fill: var(--brand); opacity: .1; }

/* Three equal quick-action tiles (ID card / QR / login) - a button grid, not
   a btn-row, because each needs an icon and a label rather than just text. */
.quick-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.quick-action {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 12px 6px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--panel-2); color: var(--ink); font-size: 12px; font-weight: 600;
  cursor: pointer; text-align: center;
}
.quick-action:hover { background: var(--brand-soft); border-color: var(--brand); color: var(--brand-ink); }
.quick-action .qa-ico { font-size: 19px; }

/* A filled alert box for "nobody can be reached" - notice-warn elsewhere is
   only a left rule, which reads as a caption; the one thing on this page that
   is a genuine problem (no contact route to a family) earns a filled box. */
.alert-box {
  display: flex; gap: 10px; padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--pend-bg); color: var(--pend-ink); font-size: 13px; align-items: flex-start;
}
.alert-box b { display: block; margin-bottom: 2px; }

/* Present/absent/late/leave, read off the same ring colour bands so the
   legend and the ring agree without a second palette to keep in sync. */
.breakdown-list { display: flex; flex-direction: column; gap: 7px; margin-top: 2px; }
.breakdown-item { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.breakdown-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; background: var(--line); }
.breakdown-dot.present { background: var(--brand-2); }
.breakdown-dot.absent { background: var(--danger); }
.breakdown-dot.late { background: var(--warn); }
.breakdown-item b { margin-left: auto; font-variant-numeric: tabular-nums; }

/* A vertical line of dots - what happened to this record, most recent first. */
.timeline { display: flex; flex-direction: column; }
.timeline-item { display: flex; gap: 10px; position: relative; padding-bottom: 14px; }
.timeline-item:last-child { padding-bottom: 0; }
.timeline-item::before {
  content: ""; position: absolute; left: 11px; top: 22px; bottom: 0; width: 1px; background: var(--line);
}
.timeline-item:last-child::before { display: none; }
.timeline-dot {
  flex: 0 0 auto; width: 23px; height: 23px; border-radius: 50%;
  display: grid; place-items: center; font-size: 12px;
  background: var(--brand-soft); color: var(--brand-ink); z-index: 1;
}
.timeline-body { min-width: 0; padding-top: 1px; }
.timeline-body p { margin: 0; font-size: 13px; font-weight: 600; }
.timeline-body span { font-size: 11.5px; color: var(--muted); }

@media (max-width: 560px) {
  .profile-hero { flex-direction: column; align-items: stretch; }
  .profile-hero-actions { width: 100%; }
  .profile-hero-actions .btn-row { width: 100%; }
}

/* ---------- Child cards (parent portal) ---------- */
.child-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.child-card {
  padding: 16px 18px; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
.child-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.child-avatar {
  width: 44px; height: 44px; border-radius: 50%; flex: 0 0 auto;
  display: grid; place-items: center; font-weight: 680; font-size: 16px;
  background: var(--brand-soft); color: var(--brand-ink);
}
.child-name { font-weight: 650; }
.child-class { font-size: 12.5px; color: var(--muted); }
.child-facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align: center; }
.child-fact strong { display: block; font-size: 16px; font-variant-numeric: tabular-nums; }
.child-fact span { font-size: 11.5px; color: var(--muted); }

/* ---------- Fee / due cards ---------- */
.due-card {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 14px; padding: 16px 18px;
  background: var(--panel); border: 1px solid var(--line);
  border-left: 3px solid var(--warn); border-radius: var(--radius);
}
.due-card.is-overdue { border-left-color: var(--danger); }
.due-card.is-clear { border-left-color: var(--brand-2); }
.due-title { font-weight: 650; }
.due-meta { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.due-amount { font-size: 19px; font-weight: 680; font-variant-numeric: tabular-nums; }

/* ---------- Homework cards ---------- */
.hw-grid { display: grid; gap: 12px; }
.hw-card {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between;
  padding: 14px 16px; background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.hw-card.is-due { border-left: 3px solid var(--warn); }
.hw-card.is-late { border-left: 3px solid var(--danger); }
.hw-card.is-done { border-left: 3px solid var(--brand-2); }
.hw-title { font-weight: 620; }
.hw-meta { font-size: 12.5px; color: var(--muted); margin-top: 3px; display: flex; gap: 10px; flex-wrap: wrap; }

/* ---------- Section headings that explain themselves ---------- */
.section-note { margin: -4px 0 12px; color: var(--muted); font-size: 13px; }

/* ---------- Density toggle ---------- */
:root[data-density="compact"] td { padding: 7px 12px; }
:root[data-density="compact"] th { padding: 7px 12px; }
:root[data-density="compact"] .button { padding: 6px 10px; font-size: 13px; }

/* ==================================================================
   PHONE LAYOUT
   Below 720px a wide table scrolls sideways, which is the wrong answer
   on a phone - parents lose the column they were reading. Rows become
   stacked cards instead, each cell labelled from its header.
   ================================================================== */
@media (max-width: 720px) {
  .responsive-table thead { display: none; }
  /* Every table carries min-width: 760px, which is exactly what makes a phone
     scroll sideways. Stacked rows need no minimum at all. */
  .responsive-table { min-width: 0; }
  .table-card:has(.responsive-table) { overflow: visible; }
  .responsive-table, .responsive-table tbody, .responsive-table tr, .responsive-table td { display: block; width: 100%; }
  .responsive-table tr {
    border: 1px solid var(--line); border-radius: var(--radius);
    margin-bottom: 10px; padding: 6px 2px; background: var(--panel);
  }
  .responsive-table tr:hover { background: var(--panel); }
  .responsive-table td {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    border: 0; padding: 8px 14px; text-align: right;
  }
  .responsive-table td::before {
    content: attr(data-label);
    flex: 0 0 auto; text-align: left;
    font-size: 11.5px; font-weight: 650; text-transform: uppercase;
    letter-spacing: .04em; color: var(--faint);
  }
  /* A cell with no header label (checkbox, action buttons) spans the row. */
  .responsive-table td:empty { display: none; }
  .responsive-table td[data-label=""] { justify-content: flex-end; }
  .responsive-table td[data-label=""]::before { content: none; }

  .portal-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
  .stat-tile { padding: 13px 14px; gap: 11px; }
  .stat-value { font-size: 20px; }
  .stat-ico { width: 36px; height: 36px; font-size: 17px; }
  .portal-hero { padding: 16px; }
  .portal-hero h2 { font-size: 18px; }
  .hero-date { text-align: left; }
  /* Comfortable thumb targets for people using this on a phone. */
  .button { min-height: 40px; }
  .due-card, .hw-card { flex-direction: column; align-items: stretch; }
  .due-card .button, .hw-card .button { width: 100%; }
}

@media print {
  .portal-hero, .more-menu, .skeleton-row { display: none; }
  .stat-tile, .child-card, .due-card, .hw-card { border: 1px solid #999; box-shadow: none; }
}

/* ---------- Position certificates ---------- */
.certificate { text-align: center; padding: 34px 28px; margin-bottom: 18px; border: 2px solid var(--brand); }
.certificate h2 { margin: 0 0 4px; font-size: 21px; }
.cert-kicker { margin: 0 0 20px; color: var(--muted); letter-spacing: .12em; text-transform: uppercase; font-size: 12px; }
.cert-body { margin: 6px 0; color: var(--muted); }
.cert-name { margin: 12px 0; font-size: 26px; font-weight: 680; letter-spacing: -.01em; }
.cert-sign { display: flex; justify-content: space-around; margin-top: 40px; color: var(--muted); font-size: 13px; }
.cert-sign span { border-top: 1px solid var(--line); padding-top: 6px; min-width: 150px; }
@media print { .certificate { page-break-after: always; border-color: #000; } }

/* ---------- Per-student attendance detail ---------- */
.link-button {
  border: 0; background: none; padding: 0; font: inherit;
  color: var(--brand-ink); cursor: pointer; text-align: left;
}
.link-button:hover { text-decoration: underline; }

.att-detail-head { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.att-month { margin-top: 16px; }
.att-month h5 { margin: 0 0 8px; font-size: 13px; color: var(--muted); font-weight: 650; }
.att-days { display: flex; flex-wrap: wrap; gap: 5px; }
/* One square per marked day. The number keeps it verifiable against a
   register; the letter means it does not rely on colour alone. */
.att-day {
  width: 38px; height: 40px; border-radius: var(--radius-sm);
  display: grid; place-items: center; gap: 0; line-height: 1.1;
  border: 1px solid var(--line); font-variant-numeric: tabular-nums;
}
.att-day b { font-size: 12px; font-weight: 650; }
.att-day i { font-size: 10px; font-style: normal; opacity: .85; }
.att-day.att-present { background: var(--ok-bg);   color: var(--ok-ink);   border-color: transparent; }
.att-day.att-absent  { background: var(--bad-bg);  color: var(--bad-ink);  border-color: transparent; }
.att-day.att-late    { background: var(--pend-bg); color: var(--pend-ink); border-color: transparent; }
.att-day.att-leave   { background: var(--panel-2); color: var(--muted); }

/* ---------- Datesheet ---------- */
.datesheet-day { margin-bottom: 18px; }
.datesheet-day h4 { margin: 0 0 8px; font-size: 15px; }
.datesheet-day h4 .muted { font-weight: 400; font-size: 13px; }
/* The bulk entry grid is a form, so it keeps its own column widths and is
   deliberately excluded from the responsive restack. */
.datesheet-grid { min-width: 640px; }
.datesheet-grid input { width: 100%; min-width: 74px; }
.datesheet-grid td:first-child { white-space: nowrap; font-weight: 600; }

/* ---------- Sparkline ---------- */
.spark-wrap { margin: 6px 0 14px; }
.spark { width: 100%; height: 76px; }
/* preserveAspectRatio="none" stretches the path, so the stroke must not
   scale with it or the line thickens as the card widens. */
.spark-line { vector-effect: non-scaling-stroke; }
.spark-dot { fill: var(--brand); vector-effect: non-scaling-stroke; }

/* ---------- Timetable selection ---------- */
.tt-cell { position: relative; }
/* Small and out of the way until wanted - the grid is read far more often
   than it is bulk-edited. */
.tt-pick {
  position: absolute; top: 4px; right: 4px; margin: 0;
  opacity: .35; transition: opacity .12s ease; cursor: pointer;
}
.tt-cell:hover .tt-pick, .tt-pick:checked, .tt-pick:focus-visible { opacity: 1; }
.tt-picked { outline: 2px solid var(--brand); outline-offset: -2px; }
@media print { .tt-pick { display: none; } }

/* ---------- Payment proofs ---------- */
.proof-card { border-left: 3px solid var(--warn); }
.proof-card.approved { border-left-color: var(--brand-2); }
.proof-card.rejected { border-left-color: var(--danger); }
.proof-head { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; align-items: flex-start; }
.proof-facts {
  display: flex; flex-wrap: wrap; gap: 8px 20px; margin-top: 12px;
  font-size: 13px; color: var(--muted);
}
.proof-facts b { color: var(--ink); font-weight: 600; margin-right: 4px; }

/* ---------- Fee structure head chips ---------- */
.head-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.head-chip {
  display: inline-flex; gap: 6px; align-items: baseline;
  padding: 3px 9px; border-radius: 999px;
  background: var(--panel-2); border: 1px solid var(--line);
  font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.head-chip b { font-weight: 600; color: var(--muted); }

/* ---------------------------------------------------------------------------
   Hub dashboard
   Every colour below resolves through a token, so the light/dark blocks at the
   top of this file cover it. The only literal colours are the per-group badge
   fills, set inline from HUB_COLORS, which carry white icons in both themes.
   --------------------------------------------------------------------------- */

.hub-hero {
  border-radius: var(--radius);
  padding: 22px 24px;
  margin-bottom: 14px;
  color: #fff;
  background: linear-gradient(120deg, #00194a 0%, #003891 45%, #008766 100%);
  box-shadow: var(--shadow);
}
.hub-hero-greet { margin: 0; font-size: 21px; font-weight: 650; letter-spacing: -.01em; }
.hub-hero-role {
  margin: 3px 0 0; font-size: 11.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .08em; color: rgba(255, 255, 255, .72);
}
.hub-hero-day { margin: 12px 0 0; font-size: 14px; color: rgba(255, 255, 255, .9); }

.hub-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.hub-chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 7px 12px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--line);
  font-size: 12.5px; color: var(--muted); white-space: nowrap;
  box-shadow: var(--shadow-sm);
}
.hub-chip b { font-size: 15px; font-weight: 650; color: var(--ink); font-variant-numeric: tabular-nums; }
.hub-chip.ok  { background: var(--ok-bg);   border-color: transparent; }
.hub-chip.ok b  { color: var(--ok-ink); }
.hub-chip.pend { background: var(--pend-bg); border-color: transparent; }
.hub-chip.pend b { color: var(--pend-ink); }
.hub-chip.bad { background: var(--bad-bg);  border-color: transparent; }
.hub-chip.bad b { color: var(--bad-ink); }

/* Columns rather than grid: cards are different heights and should stack
   tightly under one another instead of leaving a ragged row of gaps. */
.hub { columns: 4 270px; column-gap: 16px; margin-top: 4px; }

.hub-card {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  margin: 0 0 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.hub-head {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line-soft);
}
.hub-head h3 {
  margin: 0; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--ink);
}
.hub-badge {
  display: grid; place-items: center;
  width: 34px; height: 34px; flex: 0 0 34px;
  border-radius: 10px; color: #fff;
}
.hub-badge .nav-icon { width: 18px; height: 18px; }

.hub-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(102px, 1fr));
  gap: 8px; padding: 14px 16px 16px;
}
.hub-tile {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 14px 8px 12px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: inherit; color: var(--ink);
  text-align: center;
  transition: transform .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.hub-tile:hover {
  transform: translateY(-2px);
  border-color: var(--brand);
  box-shadow: var(--shadow);
}
.hub-tile:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.hub-emoji { font-size: 21px; line-height: 1; }
.hub-label { font-size: 12px; line-height: 1.3; color: var(--ink); }

@media (max-width: 720px) {
  .hub { columns: 1; }
  .hub-hero { padding: 18px; }
  .hub-hero-greet { font-size: 18px; }
}

/* ---------------------------------------------------------------------------
   Department identity
   navigate() sets --dept to the colour of the group the current screen belongs
   to, so a heading, an accent bar or a badge anywhere on the page can tint
   itself without knowing which screen it is on. It is declared here with a
   neutral default so nothing depends on the JS having run.
   --------------------------------------------------------------------------- */
:root { --dept: var(--brand); }

.page-badge {
  display: grid; place-items: center;
  width: 38px; height: 38px; flex: 0 0 38px;
  border-radius: 11px; color: #fff;
  background: var(--dept);
  box-shadow: var(--shadow-sm);
}
.page-badge .nav-icon { width: 20px; height: 20px; }
.page-badge:empty { display: none; }

/* Section headings pick up the department colour as a short leading bar. This
   is the change that makes a Finance screen read differently from a Library
   one without touching either screen's code. */
.content .toolbar h3 {
  display: flex; align-items: center; gap: 9px;
}
.content .toolbar h3::before {
  content: "";
  width: 4px; height: 17px; flex: 0 0 4px;
  border-radius: 999px;
  background: var(--dept);
}
/* The hub's own card headings already carry a coloured badge - a second accent
   bar next to it would be noise. */
.hub-card .hub-head h3::before { display: none; }

/* Surfaces: match the hub cards so a list screen and the dashboard read as one
   product rather than two. */
.card, .table-card, .form-card, .filter-bar { border-radius: var(--radius); }
.table-card { box-shadow: var(--shadow-sm); }
.table-card table { min-width: 640px; }

tbody tr { transition: background .12s ease; }
tbody tr:hover > td { background: color-mix(in srgb, var(--dept) 4%, transparent); }

/* Buttons: the primary action takes the department colour, so "Add student" on
   Academics and "Record payment" on Finance are visibly different actions. */
.button.primary {
  background: var(--dept);
  border-color: transparent;
  color: #fff;
}
.button.primary:hover { filter: brightness(1.07); }

.empty {
  display: grid; place-items: center; gap: 6px;
  padding: 36px 20px; text-align: center;
  color: var(--muted);
  background: var(--panel);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

/* ---------------------------------------------------------------------------
   Chrome: identity in the topbar, licence state on the dashboard
   --------------------------------------------------------------------------- */

.user-badge {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 5px 12px 5px 5px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
}
.user-badge .avatar {
  display: grid; place-items: center;
  width: 30px; height: 30px; flex: 0 0 30px;
  border-radius: 50%;
  background: var(--dept);
  color: #fff;
  font-size: 11.5px; font-weight: 700; letter-spacing: .02em;
}
.user-badge .who { display: flex; flex-direction: column; line-height: 1.2; }
.user-badge .who b { font-size: 13px; font-weight: 620; color: var(--ink); }
.user-badge .who small { font-size: 10.5px; color: var(--muted); }

.status-banner {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0; padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
}
.status-banner.ok   { background: var(--ok-bg);   color: var(--ok-ink); }
.status-banner.warn { background: var(--pend-bg); color: var(--pend-ink); }
.status-banner .link {
  background: none; border: 0; padding: 0;
  font: inherit; font-weight: 650; color: inherit;
  text-decoration: underline; cursor: pointer;
}

@media (max-width: 900px) {
  .user-badge .who { display: none; }
  .user-badge { padding: 4px; }
}

/* ===================================================================
   CARD LISTS
   Every row list renders as a grid of cards at every width. The markup
   stays a real <table> - enhanceTable only adds classes and data-label -
   so row selection, bulk actions, pagination, CSV export and the
   filter bar all keep working untouched.

   Tables that are not row lists (marks entry, the timetable grid,
   attendance sheets, anything printable) never get .card-list:
   isSpecialTable() excludes them before this class is added.
   =================================================================== */

.table-card:has(.card-list) {
  overflow: visible;
  background: none;
  border: 0;
  box-shadow: none;
}

.card-list, .card-list tbody, .card-list tr, .card-list td { display: block; width: auto; }
.card-list { min-width: 0; border-collapse: separate; }
.card-list thead { display: none; }

.card-list tbody {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
  gap: 12px;
}

.card-list tr {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 14px 16px 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  transition: border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.card-list tr:hover {
  border-color: var(--dept);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
.card-list tr:hover > td { background: none; }

/* Each remaining cell is one labelled line. */
.card-list td {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 4px 0;
  border: 0;
  text-align: right;
}
.card-list td::before {
  content: attr(data-label);
  flex: 0 0 auto; text-align: left;
  font-size: 11px; font-weight: 650; text-transform: uppercase;
  letter-spacing: .04em; color: var(--faint);
}
.card-list td.num { font-variant-numeric: tabular-nums; }

/* Headline: the name or reference the card is about. */
.card-list td.card-title {
  display: block; text-align: left;
  padding: 0 34px 6px 0;
  font-size: 15.5px; font-weight: 650; letter-spacing: -.01em; color: var(--ink);
}
.card-list td.card-title::before { content: none; }

/* Photo, when the list has one. */
.card-list td.card-media { display: block; text-align: left; padding: 0 0 8px; }
.card-list td.card-media::before { content: none; }
.card-list td.card-media img {
  width: 46px; height: 46px; border-radius: 50%; object-fit: cover;
  border: 2px solid var(--panel-2);
}

/* Selection sits in the corner rather than taking a labelled line. */
.card-list td.card-pick {
  position: absolute; top: 12px; right: 12px;
  padding: 0; display: block;
}
.card-list td.card-pick::before { content: none; }
.card-list tr:has(.card-pick input:checked) {
  border-color: var(--dept);
  box-shadow: 0 0 0 1px var(--dept), var(--shadow-sm);
}

/* Actions become the footer, full width and reachable by thumb. */
.card-list td.card-actions {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: auto; padding: 11px 0 0;
  border-top: 1px solid var(--line-soft);
  text-align: left;
}
.card-list td.card-actions::before { content: none; }
.card-list td.card-actions .btn-row { display: contents; }
.card-list td.card-actions .button { flex: 1 1 auto; justify-content: center; min-height: 34px; }
.card-list td.card-actions .more-menu { flex: 0 0 auto; }

/* In card view the "…" toggle sits in the footer, often wrapped to the left of
   the card. The table-view rule (open downward, anchored right) then throws the
   popover off the left edge and under the sidebar - the reported "it hides".
   Over a card, open UPWARD and anchor to the LEFT so the menu always lands on
   top of its own card body, fully on screen. */
.card-list .more-list {
  left: 0;
  right: auto;
  top: auto;
  bottom: calc(100% + 6px);
  z-index: 200;
}
/* Lift the card whose menu is open so the popover is never painted under a
   neighbouring card in the grid. */
.card-list tr:has(.more-menu.open) { z-index: 30; }

/* A cell with nothing in it would otherwise show a label and blank space. */
.card-list td.card-blank { display: none; }
.card-list td:empty { display: none; }

/* One column on a phone; the cards themselves need no other change. */
@media (max-width: 720px) {
  .card-list tbody { grid-template-columns: 1fr; gap: 10px; }
}

.list-head {
  display: flex; align-items: center; gap: 8px;
  padding: 0 2px 10px;
}
.list-head-pick {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--muted); cursor: pointer;
}
.list-head-pick input { width: auto; margin: 0; }

/* Printing wants the table back: a challan or a class list on paper should be
   rows and columns, not a column of cards. */
@media print {
  .card-list, .card-list tbody { display: table; width: 100%; }
  .card-list thead { display: table-header-group; }
  .card-list tr { display: table-row; border: 0; box-shadow: none; padding: 0; }
  .card-list td { display: table-cell; text-align: left; padding: 6px 8px; border-bottom: 1px solid #ccc; }
  .card-list td::before { content: none; }
  .card-list td.card-pick, .card-list td.card-actions, .list-head { display: none; }
}

/* Room for the corner tick, whichever cell happens to sit at the top. */
.card-list td.card-lead { padding-right: 30px; }
.card-list td.card-title.card-lead { padding-right: 34px; }

/* A destructive action was a small button in a table row. Full width on a card
   it becomes the loudest thing on screen, 24 times per page - so on a card it
   states itself in red rather than shouting in red, and the safe action keeps
   the filled treatment. */
.card-list td.card-actions .button.danger {
  background: var(--panel);
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 35%, var(--line));
  box-shadow: none;
}
.card-list td.card-actions .button.danger:hover {
  background: var(--bad-bg);
  border-color: var(--danger);
}

/* The avatar sits at the top of the card, and the headline tucks up beside it
   so a person's card leads with their face and name rather than a roll number. */
.card-list td.card-media .avatar { width: 44px; height: 44px; font-size: 15px; }
.card-list tr:has(.card-media) { padding-top: 12px; }

/* ---------------------------------------------------------------------------
   School crest
   The badge is a circle with transparent corners, so it needs no frame of its
   own and must not inherit one from the surfaces it sits on.
   --------------------------------------------------------------------------- */
/* The <picture> wrapper is the flex item and has no intrinsic width, so the
   image is sized explicitly and max-width is neutralised - a percentage would
   resolve against the wrapper's zero and collapse the crest. */
.brand picture { display: block; flex: 0 0 auto; line-height: 0; }
.brand-logo {
  display: block;
  width: 40px; height: 40px; max-width: none;
  border-radius: 50%;
  object-fit: contain;
  /* The artwork's own disc is near-white; on the navy sidebar that reads as a
     deliberate medallion rather than a cut-out, so it is left as it is. */
  background: #fff;
  flex: 0 0 auto;
}
.site-header .brand-logo { width: 38px; height: 38px; }
.footer-brand .brand-logo { width: 34px; height: 34px; }

.login-logo {
  display: block;
  width: 92px; height: 92px;
  margin: 0 0 14px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow);
}

/* The school name is longer than the placeholder was, so it needs to wrap
   inside the sidebar instead of pushing the crest out of view. */
.brand strong {
  display: block;
  font-size: 13px;
  line-height: 1.25;
  letter-spacing: .01em;
}

/* Yellow is an accent, never a text colour: 1.17:1 on white. Where it carries
   words, the dark olive companion goes on top of it. */
.accent-chip {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 650;
}

/* Back sits left of the page badge, in the same visual family as the theme and
   density controls rather than competing with the page title. */
.back-button { flex: 0 0 auto; }
.back-button[hidden] { display: none; }
.back-button svg { width: 19px; height: 19px; }

/* The messaging on/off switch. Its state has to be readable from across the
   room, because the question it answers is "are we texting parents right now". */
.sms-switch {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 14px;
  border-left: 4px solid var(--line);
}
.sms-switch p { margin: 3px 0 0; font-size: 13px; }
.sms-switch.is-live { border-left-color: var(--brand); background: var(--ok-bg); }
.sms-switch.is-live strong { color: var(--ok-ink); }
.sms-switch.is-held { border-left-color: var(--pend-ink); background: var(--pend-bg); }
.sms-switch.is-held strong { color: var(--pend-ink); }

/* The total block on a family voucher. A parent looks for one number; it has
   to be the largest thing on the page. */
.voucher-total { margin-top: 18px; border-top: 2px solid var(--ink); padding-top: 12px; }
.voucher-total > div { display: flex; justify-content: space-between; padding: 4px 0; }
.voucher-total .grand { font-size: 19px; border-top: 1px solid var(--line); margin-top: 6px; padding-top: 10px; }
.voucher-total .grand strong { color: var(--dept); }

/* A family voucher is wider than a receipt: it carries a table per child, and
   the modal default clipped the Amount and Paid columns clean off the right
   edge. Present but unreadable is the same as missing on a document a parent
   is asked to pay from. */
.document-card { width: min(920px, calc(100vw - 32px)); }
#printableFamilyVoucher table { min-width: 0; width: 100%; }
#printableFamilyVoucher td, #printableFamilyVoucher th { padding: 7px 10px; }
#printableFamilyVoucher td:nth-child(4), #printableFamilyVoucher td:nth-child(5),
#printableFamilyVoucher th:nth-child(4), #printableFamilyVoucher th:nth-child(5) {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* ===========================================================================
   PHONE AND TABLET LAYOUT
   ===========================================================================
   Kept at the end of the file on purpose. Everything here has to beat rules
   written earlier, and several of the resets below had already been attempted
   further up where they could never win.

   The bug this section exists to fix
   ----------------------------------
   `.table-card table { min-width: 640px }` sits near the end of this file. The
   mobile reset that tried to undo it - `.responsive-table { min-width: 0 }` -
   is BOTH earlier and less specific (0,1,0 against 0,1,1), so it never applied
   to a single table. Being inside a media query does not help: specificity is
   compared first, and a media query adds none.

   The consequence was not a wide table, it was a wide APP. `.content` is a
   grid, and a grid item may not shrink below its content unless it is told it
   may, so a 640px table made the whole workspace 640px. Every toolbar, filter
   bar and card then stretched to match and a 375px phone scrolled sideways by
   281px on nearly every screen in the system.
   =========================================================================== */
@media (max-width: 900px) {
  /* 1. Let the layout shrink. Without min-width:0 a grid or flex child keeps
        its content's minimum size, and overflow-x on an inner box can never
        take effect - the box grows instead of scrolling. */
  .app, .workspace, .content, .content > *, .table-card, .card, section, form { min-width: 0; }

  /* 2. Undo the 640px floor for real this time. Tables that genuinely need
        width to be readable opt back in individually below. */
  .table-card table, table.responsive-table, table.card-list { min-width: 0; }

  /* 3. Long words, ids and email addresses must wrap rather than push. */
  .content, .card, .table-card, .toolbar h3, .toolbar h4 { overflow-wrap: anywhere; }
}

/* ---------------------------------------------------------------------------
   The register on a phone
   ---------------------------------------------------------------------------
   The one screen in this system most likely to be used on a phone, standing
   up, in front of a class. It is an entry grid, so it is deliberately excluded
   from the card conversion that every other list gets - which left it as a
   four-column table scrolling sideways, with the P/A/L/Lv buttons half off the
   screen and a remarks box the width of a thumbnail.

   Each pupil becomes a card: name across the top, the four status buttons full
   width beneath it, remarks under that. The buttons keep a 44px touch target,
   because this is tapped forty times in a row by someone not looking closely.
   --------------------------------------------------------------------------- */
@media (max-width: 720px) {
  .att-table, .att-table tbody, .att-table tr, .att-table td { display: block; width: auto; }
  .att-table thead { display: none; }
  .att-table { border-collapse: separate; }

  .att-table tbody tr {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: 4px 10px;
    margin: 0 0 10px;
    padding: 12px 14px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
  }
  .att-table tbody tr:hover > td { background: none; }

  /* roll and name share the first line; the controls each take a full row */
  .att-table td { border: 0; padding: 0; }
  .att-table td:nth-child(1) { grid-column: 1; color: var(--faint); font-size: 12.5px; }
  .att-table td:nth-child(2) { grid-column: 2; font-weight: 650; }
  .att-table td:nth-child(2) .link-button { font-weight: 650; font-size: 15px; text-align: left; }
  .att-table td:nth-child(3),
  .att-table td:nth-child(4) { grid-column: 1 / -1; }
  .att-table td:nth-child(3) { margin-top: 8px; }

  /* Four equal, thumb-sized targets rather than four small ones. */
  .att-switch { display: flex; width: 100%; gap: 6px; }
  .att-opt {
    flex: 1 1 0;
    min-width: 0;
    min-height: 44px;
    font-size: 15px;
  }

  .att-table input[data-field="remarks"] {
    width: 100%; min-width: 0; margin-top: 8px; min-height: 40px;
  }

  /* The tally is the answer to "how many are in today" - keep it in view
     while the register is being marked rather than at the top of a long list. */
  .att-tally {
    position: sticky; bottom: 0; z-index: 2;
    background: var(--panel); border-top: 1px solid var(--line);
    justify-content: space-between;
  }
}

/* ---------------------------------------------------------------------------
   Touch: field sizes and tap targets
   ---------------------------------------------------------------------------
   Two things that only show up on a real phone.

   1. iOS Safari zooms the whole page in whenever a focused field has a
      font-size below 16px - and it does not zoom back out afterwards. Every
      input, select and search box here was 13.5-14px, so tapping any field in
      any form left the user zoomed in and scrolling sideways to escape. 16px
      is the threshold, not a preference.

   2. A 34px icon button is below every published touch guidance (44px Apple,
      48dp Android). These are the controls somebody uses one-handed while
      holding a register, so they get real targets.
   --------------------------------------------------------------------------- */
@media (max-width: 900px) {
  input, select, textarea,
  .f-field select, .f-field input,
  .filter-bar input, .filter-bar select,
  #globalSearch {
    font-size: 16px;
  }
  /* Date and number fields are the ones iOS is fussiest about. */
  input[type="date"], input[type="number"], input[type="tel"], input[type="email"] { font-size: 16px; }

  .icon-button { width: 40px; height: 40px; }
  .button { min-height: 40px; }
  .menu-toggle { width: 44px; height: 44px; }

  /* A row of actions on a card should wrap into full-width buttons rather than
     shrink into a row of slivers. */
  .card-list td.card-actions .button { min-height: 40px; }
}

/* ---------------------------------------------------------------------------
   Cards or rows
   ---------------------------------------------------------------------------
   Whether a list reads better as cards or as a table is a property of the
   reader, not of the data: some people scan a table far faster, and on a wide
   screen forty cards is a lot of scrolling. The icon shows the view you would
   switch TO, which is the convention the theme toggle already uses.
   --------------------------------------------------------------------------- */
.icon-button .icon-rows { display: none; }
:root[data-view="table"] .icon-button .icon-cards { display: none; }
:root[data-view="table"] .icon-button .icon-rows { display: block; }

/* A table the reader asked for keeps its columns and scrolls inside its own
   card, rather than being crushed into an unreadable width. This has to beat
   `.table-card table { min-width: 0 }` from the phone rules above, which is
   why it names the class. */
@media (max-width: 900px) {
  .table-card .plain-table { min-width: 560px; }
}

/* ---- Building the timetable ------------------------------------------- */
/* The bar that says how much of a week is spoken for. Over-allocation is a
   different colour rather than a fuller bar: it is not "very full", it is a
   week that cannot be built. */
.tt-bar {
  height: 8px;
  margin-top: 6px;
  background: var(--line, #e4e7ec);
  border-radius: 999px;
  overflow: hidden;
}
.tt-bar > span { display: block; height: 100%; border-radius: 999px; background: var(--brand); }
.tt-bar-under > span { background: #f79009; }
.tt-bar-full  > span { background: #12b76a; }
.tt-bar-over  > span { background: #d92d20; }

.tt-step { position: relative; }
.tt-step-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.tt-step-head h4 { margin: 0; }
.tt-step-num {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--brand); color: #fff;
  font-size: 13px; font-weight: 700;
}
.tt-step.is-done .tt-step-num { background: #12b76a; }
.tt-step-body > .table-card { margin-top: 4px; }

.plan-meter-line { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.plan-meter-line strong { font-size: 15px; }
