:root{
  --bg:#f4f6f9; --surface:#ffffff; --ink:#1b2733; --muted:#6b7a8d;
  --line:#e6ebf1; --brand:#0f3d3e; --brand-2:#12726e; --accent:#f5a623;
  --primary:#127a6b; --primary-d:#0e6759; --danger:#d64545; --danger-bg:#fdecec;
  --ok:#2f9e6f; --ok-bg:#e8f6ef; --warn:#c9860a; --warn-bg:#fdf3e2;
  --radius:12px; --radius-sm:8px; --shadow:0 1px 3px rgba(16,32,45,.08),0 1px 2px rgba(16,32,45,.06);
  --shadow-lg:0 10px 30px rgba(16,32,45,.15); --sidebar:250px;
  /* Deep black grid lines + text for printable reports (Mark List/Class
     List/Score Sheet/Exam Analysis) — deliberately darker/bolder than the
     app's normal --line/--muted, matching the ink weight of the reference
     printed sample rather than the softer on-screen UI palette. */
  --grid-ink:#000000; --grid-text:#000000;
}
*{box-sizing:border-box}
/* Round 4 §2 (accessibility BUG, site-wide): every native checkbox anywhere
   in the app — Class List's column-picker, Permissions toggles, Publish
   Results' subject list, staff admin/publish-access checkboxes, School
   Settings, etc. — previously rendered with whatever grey the browser's OS
   theme defaults to, which is exactly as hard to see as the custom
   .ex-class-check squares this brief screenshots (fixed separately, further
   down, since those are fully custom-drawn and don't take accent-color).
   accent-color is respected by every browser this app targets and re-colors
   the WHOLE control (box outline + check mark + focus ring), not just the
   checked fill, so a single rule here fixes visibility everywhere at once
   instead of needing to be hunted down and set per screen. Explicit
   width/height so every checkbox reads as comfortably tappable/visible by
   default too, not just accidentally-tiny ones some screens forgot to size —
   any screen using the visually-hidden-native-input-behind-a-custom-box
   pattern (e.g. .ex-class-check) keeps working exactly as before, since
   THOSE inputs set their own size via an inline style, which always wins
   over this general rule. */
input[type="checkbox"]{accent-color:var(--primary);width:16px;height:16px}
html,body{margin:0;padding:0}
body{font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  background:var(--bg);color:var(--ink);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased}
a{color:var(--primary);text-decoration:none}
h1,h2,h3,h4{margin:0;font-weight:650;color:var(--ink)}
.hidden{display:none!important}
.muted{color:var(--muted)}
.center{text-align:center}
.spacer{flex:1}

/* ---------- Buttons ---------- */
.btn{display:inline-flex;align-items:center;gap:7px;border:1px solid transparent;background:var(--primary);
  color:#fff;padding:9px 16px;border-radius:var(--radius-sm);font-weight:600;font-size:13.5px;cursor:pointer;
  transition:.15s;white-space:nowrap;font-family:inherit}
.btn:hover{background:var(--primary-d)}
.btn:disabled{opacity:.55;cursor:not-allowed}
.btn.secondary{background:#fff;color:var(--ink);border-color:var(--line)}
.btn.secondary:hover{background:#f6f8fa}
.btn.ghost{background:transparent;color:var(--muted)}
.btn.ghost:hover{background:#eef1f5;color:var(--ink)}
.btn.danger{background:var(--danger)}
.btn.danger:hover{filter:brightness(.93)}
.btn.sm{padding:6px 11px;font-size:12.5px}
.btn.block{width:100%;justify-content:center}
.icon-btn{border:none;background:none;cursor:pointer;color:var(--muted);padding:6px;border-radius:6px;font-size:15px;line-height:1}
.icon-btn:hover{background:#eef1f5;color:var(--ink)}
.icon-btn.danger:hover{background:var(--danger-bg);color:var(--danger)}
/* Design standard brief item 5: Collections' row actions (Print/Reverse/
   Transfer) move from three identical grey text buttons to colour-coded
   icon buttons, matching the wider push in this brief toward purposeful
   colour instead of one flat grey everywhere — same .icon-btn base every
   other per-row action in the app already uses (teachers.mjs, staff.mjs),
   just with a persistent tint instead of only-on-hover. */
.icon-btn.warn{color:var(--warn)}
.icon-btn.warn:hover{background:var(--warn-bg)}
.icon-btn.info{color:#2563eb}
.icon-btn.info:hover{background:#e7f0fe}
/* Stream row (Classes -> a class's streams): the whole row is still one
   click target (data-open-stream on the container), but "Manage Subjects &
   Teachers" is now a real, prominent button instead of a plain text link —
   the 3-column grid (info / button / delete) puts the button nearer the
   row's centre instead of crowded flush-right next to Delete. */
/* Round 2 §4 added a "Rename" button alongside Manage/Delete — a 4th
   explicit column (info / manage / rename / delete) instead of letting the
   new button fall into whatever the 3-column grid's implicit sizing did. */
.stream-row{display:grid;grid-template-columns:1fr auto auto auto;align-items:center;gap:14px;cursor:pointer}
.stream-info{min-width:0}
.stream-del{justify-self:end}
@media(max-width:640px){.stream-row{grid-template-columns:1fr;justify-items:start;gap:10px}.stream-del{justify-self:start}}
/* Round 2 §7 (approved design): exam class-selection card grid — see
   examDesk.mjs's openExamModal. Replaces a checkbox/name/input table row
   (where the name column had no room and wrapped mid-word, e.g. "Grade 1"
   truncating to "G"/"1") with one card per class, grouped by CBC level. */
.ex-class-toolbar{display:flex;align-items:center;gap:8px;margin:10px 0 14px}
.ex-class-scroll{max-height:360px;overflow:auto;padding-right:2px}
.ex-class-group{margin-bottom:18px}
.ex-class-group:last-child{margin-bottom:0}
.ex-class-level-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin-bottom:8px}
.ex-class-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:10px}
.ex-class-card{border:1.5px solid var(--line);border-radius:var(--radius-sm);padding:11px 13px;cursor:pointer;transition:.15s;background:var(--surface)}
.ex-class-card:hover{border-color:var(--primary)}
.ex-class-card.on{border-color:var(--primary);background:rgba(18,122,107,.06)}
.ex-class-card.locked{background:var(--bg);cursor:default}
.ex-class-top{display:flex;align-items:center;gap:9px;cursor:inherit}
/* Round 4 §2 (accessibility BUG): this used to border with var(--line), the
   app's very light greyish-blue chrome color (#e6ebf1) — fine for a subtle
   card divider, but nearly invisible as an EMPTY checkbox outline, which is
   exactly what someone with low vision needs to be able to see clearly
   before they've ticked it. var(--ink) (near-black) gives it real contrast
   against the white card background; the CHECKED state was already clearly
   visible (solid theme green fill) and is untouched. */
.ex-class-check{width:19px;height:19px;border-radius:6px;border:2px solid var(--ink);flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;font-size:11px;color:#fff;background:#fff}
.ex-class-card.on .ex-class-check{background:var(--primary);border-color:var(--primary)}
.ex-class-card.locked .ex-class-check{background:var(--muted);border-color:var(--muted)}
.ex-class-name{font-weight:650;font-size:13.5px;flex:1;min-width:0}
.ex-class-locked-note{font-size:10.5px;color:var(--muted);margin-top:6px}
.ex-class-min{display:none;align-items:center;gap:8px;margin-top:9px;padding-top:9px;border-top:1px dashed var(--line)}
.ex-class-card.on .ex-class-min,.ex-class-card.locked .ex-class-min{display:flex}
.ex-class-min label{font-size:11px;color:var(--muted);flex:1}
.ex-class-min input{width:60px;flex:0 0 auto;padding:5px 7px;font-size:12px;text-align:center}
@media(max-width:640px){.ex-class-toolbar{flex-wrap:wrap}.ex-class-grid{grid-template-columns:1fr}}
.back-link{cursor:pointer;color:var(--primary);font-size:12.5px;font-weight:650;display:inline-block;margin-bottom:6px}
.back-link:hover{text-decoration:underline}
.clickable-row{cursor:pointer}

/* ---------- Icon hub (Exams / Reports landing pages, Settings tabs) ----------
   Feature brief: "avoid so many submodules, just have them as icons" — a
   flat sidebar entry (Exams, Reports) lands here first, showing a grid of
   icon cards for its actual screens, instead of a nested sidebar dropdown.
   Deliberately built with Shule's own existing card look (not Zeraki's own
   colors/style — inspiration for the STRUCTURE only, per the brief: "see
   this from Zeraki for inspiration don't build like them"). */
.hub-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:16px}
/* Round 2 §6 (Exams module): vertical stack instead of a horizontal card
   grid — one column. Round 5 §4: reduced from the full content width to
   3/4 of it — a PERCENTAGE (not a hardcoded px cap) so it scales correctly
   at any viewport width instead of risking ending up under/over the
   intended proportion the way a fixed cap could. Reverts to full width
   below 960px (see the max-width:960px media query further down) so it
   doesn't get uncomfortably narrow on small screens. */
.hub-grid.stack{grid-template-columns:1fr;width:75%}
.hub-tile{background:#fff;border:1px solid var(--line);border-left:4px solid transparent;border-right:4px solid transparent;
  border-radius:var(--radius);padding:18px 15px;cursor:pointer;
  transition:.12s;display:flex;flex-direction:column;gap:6px;text-align:left}
/* Only the top/bottom hairline responds to hover — a tile carrying one of
   the .tile-* accent classes below (currently just Reports) keeps its own
   left/right border color always, hover included. */
.hub-tile:hover{border-top-color:var(--primary);border-bottom-color:var(--primary);box-shadow:var(--shadow)}
.hub-tile .h-ico{font-size:26px}
.hub-tile .h-title{font-weight:700;font-size:14.5px;color:var(--ink)}
.hub-tile .h-desc{color:var(--muted);font-size:12.5px}
/* UI colour refresh — Reports tile colour (brief item 7, round 2): a light
   accent border on both sides, one hue per tile, same mechanism and same
   hex values as the Dashboard's .stat-* accents above for visual
   consistency. Scoped to individual tiles via these classes rather than
   changing .hub-tile itself, so Exams/Finance/Settings' hub tiles (which
   reuse the same component) are unaffected unless asked for separately. */
.tile-blue{border-color:#2563eb}.tile-green{border-color:var(--ok)}
.tile-amber{border-color:var(--warn)}.tile-purple{border-color:#7c3aed}
.tile-teal{border-color:var(--primary)}.tile-rose{border-color:#db2777}
.tile-indigo{border-color:#4f46e5}
/* ---------- Teachers module (card grid, Phase 2g / brief §6) ---------- */
.teacher-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px}
/* Design standard brief item 2: each teacher's card now carries the same
   coloured accent border as the Reports/Exams hub tiles — reuses the exact
   same .tile-* hue classes, just applied to a .card instead of .hub-tile,
   so both need the same transparent left/right border to accept them.
   BUG FIX (round 2): this used to be the bare class `.teacher-card{...}`,
   specificity (0,1,0) — tied with plain `.card{border:1px solid var(--line)}`
   (also 0,1,0) and with `.tile-blue` etc (also 0,1,0). Since `.card` sits
   LATER in this file, on that tie it always won outright — border-left/
   right silently stayed the plain 1px grey `.card` border, and the .tile-*
   colour never showed AT ALL (confirmed with getComputedStyle — not just
   hard to see, genuinely inactive). Making this selector compound
   (`.card.teacher-card`, specificity 0,2,0) beats `.card` unconditionally
   regardless of file order. The matching `.teacher-card.tile-*` overrides
   below (also 0,2,0, placed after) then win the colour for the same
   reason a plain `.tile-blue` no longer could. */
.card.teacher-card{border-left:4px solid transparent;border-right:4px solid transparent}
.teacher-card.tile-blue{border-color:#2563eb}.teacher-card.tile-green{border-color:var(--ok)}
.teacher-card.tile-amber{border-color:var(--warn)}.teacher-card.tile-purple{border-color:#7c3aed}
.teacher-card.tile-teal{border-color:var(--primary)}.teacher-card.tile-rose{border-color:#db2777}
.teacher-card.tile-indigo{border-color:#4f46e5}
/* Generic reusable version of the same fix, for any OTHER single .card
   that just wants a plain left+right accent border (e.g. "Students per
   class" on the dashboard) without inventing a bespoke class each time —
   same compound-selector trick: `.card.side-accent` (0,2,0) beats plain
   `.card` (0,1,0), and `.side-accent.tile-*` (0,2,0, placed after) then
   wins the colour for the same reason. Usage: class="card side-accent
   tile-blue" (pick any .tile-* hue). */
.card.side-accent{border-left:4px solid transparent;border-right:4px solid transparent}
.side-accent.tile-blue{border-color:#2563eb}.side-accent.tile-green{border-color:var(--ok)}
.side-accent.tile-amber{border-color:var(--warn)}.side-accent.tile-purple{border-color:#7c3aed}
.side-accent.tile-teal{border-color:var(--primary)}.side-accent.tile-rose{border-color:#db2777}
.side-accent.tile-indigo{border-color:#4f46e5}
/* Round 2 brief item 7 (approved): matches the real dashboard .stat tile
   language — a tinted square icon badge (same shape/radius as .s-ico)
   instead of a plain gradient circle, paired to the same hue as the
   card's .tile-* border via the existing .t-* utility classes. */
.avatar-circle{width:48px;height:48px;border-radius:12px;display:grid;place-items:center;font-weight:750;font-size:15px;flex-shrink:0}
.teacher-card .btn.sm{padding:6px 8px;font-size:12px}
/* Name + status badge share one header row; email/phone collapse onto a
   single truncated line; the three actions became compact icon buttons
   instead of three stretched text buttons stacked underneath everything. */
.teacher-card-head{display:flex;align-items:flex-start;gap:12px}
.teacher-card-name{min-width:0;flex:1}
.teacher-card-name .t-name{font-weight:700;font-size:15px;line-height:1.25}
.teacher-card-name .t-contact{color:var(--muted);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
.teacher-card-head .badge{flex-shrink:0}
.teacher-card-actions{display:flex;gap:4px;justify-content:flex-end;margin-top:14px;border-top:1px solid var(--line);padding-top:10px}

/* ---------- Student profile (brief §5) ---------- */
.profile-meta{display:grid;gap:2px;font-size:13.5px}
.profile-meta>div{display:flex;justify-content:space-between;gap:14px;border-bottom:1px dotted var(--line);padding:7px 0}
.profile-meta>div:last-child{border-bottom:none}
.profile-meta>div span:first-child{color:var(--muted)}
.profile-meta>div span:last-child{font-weight:600;text-align:right}

/* ---------- Forms ---------- */
label{display:block;font-weight:600;font-size:12.5px;color:var(--ink);margin-bottom:6px}
input,select,textarea{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:var(--radius-sm);
  font-size:14px;font-family:inherit;background:#fff;color:var(--ink);transition:.15s}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px rgba(18,122,107,.12)}
.field{margin-bottom:15px}
/* Design standard rollout round 2 (approved): every search bar site-wide
   gets a blue accent border — same "just the border colour" treatment as
   Students per class/Classes/etc, no shape or shadow change. Targets every
   search input by its shared 🔍-prefixed placeholder (every search field in
   the app already starts with one) instead of needing a class added to
   each — students.mjs, teachers.mjs, financeHub.mjs, financeStudent.mjs,
   and any future one, all covered by this single rule. */
input[placeholder^="🔍"]{border-color:#2563eb}
/* Next Sprint 2 §4: show/hide password toggle — see passwordFieldHtml()/
   wirePasswordToggle() in app.js. The toggle button sits inside the input's
   own box (absolutely positioned over the right-padding it reserves) so it
   reads as part of the field, not a separate control next to it. */
.pw-wrap{position:relative}
.pw-wrap input{padding-right:40px}
.pw-toggle{position:absolute;right:2px;top:2px;bottom:2px;width:36px;border:none;background:transparent;
  cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;color:var(--muted)}
.pw-toggle:hover{color:var(--ink)}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.grid3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px}
/* Round 3 §12: Report Forms gained a Stream filter alongside its existing
   Exam/Class/Student pickers, making four fields in one row. */
.grid4{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:14px}
.hint{font-size:12px;color:var(--muted);margin-top:5px}

/* ---------- Layout ---------- */
#app{min-height:100vh}
.shell{display:flex;min-height:100vh}
.sidebar{width:var(--sidebar);background:var(--brand);color:#cfe3e2;position:fixed;top:0;bottom:0;left:0;
  display:flex;flex-direction:column;z-index:40;transition:transform .25s}
.brand{padding:20px 20px 16px;display:flex;align-items:center;gap:11px;border-bottom:1px solid rgba(255,255,255,.08)}
.brand .logo{width:38px;height:38px;border-radius:10px;background:linear-gradient(135deg,var(--accent),#e8890b);
  display:grid;place-items:center;color:#fff;font-weight:800;font-size:18px;flex-shrink:0}
.brand h1{color:#fff;font-size:17px;letter-spacing:.3px}
.brand small{color:#8fb5b3;font-size:11px;display:block}
.nav{flex:1;overflow-y:auto;padding:12px 10px}
.nav .group{color:#6f9a98;font-size:10.5px;text-transform:uppercase;letter-spacing:.8px;padding:14px 12px 6px;font-weight:700}
.nav a{display:flex;align-items:center;gap:11px;padding:9.5px 12px;border-radius:9px;color:#bcd6d4;
  font-weight:550;font-size:13.5px;margin-bottom:2px;cursor:pointer;transition:.12s}
.nav a .ico{width:18px;text-align:center;opacity:.9;font-size:15px}
.nav a:hover{background:rgba(255,255,255,.07);color:#fff}
.nav a.active{background:var(--primary);color:#fff;box-shadow:0 2px 8px rgba(0,0,0,.18)}
/* Expandable parent menus */
.navparent .parent-toggle{position:relative}
.navparent .caret{position:absolute;right:12px;transition:transform .18s;font-size:12px;opacity:.7}
.navparent.open .caret{transform:rotate(90deg)}
.navparent .subnav{max-height:0;overflow:hidden;transition:max-height .22s ease}
.navparent.open .subnav{max-height:320px}
.nav a.subitem{padding-left:41px;font-size:13px;color:#a8c8c6}
.nav a.subitem::before{content:'';width:5px;height:5px;border-radius:50%;background:currentColor;opacity:.45;margin-right:-4px}
.nav a.subitem.active{background:var(--primary);color:#fff}
.nav a.subitem.active::before{opacity:0}
.side-foot{padding:14px;border-top:1px solid rgba(255,255,255,.08);font-size:11.5px;color:#7ea6a4}

.main{flex:1;margin-left:var(--sidebar);min-width:0;display:flex;flex-direction:column}
.topbar{height:62px;background:var(--surface);border-bottom:1px solid var(--line);display:flex;align-items:center;
  gap:14px;padding:0 24px;position:sticky;top:0;z-index:30}
.topbar .menu-toggle{display:none}
.topbar .ctx{font-size:12.5px;color:var(--muted)}
.topbar .ctx b{color:var(--ink)}
.avatar{width:36px;height:36px;border-radius:50%;background:var(--brand-2);color:#fff;display:grid;place-items:center;font-weight:700;font-size:14px;cursor:pointer}
.usermenu{position:relative}
.dropdown{position:absolute;right:0;top:46px;background:#fff;border:1px solid var(--line);border-radius:10px;
  box-shadow:var(--shadow-lg);min-width:200px;padding:6px;z-index:60}
.dropdown .u-name{padding:10px 12px;border-bottom:1px solid var(--line);margin-bottom:6px}
.dropdown a{display:block;padding:9px 12px;border-radius:7px;color:var(--ink);cursor:pointer;font-size:13.5px}
.dropdown a:hover{background:#f2f5f8}
.content{padding:24px;max-width:1200px;width:100%}
/* Global safety net: no single element should ever be able to push the
   whole page into horizontal scroll (the root cause behind every "some
   pages scroll sideways" report). Everything that legitimately needs to
   scroll horizontally (e.g. .fin-tabs, .tabs, wide tables) opts in with
   its own overflow-x:auto below — this just stops stray overflow from
   width-locked children (long labels, fixed-width blocks) from widening
   <body> itself. */
html,body{max-width:100%;overflow-x:hidden}

/* ---------- Page header ---------- */
.page-head{display:flex;align-items:flex-start;gap:16px;margin-bottom:20px;flex-wrap:wrap}
.page-head h2{font-size:22px}
.page-head p{margin:3px 0 0;color:var(--muted);font-size:13.5px}

/* ---------- Cards ---------- */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
.card .card-h{padding:16px 20px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.card .card-h h3{font-size:15.5px}
.card .card-b{padding:20px}
.card.pad{padding:20px}

/* ---------- Stat tiles ---------- */
.stats{display:flex;flex-wrap:wrap;justify-content:center;gap:16px;margin-bottom:22px}
.stat{background:var(--surface);border:1px solid var(--line);border-left:4px solid transparent;border-right:4px solid transparent;
  border-radius:var(--radius);padding:18px 17px;box-shadow:var(--shadow);display:flex;align-items:center;gap:15px;flex:1 1 210px;max-width:240px}
.stat .s-ico{width:48px;height:48px;border-radius:12px;display:grid;place-items:center;font-size:22px;flex-shrink:0}
.stat .s-val{font-size:26px;font-weight:750;line-height:1}
.stat .s-lab{color:var(--muted);font-size:12.5px;margin-top:4px}
.stat.clickable{cursor:pointer;transition:box-shadow .15s,transform .15s}
.stat.clickable:hover{box-shadow:0 6px 16px rgba(0,0,0,.09);transform:translateY(-1px)}
.t-blue{background:#e7f0fe;color:#2563eb}.t-green{background:var(--ok-bg);color:var(--ok)}
.t-amber{background:var(--warn-bg);color:var(--warn)}.t-purple{background:#f0e9fd;color:#7c3aed}
.t-teal{background:#e2f5f2;color:var(--primary)}.t-rose{background:#fde8ee;color:#db2777}
.t-indigo{background:#eef2ff;color:#4f46e5}
/* UI colour refresh (brief item 4, round 2 — approved direction: a light
   accent border on both sides, tile otherwise unchanged) — a second,
   border-only class distinct from .t-* above so it can go on the OUTER
   .stat card without also flooding its background/text color the way
   .t-* does for the icon badge (.s-ico). Same hex values as .t-* so a
   tile's border always matches its own icon's color. */
.stat-blue{border-color:#2563eb}.stat-green{border-color:var(--ok)}
.stat-amber{border-color:var(--warn)}.stat-purple{border-color:#7c3aed}
.stat-teal{border-color:var(--primary)}.stat-rose{border-color:#db2777}
/* Finance dashboard tiles (approved mobile redesign): short caps labels,
   scoped to #fd-body only so the rest of the app's .s-lab tiles (which use
   longer, sentence-case labels) are untouched. */
#fd-body .s-lab{text-transform:uppercase;letter-spacing:.3px}
#fd-body .stat.no-ico{justify-content:center;text-align:center}
#fd-body .stat.no-ico>div{width:100%}

/* Dashboard "Last Exam Analyzed" — horizontal bars, sorted highest-first
   (brief item 5, approved option B). One hue (brand primary) since every
   bar is the same series (subject mean score) — no legend needed. */
.dash-eg-row{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.dash-eg-row:last-child{margin-bottom:0}
.dash-eg-lab{width:110px;flex-shrink:0;font-size:12.5px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dash-eg-track{flex:1;background:var(--line);border-radius:5px;height:16px;overflow:hidden}
.dash-eg-fill{height:100%;background:var(--primary);border-radius:5px;transition:width .2s}
.dash-eg-val{width:36px;flex-shrink:0;text-align:right;font-size:12.5px;font-weight:700;color:var(--ink)}
@media screen and (max-width:480px){.dash-eg-lab{width:78px;font-size:11.5px}}

/* ---------- Dashboard tile groups (Phase 2f: separate mobile/desktop layouts,
   toggled purely by the existing 960px breakpoint — no JS matchMedia needed) ---------- */
.stats-desktop{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;max-width:640px}
.stats-mobile{display:none}
.stats-desktop .stat,.stats-mobile .stat{max-width:none;flex:none;width:100%}
/* Desktop dashboard top row: the compact 2x2 tile grid sits beside the
   "Students by gender" card (instead of the tile grid alone floating
   narrow above the full-width cards below it) so the row fills the same
   width as everything below it — closing the large empty gutter a
   narrower .stats-desktop used to leave on wide screens. Both sides get an
   EQUAL share of the row (flex:1 each, tile grid's own max-width lifted
   inside this row) so the gender card ends up exactly as wide as the tile
   grid itself — i.e. the same width as the first 2 cards side by side —
   rather than stretching to fill whatever's left over and squeezing the
   tiles in the process. "Students per class" (which can have many more
   rows than the tile grid is tall) is its own full-width card underneath,
   not squeezed into this row. */
.dash-top-row{display:flex;gap:16px;align-items:stretch;margin-bottom:16px}
.dash-top-row .stats-desktop{margin-bottom:0;flex:1;max-width:none}
.dash-top-row .dash-gender-desktop{flex:1;min-width:280px;display:flex;flex-direction:column}
.dash-top-row .dash-gender-desktop .gender-panel{flex:1}
.gender-panel{display:flex;flex-direction:column;gap:16px;padding:24px 30px}
.gender-panel .g-row{display:flex;align-items:center;justify-content:space-between}
.gender-panel .g-side{text-align:center}
.gender-panel .g-num{font-size:38px;font-weight:800;line-height:1}
.gender-panel .g-lab{font-size:12.5px;color:var(--muted);margin-top:6px;font-weight:650;text-transform:uppercase;letter-spacing:.04em}
.gender-panel .g-div{width:1px;align-self:stretch;background:var(--line)}
/* Boys/Girls visual upgrade (design standard brief item 8, approved option
   A): a horizontal proportional split-bar in the space below the two
   counts — purely decorative, the counts themselves are unchanged. */
.gender-bar{height:10px;border-radius:6px;overflow:hidden;display:flex;background:var(--line)}
.gender-bar .gb-b{background:#2563eb}
.gender-bar .gb-g{background:#db2777}
.stat.gender-tile{flex-direction:column;align-items:stretch;gap:8px}
.stat.gender-tile .s-ico{align-self:center}
.stat.gender-tile .s-body{display:flex;align-items:center;justify-content:space-between;flex:1;gap:8px;min-width:0}
.stat.gender-tile .g-side{text-align:center}
.stat.gender-tile .g-num{font-size:21px;font-weight:800;line-height:1}
.stat.gender-tile .g-lab{font-size:10.5px;color:var(--muted);margin-top:3px}
.stat.gender-tile .gender-bar{height:6px;margin-top:2px}

/* ---------- Tables ---------- */
.table-wrap{overflow-x:auto}
table.data{width:100%;border-collapse:collapse;font-size:13.5px}
table.data th{text-align:left;padding:11px 14px;background:#f7f9fb;color:var(--muted);font-weight:650;
  font-size:11.5px;text-transform:uppercase;letter-spacing:.4px;border-bottom:1px solid var(--line);white-space:nowrap}
/* BUG FIX: a table sitting flush against its card's edge (no .card-b
   padding — e.g. Classes, Students' class list) showed a small square
   gap at the top two corners, because the header row's own grey
   background is a plain rectangle that doesn't follow the card's rounded
   corners the way the plain white body rows (no background of their own)
   already blend into invisibly at the bottom. Rounding just the header's
   own outer corners to match closes that gap. */
table.data thead tr:first-child th:first-child{border-top-left-radius:var(--radius)}
table.data thead tr:first-child th:last-child{border-top-right-radius:var(--radius)}
table.data td{padding:11px 14px;border-bottom:1px solid var(--line);vertical-align:middle}
table.data tr:last-child td{border-bottom:none}
/* .compact — SMS History (Messaging_Overhaul.docx item 7) feedback: rows
   read as too tall, more like a spread-out list than a table you can scan
   — thinner rows, closer to the density of Finance's own Balances table
   (.print-grid), so more of the batch fits on screen without scrolling. */
table.data.compact{font-size:12.5px}
table.data.compact th{padding:7px 10px;font-size:10.5px}
table.data.compact td{padding:7px 10px}
table.data tbody tr:hover{background:#fafbfc}
table.data td.num,table.data th.num{text-align:center}
.row-actions{display:flex;gap:6px;justify-content:flex-end;flex-wrap:wrap}

/* SMS Credits redesign (design review "Option B"): balance + request
   history as ONE plain card instead of two separately colored ones — see
   smsCredits.mjs's header comment. No accent color anywhere; a single
   plain rule is what separates the balance strip from the request list. */
.sms-wallet-hero{display:flex;align-items:center;justify-content:space-between;padding:22px 20px;gap:16px;flex-wrap:wrap}
.sms-wallet-div{border-top:1px solid var(--line)}
.sms-wallet-subh{padding:12px 20px 0;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:700}
.sms-wallet-rows{padding:6px 20px 14px}
.sms-wallet-row{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--line);font-size:13px}
.sms-wallet-row:last-child{border-bottom:none}
.sms-wallet-row .d{color:var(--muted);font-size:12px}

/* Mark List (feature brief "Merit List Design"): a real bordered grid —
   every cell boxed, not just a bottom rule per row — condensed enough to
   fit a class's full subject list on one printable page. */
/* table-layout:fixed makes column widths column-driven (set below) instead
   of content-driven — this is what lets a long subject name wrap instead of
   blowing the sheet past printable landscape width. Every column type below
   gets an explicit width so switching to fixed layout doesn't just squash
   the Name column or the summary columns instead. */
/* Next Sprint 2 §8: font size bumped (12.5px -> 13.5px, th 10.5px -> 11.5px)
   now that broadsheet.mjs prints this screen with halved page margins
   (5mm instead of the app-wide 10mm — see wirePrintOptions()'s marginMm
   param) — the freed-up horizontal room absorbs the larger text without
   the wide, many-subject-column grid overflowing the printed page. */
.mark-list-grid{border-collapse:collapse;font-size:13.5px;table-layout:fixed;width:100%;color:var(--grid-text)}
.mark-list-grid th,.mark-list-grid td{border:1.3px solid var(--grid-ink);padding:7px 8px}
/* Header-truncation fix (feature brief §6): "Total Marks" was printing as
   "Total Mk" and "Stream Position" as "Stream Po..." because every header
   cell was fixed-height, single-line and clipped with overflow:hidden. Every
   header now wraps onto as many lines as it needs — the header ROW grows
   downward to fit the full text, instead of the text being cut off — while
   data cells (mostly short numbers/grades) stay single-line and compact.
   Bug fix: this table used to also carry the generic ".data" class, whose
   "table.data th{white-space:nowrap}" rule has higher CSS specificity than
   ".mark-list-grid th{white-space:normal}" — so the wrap NEVER actually
   applied, and a long single-line header like "Integrated Science and
   Technology" silently overflowed its fixed-width column and visually
   bled into the next header cell instead of wrapping. Dropping the ".data"
   class (this table is fully self-styled by .mark-list-grid already) fixes
   the specificity fight; overflow-wrap:break-word then wraps normally at
   word boundaries and only breaks inside a single long word if that word
   alone doesn't fit the column. */
.mark-list-grid th{font-size:11.5px;font-weight:800;color:var(--grid-text);background:#f7f9fb;text-transform:uppercase;letter-spacing:.3px;text-align:left;white-space:normal;word-break:normal;overflow-wrap:break-word;vertical-align:bottom;overflow:visible}
.mark-list-grid td{white-space:nowrap;overflow:hidden}
.mark-list-grid td.num,.mark-list-grid th.num{text-align:center}
.mark-list-grid th.id-col,.mark-list-grid td.id-col{width:64px}
.mark-list-grid th.name-col,.mark-list-grid td.name-col{width:165px;white-space:normal;word-break:normal}
.mark-list-grid th.str-col,.mark-list-grid td.str-col{width:56px}
/* Subject header cells: fixed width regardless of the subject's name length
   — a long name wraps onto multiple lines (row height grows) instead of
   stretching the column horizontally. Widened slightly (70px -> 78px)
   alongside the Next Sprint 2 §8 font bump above so headers like "ENGLISH"/
   "KISWAHILI" still fit on one line instead of wrapping awkwardly. */
.mark-list-grid th.subj-col{width:78px}
.mark-list-grid th.sum-col,.mark-list-grid td.sum-col{width:52px}
/* Sprint Review redo: the earned/possible ("870/1200") format moved from
   the per-student TT MKS column to the class-level TOTAL row's subject
   columns (subj-col, already 70px — wider than sum-col — which is enough
   room for that format; verified visually, see EX7/EX8 CSS verification
   notes below). */
.mark-list-grid tr:last-child td{border-bottom:1.3px solid var(--grid-ink)}
.mark-cell .mark-grade{color:var(--grid-text);font-weight:600;font-size:10.5px}
/* Round 5 §2: the TOTAL/AVERAGE rows at the very bottom of the Merit List —
   shaded like the header row so they read as a summary, not just two more
   student rows. */
.mark-list-grid .bs-agg-row td{background:#f7f9fb;font-weight:700}
/* System Fixes brief §15: Mark List summary stat tables (Class Grade
   Summary / Gender Grade Summary / Grade Breakdown by Subject), replacing
   the old single "Class average" line.
   Sprint Review EX5 (redesign, approved): the Round 6 §4 bordered-tile 2x2
   grid was rejected — reverted to the original unboxed heading + table
   look. Class Grade Summary and Gender Grade Summary sit side by side via
   the app's existing `.grid2` class; Grade Breakdown by Subject is its own
   full-width block below them (see broadsheet.mjs's summaryTablesHtml),
   not wrapped into the same 2-across grid. Every table still uses the same
   black grid-ink border every other printed report on this app uses
   (main.css's .print-grid/.mark-list-grid). */
.bs-summary{padding:16px 18px}

/* Shared printable-report header (feature brief §2/§4; Round 5 §2/§3): logo
   far left, school name centered, address block far right — same layout for
   the Class List, Mark List, Score Sheet, Exam Analysis and Report Form.
   Round 5 dropped the divider line that used to run under this row (it now
   reads as an unnecessary straight line once the green title bar sits right
   underneath) and moved the report's own title out into that title bar
   (.ph-title-bar) instead of the small plain-text subtitle this used to
   carry — see src/lib/printHeader.mjs's printHeaderHtml()/reportTitleBarHtml(). */
.print-header{display:flex;align-items:center;gap:16px;padding-bottom:4px;margin-bottom:0}
.print-header .ph-logo{flex:0 0 auto}
.print-header .ph-logo-img{width:56px;height:56px}
.print-header .ph-center{flex:1;min-width:0;text-align:center}
.print-header .ph-school{font-size:19px;font-weight:800;color:var(--grid-text);line-height:1.25}
.print-header .ph-address{flex:0 0 auto;text-align:right;font-size:11px;color:var(--grid-text);line-height:1.55;white-space:nowrap}
/* The "green rectangle" title bar — was Report-Form-only (.report
   .r-title-bar); now the one shared component every printable report uses
   for its own descriptive title (exam name, class, etc). */
.ph-title-bar{background:var(--brand);color:#fff;text-align:center;font-weight:750;letter-spacing:.2px;padding:9px 14px;font-size:13.5px;margin-top:10px}
/* Class List / Score Sheet full-grid print table (feature brief §2: "print
   as a table with clear rows, columns, and borders — the same standard
   already used on the mark list"), plus a shaded title band ("GRADE 8 -
   CLASS LIST") matching the reference sample. */
.print-grid{border-collapse:collapse;width:100%;font-size:12.5px;color:var(--grid-text)}
.print-grid th,.print-grid td{border:1.3px solid var(--grid-ink);padding:8px 10px}
.print-grid th{background:#f7f9fb;font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.4px;color:var(--grid-text);text-align:left;white-space:normal;word-break:break-all}
.print-grid td.num,.print-grid th.num{text-align:center}
.grid-title-band{text-align:center;font-weight:800;font-size:12px;background:#f0f2f5;color:var(--grid-text);border:1.3px solid var(--grid-ink);border-bottom:none;padding:8px;letter-spacing:.3px}
/* Design standard brief item 6: group Excel + Print + paper size +
   orientation into one visually distinct cluster (a light pill), instead of
   Excel sitting loose in the toolbar and Print's own controls floating
   separately right after it — same idea applied everywhere .fin-toolbar
   ends with an export/print action pair. */
.fin-report-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;background:var(--bg);
  border:1px solid var(--line);border-radius:10px;padding:6px 8px}
.print-opts{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.print-opts select{padding:8px 10px;border:1px solid var(--line);border-radius:8px;font-size:13px;background:#fff}
/* Toolbar for Download/Print/paper-size controls, sat ABOVE the printable
   report card (not mixed into the print header itself) so the card below
   it is a true preview of exactly what prints. */
.report-toolbar{display:flex;justify-content:flex-end;align-items:center;gap:10px;margin-bottom:12px;flex-wrap:wrap}

/* ---------- Badges ---------- */
.badge{display:inline-flex;align-items:center;padding:3px 10px;border-radius:20px;font-size:11.5px;font-weight:650}
.badge.green{background:var(--ok-bg);color:var(--ok)}.badge.grey{background:#eef1f5;color:var(--muted)}
.badge.amber{background:var(--warn-bg);color:var(--warn)}.badge.blue{background:#e7f0fe;color:#2563eb}
.badge.red{background:var(--danger-bg);color:var(--danger)}
/* Round 6 §2 (BUG): Achievement/Performance Level badges (EE1, EE2, ME1...)
   on the Mark List, Report Form, Exam Analysis and Transcript were all using
   .badge.blue — the same "informational" blue used for genuinely unrelated
   status pills elsewhere. A grade isn't informational metadata, it's the
   result itself, so it gets its own neutral variant: plain black text on
   the same light-grey pill every other neutral badge uses (--grid-text is
   the pure-black token these reports already use throughout). */
.badge.grade{background:#eef1f5;color:var(--grid-text)}

/* ---------- Status buttons (attendance marking) ---------- */
.status-btn{border:1px solid var(--line);background:#fff;color:var(--muted);padding:6px 12px;border-radius:7px;
  font-size:12px;font-weight:650;cursor:pointer;font-family:inherit;white-space:nowrap}
.status-btn:hover{background:#fafbfc}
.status-btn.on.green{background:var(--ok-bg);color:var(--ok);border-color:transparent}
.status-btn.on.red{background:var(--danger-bg);color:var(--danger);border-color:transparent}
.status-btn.on.amber{background:var(--warn-bg);color:var(--warn);border-color:transparent}
.status-btn.on.blue{background:#e7f0fe;color:#2563eb;border-color:transparent}

/* ---------- Empty states ---------- */
.empty{text-align:center;padding:46px 24px;color:var(--muted)}
.empty .e-ico{font-size:44px;margin-bottom:14px;opacity:.8}
.empty h3{font-size:16.5px;margin-bottom:6px;color:var(--ink)}
.empty p{margin:0 auto 18px;max-width:420px;font-size:13.5px}
.empty.warn .e-ico{color:var(--warn)}

/* "Still under development" module banner (Timetable — on hold, brief
   asked for a designed notice rather than a plain disclaimer line). Soft
   warn-tinted card, not a harsh full-width alert bar, so it reads as an
   FYI rather than an error. */
.dev-notice{display:flex;align-items:flex-start;gap:12px;background:var(--warn-bg);border:1px solid rgba(0,0,0,.04);
  border-radius:var(--radius);padding:13px 16px;margin-bottom:16px;font-size:13px;color:var(--ink);line-height:1.5}
.dev-notice-ico{font-size:18px;flex-shrink:0;line-height:1.4}
.dev-notice strong{color:var(--ink)}

/* ---------- Toolbar / filters ---------- */
.toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:16px}
.toolbar .grow{flex:1;min-width:180px}
.toolbar select,.toolbar input{max-width:230px}

/* Students home: one big, roughly-centered search box (brief §5) instead of
   a search field squeezed into a regular toolbar row. */
.search-hero{max-width:640px;margin:0 auto 20px}
.search-hero input{padding:16px 20px;font-size:16px;border-radius:12px;box-shadow:var(--shadow-sm,0 1px 3px rgba(16,32,45,.08))}

/* Next Sprint 2 §14: Finance's "Search Student" box, now pinned to the top
   right of the module header (same line as the "Finance" title) instead of
   being its own tab — large and prominent per the request, same visual
   weight as .search-hero's own input, just sized to sit inline in a header
   instead of centered as its own block. */
.fin-search-prominent{width:100%;min-width:280px;padding:14px 18px;font-size:15.5px;border-radius:12px;
  box-shadow:var(--shadow-sm,0 1px 3px rgba(16,32,45,.08))}
@media(max-width:360px){.fin-search-prominent{min-width:0;padding:12px 14px;font-size:14.5px}}

/* ---------- Modal ---------- */
.modal-back{position:fixed;inset:0;background:rgba(16,32,45,.45);display:grid;place-items:center;z-index:100;padding:20px}
.modal{background:#fff;border-radius:14px;box-shadow:var(--shadow-lg);width:100%;max-width:520px;max-height:90vh;
  overflow:auto;animation:pop .18s ease}
.modal.wide{max-width:820px}
@keyframes pop{from{transform:translateY(10px) scale(.98);opacity:0}to{transform:none;opacity:1}}
.modal-h{padding:18px 22px;border-bottom:1px solid var(--line);display:flex;align-items:center}
.modal-h h3{font-size:16.5px}
.modal-b{padding:22px}
.modal-f{padding:16px 22px;border-top:1px solid var(--line);display:flex;justify-content:flex-end;gap:10px}

/* ---------- Toast ---------- */
#toasts{position:fixed;top:18px;right:18px;z-index:200;display:flex;flex-direction:column;gap:10px}
.toast{background:#fff;border-left:4px solid var(--primary);border-radius:10px;box-shadow:var(--shadow-lg);
  padding:13px 18px;min-width:260px;max-width:360px;font-size:13.5px;font-weight:550;animation:slidein .2s ease}
.toast.ok{border-color:var(--ok)}.toast.err{border-color:var(--danger)}.toast.warn{border-color:var(--warn)}
@keyframes slidein{from{transform:translateX(30px);opacity:0}to{transform:none;opacity:1}}
/* Optimistic school creation (brief C1) — a dismissible "still setting up"
   notice shown right after the admin lands on their new dashboard, instead
   of a blocking progress bar. */
.toast.setup-toast{display:flex;align-items:center;gap:12px;border-color:var(--accent, var(--warn))}
.toast.setup-toast .toast-close{background:none;border:none;cursor:pointer;font-size:17px;line-height:1;
  color:var(--muted);padding:0 2px;font-family:inherit}

/* ---------- Login ---------- */
/* Landing redesign, final pass: the two full-height columns fill edge-to-
   edge exactly as before (teal gradient / light page background — colors
   unchanged, positions unchanged). What's now wrapped in its own rounded,
   shadowed TILE is just the CONTENT on each side (the logo/heading/feature
   grid on the left, the "Welcome back"+form on the right) — a card sized to
   its content, floating with visible margin inside its column, not a tile
   stretched to fill the whole column. .auth is the full-height grid;
   .auth-card is a non-rendering wrapper (display:contents) so .promo and
   .formside sit directly as the two grid columns. */
.auth{min-height:100vh;display:grid;grid-template-columns:.82fr 1.18fr}
.auth-card{display:contents}
.auth .promo{position:relative;background:linear-gradient(150deg,var(--brand),var(--brand-2));color:#fff;
  padding:48px 64px;display:flex;flex-direction:column;justify-content:center;overflow:hidden}
.auth .promo::after{content:'';position:absolute;right:-80px;top:-80px;width:260px;height:260px;border-radius:50%;
  background:rgba(255,255,255,.05)}
.auth .promo::before{content:'';position:absolute;left:-60px;bottom:-60px;width:200px;height:200px;border-radius:50%;
  background:rgba(255,255,255,.04)}
.auth .promo-inner{position:relative;z-index:1;max-width:400px;background:rgba(255,255,255,.15);
  border:1px solid rgba(255,255,255,.25);border-radius:20px;box-shadow:0 20px 50px rgba(0,0,0,.2);
  padding:30px 28px;backdrop-filter:blur(4px)}
.auth .promo .logo{width:54px;height:54px;border-radius:14px;background:linear-gradient(135deg,var(--accent),#e8890b);
  display:grid;place-items:center;font-size:27px;font-weight:800;margin-bottom:22px;box-shadow:0 8px 20px rgba(0,0,0,.2)}
.auth .promo h1{color:#fff;font-size:28px;line-height:1.15;margin-bottom:12px}
.auth .promo p{color:#c8e0de;font-size:14px;max-width:380px;margin:0 0 26px}
.feat-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.feat-tile{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);border-radius:12px;
  padding:12px;display:flex;gap:10px;align-items:center;backdrop-filter:blur(4px)}
.feat-tile .ft-ico{font-size:20px;flex-shrink:0}
.feat-tile .ft-title{font-weight:650;font-size:13.5px;color:#fff}
.feat-tile .ft-sub{font-size:11px;color:#a9cfcd;margin-top:1px}
.auth .formside{display:flex;align-items:center;justify-content:center;padding:44px 40px;
  background:var(--bg)}
.auth .formcard{width:100%;max-width:520px;background:var(--surface);border:1px solid var(--line);
  border-radius:20px;box-shadow:var(--shadow-lg);padding:30px 52px}
.auth .formcard h2{font-size:24px;margin-bottom:6px}
.auth .formcard .sub{color:var(--muted);margin-bottom:18px;font-size:14px}
.auth .formcard .hint{margin-top:12px}
/* Landing redesign brief A1: "'Welcome back' text must be centered within
   the tile/banner". */
.auth .formcard h2.auth-center,.auth .formcard .sub.auth-center{text-align:center}

/* Smart-login account picker (brief B1) — a phone number that matches more
   than one account across schools/roles. */
.acct-list{display:flex;flex-direction:column;gap:10px;margin-bottom:20px}
.acct-pick{display:flex;align-items:center;gap:12px;border:1px solid var(--line);border-radius:12px;
  padding:12px 14px;cursor:pointer;transition:.12s}
.acct-pick:hover{border-color:var(--primary)}
.acct-pick input{accent-color:var(--primary);width:16px;height:16px;flex-shrink:0}
.acct-school{font-weight:650;font-size:14px}
.acct-role{color:var(--muted);font-size:12px;text-transform:capitalize;margin-top:1px}
.tabs{display:flex;gap:6px;background:#eef1f5;padding:5px;border-radius:10px;margin-bottom:22px}
.tabs button{flex:1;padding:9px;border:none;background:none;border-radius:7px;font-weight:600;cursor:pointer;
  color:var(--muted);font-family:inherit;font-size:13px}
/* Mobile: with several long labels ("Academic Years & Terms", "Permissions")
   flex:1 can't shrink buttons below their own min-content width, so the row
   as a whole exceeds the viewport and — since nothing upstream clips it —
   drags the entire page into horizontal scroll. Below 960px, stop trying to
   evenly divide the row and instead let it scroll horizontally as its own
   contained pill strip; each button keeps its natural width and wraps its
   own label onto at most 2 lines instead of stretching/squashing. */
@media screen and (max-width:960px){
  .tabs{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin}
  .tabs button{flex:0 0 auto;min-width:104px;white-space:normal;line-height:1.2}
  /* BUG FIX: the scroll-strip treatment above is for bars with several long
     labels (Settings' 4 tabs) — it was wrongly also catching 2-item bars
     (Teachers/Staff, Students Active/Left), which just sat left-aligned with
     dead space filling the rest of the row. `:first-child:nth-last-child(2)`
     picks out "exactly 2 children" in pure CSS — a 2-item bar always splits
     evenly, full width, exactly like it already does on desktop. */
  .tabs > button:first-child:nth-last-child(2),
  .tabs > button:first-child:nth-last-child(2) ~ button{flex:1;min-width:0;white-space:nowrap}
  .tabs:has(> button:first-child:nth-last-child(2)){overflow-x:visible}
}
@media screen and (max-width:400px){.tabs button{min-width:88px;font-size:12px;padding:8px 6px}}
.tabs button.active{background:#fff;color:var(--ink);box-shadow:var(--shadow)}
.auth-err{background:var(--danger-bg);color:var(--danger);padding:10px 13px;border-radius:8px;font-size:13px;
  margin-bottom:16px;font-weight:550}
/* SignUp_Fixes §1: a real, specific inline error right under the field it's
   about (e.g. "Enter a correct phone number") — separate from .auth-err's
   form-level banner, and from a field's own .hint text (which stays visible
   at the same time explaining the expected format). Empty by default so it
   takes up no space until there's actually something wrong to say. */
.field-err{color:var(--danger);font-size:12.5px;font-weight:600;margin-top:4px}
.field-err:empty{display:none}

/* Phone OTP verify screen (renderOtpVerify, app.js) — the Resend link
   during its 30s cooldown, matching send-otp.js's own server-side cooldown. */
.disabled-link{pointer-events:none;color:var(--muted);cursor:default;text-decoration:none}

/* ---------- Checklist ---------- */
.checklist{list-style:none;padding:0;margin:0}
.checklist li{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--line);cursor:pointer}
.checklist li:last-child{border-bottom:none}
.checklist .ck{width:24px;height:24px;border-radius:50%;display:grid;place-items:center;font-size:13px;flex-shrink:0;font-weight:700}
.checklist .ck.done{background:var(--ok-bg);color:var(--ok)}
.checklist .ck.todo{background:#eef1f5;color:var(--muted);border:2px dashed #cbd5df}
.checklist li .lab{flex:1;font-weight:550}
.checklist li.done .lab{color:var(--muted);text-decoration:line-through}

/* ---------- Chips (multi-select subjects) ---------- */
.chips{display:flex;flex-wrap:wrap;gap:9px}
.chip{display:inline-flex;align-items:center;gap:7px;padding:8px 13px;border:1px solid var(--line);border-radius:20px;
  cursor:pointer;font-size:13px;font-weight:550;user-select:none;transition:.12s}
.chip:hover{border-color:var(--primary)}
.chip.on{background:var(--primary);border-color:var(--primary);color:#fff}

/* ---------- Report card / broadsheet ---------- */
/* Redesigned Zeraki-style report form (feature brief "Report Forms and Merit
   List Design" — condensed, gridded, everything fits one printable page). */
.report{background:#fff;border:1px solid var(--line);border-radius:12px;padding:0;max-width:800px;margin:0 auto;overflow:hidden}
/* System Fixes brief §9: report form header uses the same shared
   printHeaderHtml() component as Mark List/Class List (logo far left,
   school name centered, address block on the right) instead of its own
   bespoke left-aligned layout — .print-header (main.css) supplies the
   flex/padding, this just adds the report card's own outer inset. Round 5
   §2/§3: the divider-drop and green title bar (.ph-title-bar) that used to
   be bespoke to the Report Form are now the shared default for every
   printable report's header — see main.css's .print-header/.ph-title-bar
   and src/lib/printHeader.mjs. .ph-title-bar sits OUTSIDE .r-head (a
   sibling, not nested) so it stays flush edge-to-edge with the card like
   before, instead of picking up r-head's own left/right inset. */
.report .r-head{padding:16px 22px 0}
.report .r-student-row{display:flex;gap:20px;align-items:stretch;padding:16px 22px;border-bottom:1px solid var(--grid-ink)}
/* Follow-up polish: the row got noticeably taller once the chart grew, and
   the (untouched, per instruction) avatar was left stranded at the very
   top of that extra height. align-self:center only repositions the avatar
   itself within the row — r-meta/r-chart keep their own existing alignment
   (still align-items:stretch on the row itself), so nothing else shifts. */
.report .r-avatar{width:78px;height:78px;border-radius:10px;background:linear-gradient(160deg,var(--brand),var(--brand-2));color:#fff;display:grid;place-items:center;font-weight:750;font-size:24px;flex:none;align-self:center}
/* Round 3 follow-up (visual parity pass): r-meta used to be flex:1 (grow)
   while r-chart was pinned to a fixed 220px box — backwards from the Zeraki
   reference, where the compact name/admno/grade block stays a fixed width
   on the left and the chart stretches to fill the rest of the row. That
   mismatch is exactly why the chart used to read as a small, isolated box
   with dead space around it instead of a wide, integrated graph. Swapped:
   r-meta is now a fixed-ish column, r-chart is the one that grows. */
.report .r-meta{display:flex;flex-direction:column;justify-content:center;gap:4px;font-size:12.5px;flex:0 0 auto;width:230px;color:var(--grid-text)}
.report .r-meta div{display:flex;justify-content:space-between;gap:10px;border-bottom:1px dotted var(--grid-ink);padding:3px 0}
/* Reference-layout pass: the chart needed "significantly more visual room",
   not just full width — taller too. r-chart no longer stretches to match
   the (fixed-size, untouched-per-instruction) avatar's height; it sets its
   own comfortable height via the SVG's viewBox aspect ratio instead, and the
   title/legend now share one row (title left, legend right) the way the
   reference does, instead of a centered legend line eating vertical space
   below the plot. */
.report .r-chart{flex:1;min-width:0;display:flex;flex-direction:column}
.report .r-chart .r-chart-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:2px}
.report .r-chart .r-chart-title{font-size:11px;font-weight:700;color:var(--grid-text)}
.report .r-chart svg{width:100%;height:auto;flex:1}
.report .r-chart-legend{display:flex;gap:12px;font-size:9.5px;color:var(--grid-text)}
.report .r-chart-legend span{display:inline-flex;align-items:center;gap:5px}
.report .r-chart-legend i{width:12px;height:12px;border-radius:3px;background:var(--brand);display:inline-block}
.report .r-chart-legend i.avg{background:#cfd6de}
/* §2: dropped the 5th "Position" tile (large circular badge) — Position is
   still shown, just up in the r-meta panel next to Name/Class/Arm, so no
   information is lost, only the redundant/oversized badge. Four equal
   columns now: Performance Level / Total Marks / Total Points / Mean
   Points, matching the reference exactly. */
/* Follow-up polish: these read as one flat bordered strip with dividers —
   changed to actual tiles (own grey background, rounded corners, gap
   between them) instead of cells "just hanging there in a rectangle". */
.report .r-summary{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;padding:14px 22px}
.report .r-summary .box{padding:12px 8px;text-align:center;background:#f7f9fb;border-radius:9px}
.report .r-summary .box .v{font-size:18px;font-weight:750;color:var(--brand)}
.report .r-summary .box .l{font-size:10px;color:var(--grid-text);margin-top:2px}
.report .r-body{padding:14px 22px;color:var(--grid-text)}
.report .report-grid{width:100%;border-collapse:collapse;font-size:12px;color:var(--grid-text)}
/* Fixed layout (so the <colgroup> above actually controls column widths)
   only on the main learning-areas grid — NOT on .r-descriptors-grid, which
   shares the .report-grid class but has its own 9-column shape (label +
   however many grade bands) and needs the browser's normal auto-sizing,
   not the subjects grid's proportions forced onto it. */
.report .report-grid:not(.r-descriptors-grid){table-layout:fixed}
.report .report-grid th,.report .report-grid td{border:1.3px solid var(--grid-ink);padding:7px 8px;overflow-wrap:break-word}
/* Brief §11: match the header treatment Mark List/Class List already use
   (.mark-list-grid th, main.css:243) — uppercase, letter-spaced, bold —
   instead of a slightly different plain-case style just on this report. */
.report .report-grid th{font-size:10.5px;font-weight:800;background:#f7f9fb;text-transform:uppercase;letter-spacing:.3px}
/* §4: subject-cluster summary (STEM / Social Sciences / Arts and Sport
   Science) — same table look as the main grid, just centered cells and no
   fixed layout (only 3 columns, no need to constrain widths). */
.report .r-clusters{margin-top:10px}
.report .r-cluster-grid{width:100%;border-collapse:collapse;font-size:12px}
.report .r-cluster-grid th,.report .r-cluster-grid td{border:1.3px solid var(--grid-ink);padding:8px;text-align:center}
.report .r-cluster-grid th{font-size:10.5px;font-weight:800;background:#f7f9fb;text-transform:uppercase;letter-spacing:.3px;color:var(--grid-text)}
.report .r-cluster-grid td{font-weight:700;color:var(--brand)}
/* Class Teacher's / Principal's remarks + Grade Descriptors, at the bottom
   of the report form (feature brief: "remarks are a must" — always
   auto-generated from the learner's overall performance band, not a blank
   free-text box; a signature line is printed instead). */
.report .r-remarks{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px}
.report .r-remark-box{border:1px solid var(--grid-ink);border-radius:8px;padding:10px 14px;background:#f7f9fb}
.report .r-remark-h{font-weight:700;font-size:12px;margin-bottom:6px}
.report .r-remark-box p{margin:0;font-size:12.5px;line-height:1.45;color:var(--grid-text)}
.report .r-sig{margin-top:10px;font-size:11.5px;color:var(--grid-text);display:flex;align-items:center;gap:6px}
.report .r-sig-line{flex:1;border-bottom:1px dashed var(--grid-ink);height:1px}
.report .r-descriptors{margin-top:12px}
.report .r-descriptors-h{font-weight:700;font-size:13px;margin-bottom:6px}
/* System Fixes brief §10: transposed Grade Descriptors table (bands as
   columns, not rows) — tighter cell padding and a smaller remark line under
   each grade label, since a scale with many bands (e.g. CBC's 8) now needs
   many narrow columns instead of a handful of wide rows. */
.report .r-descriptors-grid th,.report .r-descriptors-grid td{padding:5px 6px}
.report .r-descriptor-remark{font-weight:400;font-size:8.5px;color:var(--grid-text);margin-top:2px;white-space:normal}
/* System Fixes brief §16, reference-layout pass (§5): "Term Dates" used to
   be one easy-to-miss inline text line — now its own clearly labeled
   section (heading + a small 2-column table), matching the reference.
   Still only rendered when at least one date is actually set. */
.report .r-term-dates-h{font-weight:700;font-size:13px;margin-top:14px;margin-bottom:6px}
.report .r-term-dates-grid{width:100%;border-collapse:collapse;font-size:12px}
.report .r-term-dates-grid th,.report .r-term-dates-grid td{border:1.3px solid var(--grid-ink);padding:8px;text-align:center}
.report .r-term-dates-grid th{font-size:10.5px;font-weight:800;background:#f7f9fb;text-transform:uppercase;letter-spacing:.3px;color:var(--grid-text)}
.report .r-term-dates-grid td{font-weight:700;color:var(--grid-text)}
/* §6: a branded finishing strip at the very bottom of the report — the
   school's motto (Settings already has this field; it just wasn't shown
   anywhere printable before now). Full-bleed edge to edge, so it sits
   OUTSIDE .r-body's padding as its own sibling — see renderReportCard(). */
/* Round 5 §3: was right-aligned — centered within its rectangle instead. */
.report .r-motto{background:linear-gradient(100deg,var(--brand),var(--brand-2));color:#fff;text-align:center;font-weight:700;font-style:italic;font-size:11.5px;padding:8px 22px;letter-spacing:.2px}
/* Round 3 §14/§15: the report form previously used the app's light on-screen
   chrome tokens (--line/--muted) for its table borders and secondary text —
   fine for UI chrome, but washed-out grey once printed/saved as a PDF,
   instead of the crisp deep-black grid Mark List/Class List/Score Sheet/Exam
   Analysis already use (--grid-ink/--grid-text, defined at the top of this
   file). Reusing those same tokens here fixes the grey-borders-and-text bug
   and brings the report in line with the rest of the app's printables. Only
   the --brand teal accents (title bar, avatar, summary figures) are left
   untouched — those are intentional brand color, not part of
   the grey/black complaint. Preview/print/download all render from this
   same CSS (see app.js's printWithOptions — Download is just window.print()
   with no separate PDF pipeline), so this one change keeps all three
   consistent, per §14. No QR/verification code was added — none exists in
   _reportCard.mjs and none was requested; §14 explicitly excludes it. */
/* Column-selection checkboxes (class list print options) */
.chk-row{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:2px}
.chk{display:flex;align-items:center;gap:7px;font-weight:500;font-size:13px;cursor:pointer;color:var(--ink)}
.chk input{width:auto;margin:0}
/* Bug fix (feature brief §2): this used to be scoped to ".col.print-hide",
   a class that's never actually applied anywhere (classList.mjs toggles
   "print-hide" on elements classed "col-guardian_name" etc, not "col") — so
   the rule silently never matched and an unticked Guardian/Guardian Contact
   column kept showing on both screen and print regardless of the checkbox.
   Matching on ".print-hide" alone, and unconditionally (not just inside
   @media print), makes the on-screen preview and the printed/downloaded
   output agree with what's actually ticked. */
.print-hide{display:none!important}
/* Logo (School Settings + printouts) */
.logo-thumb{width:64px;height:64px;border-radius:10px;object-fit:cover;border:1px solid var(--line);background:#fff}
.logo-placeholder{width:64px;height:64px;border-radius:10px;border:1.5px dashed var(--line);display:grid;place-items:center;color:var(--muted);font-size:22px;background:#fafbfc}
/* ----------------------------------------------------------------------
   Round 4 §7 — Timetable module (_timetableGrid.mjs / timetable.mjs /
   timetableSetup.mjs). The main grid deliberately reuses the same visual
   language as .report-grid (bordered cells, uppercase small headers) so a
   printed timetable feels consistent with every other printed report,
   without actually depending on .report's own scoping (Timetable isn't a
   "report" page — see printHeader.mjs's global, unscoped .print-header,
   which every non-Report-Form printable screen already relies on the same
   way). Prints portrait/landscape via the same shared printOptionsHtml()/
   wirePrintOptions() every other report uses — no bespoke print code here.
   ---------------------------------------------------------------------- */
/* Round 2 §7: printed page shell — themed border + a muted header band
   instead of the old rule under the header (see _timetableGrid.mjs's
   timetableGridPageHtml doc comment). */
.tt-page{border:1.5px solid var(--primary)}
.tt-page-head{background:var(--bg);padding:14px 16px}

/* Round 2 §7: TRANSPOSED — days are rows, periods are columns (used to be
   the other way around). .tt-day-col is the left-hand day label column,
   equivalent to the old .tt-period-col's role but now holding a day name
   instead of a period; .tt-break-col/.tt-break-cell replace the old
   .tt-break-row now that a break is a whole column, not a whole row.

   Round 5 §9: table-layout:fixed + the <colgroup> that
   _timetableGrid.mjs's colgroupHtml() emits is what makes every normal
   period column share the remaining width equally, no matter how long a
   subject name is — the column no longer grows to fit its widest cell,
   and text wraps within the fixed width instead (see .tt-subject below).
   Break columns get their own narrow fixed width from that same
   colgroup and merge via colspan (see .tt-break-merged) instead of
   sitting side-by-side as separate columns. Font sizes bumped up across
   the board for print legibility; the black grid lines below are left
   exactly as they were — not lightened. */
.tt-grid{width:100%;table-layout:fixed;border-collapse:collapse;font-size:13.5px;color:var(--grid-text)}
.tt-grid th,.tt-grid td{border:1.3px solid var(--grid-ink);padding:8px 6px;vertical-align:middle}
.tt-grid thead th{font-size:11.5px;font-weight:800;background:#f7f9fb;text-transform:uppercase;letter-spacing:.3px;text-align:center}
.tt-grid .tt-day-col{font-size:12.5px;white-space:nowrap;background:#fafbfc;text-align:left;vertical-align:middle}
.tt-grid .tt-period-col{overflow-wrap:break-word}
.tt-grid .tt-time{font-size:10.5px;color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0}
.tt-grid .tt-break-col{background:#eef1f5}
.tt-grid .tt-break-cell{background:#eef1f5;text-align:center;font-weight:700;color:var(--muted);font-size:12px;vertical-align:middle;padding:6px 2px}
/* A merged multi-period break block (colspan>1) reads as one clean cell —
   no internal border, the full session name centered and rotated to fit
   the narrow column instead of Zeraki's letter-by-letter split. */
.tt-grid .tt-break-merged{padding:10px 2px}
.tt-break-label-v{writing-mode:vertical-rl;transform:rotate(180deg);display:inline-block;white-space:nowrap;font-weight:800;letter-spacing:.4px;font-size:12px;color:var(--muted)}
.tt-grid .tt-cell{text-align:center}
.tt-grid .tt-subject{font-weight:700;font-size:14px;line-height:1.2;white-space:normal;overflow-wrap:break-word;word-break:normal}
.tt-grid .tt-sub2{font-size:11.5px;color:var(--muted);margin-top:2px;white-space:normal;overflow-wrap:break-word}
.tt-grid .tt-room{display:inline-block;margin-top:4px;font-size:10px;font-weight:700;color:var(--brand-2);background:#eaf3f2;border-radius:6px;padding:1px 6px}
.tt-grid .tt-editable{cursor:pointer}
.tt-grid .tt-editable:hover{background:#eef6f5}

/* Round 5 §10: version history pills on the View tab — a regenerate keeps
   the last 3 versions instead of deleting the previous one, and these are
   how someone switches back to an older one. */
.tt-ver-list{display:flex;flex-wrap:wrap;gap:8px}
.tt-ver-pill{font-size:12px;font-weight:600;border-radius:20px;padding:6px 12px;border:1px solid var(--line)}
.tt-ver-active{background:#eaf3f2;color:var(--brand-2);border-color:#cfe6e3}
.tt-ver-switch{background:#fff;color:var(--ink);cursor:pointer}
.tt-ver-switch:hover{background:#f7f9fb;border-color:var(--primary)}
/* Teacher Availability click-grid (Setup tab) — not printed, screen-only. */
.tt-avail-grid th,.tt-avail-grid td{text-align:center}
.tt-avail-cell{cursor:pointer;font-weight:600;font-size:12px;color:#1a7a4c;user-select:none}
.tt-avail-cell:hover{background:#f2f8f5}
.tt-avail-cell.blocked{color:var(--danger);background:#fdf1f1}

/* Round 5 §8: Timetable Constraints restyle — Zeraki-style flat, grouped
   rows (short bold name + one muted line + a toggle) instead of one big
   card-with-a-form per constraint. .tt-crow-config is the small inline
   editor that slides open only for a constraint that actually needs more
   than on/off (which subjects, a max number) — collapsed by default so the
   list reads as clean as the reference even though a few rows do need
   extra setup. */
.tt-constraint-group .card-h{align-items:flex-start}
.tt-crow-list{display:flex;flex-direction:column}
.tt-crow{padding:14px 20px;border-top:1px solid var(--line)}
.tt-crow-main{display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.tt-crow-text{flex:1;min-width:0}
.tt-crow-title{font-weight:700;font-size:13.5px;color:var(--ink)}
.tt-crow-desc{font-size:12px;color:var(--muted);margin-top:2px;line-height:1.4}
.tt-crow-actions{display:flex;align-items:center;gap:10px;flex:none}
.tt-crow-config{margin-top:12px;padding-top:12px;border-top:1px dashed var(--line)}
/* A real pill toggle switch, not a plain checkbox — matches the reference
   images' toggles. Built from a hidden checkbox + a styled label, same
   accessible-checkbox-under-custom-look pattern as .chk elsewhere. */
.tt-switch{position:relative;display:inline-block;width:38px;height:22px;flex:none;cursor:pointer}
.tt-switch input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer;z-index:1}
.tt-switch-slider{position:absolute;inset:0;background:#d5dbe2;border-radius:22px;transition:.15s}
.tt-switch-slider::before{content:"";position:absolute;width:16px;height:16px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.15s;box-shadow:0 1px 2px rgba(0,0,0,.25)}
.tt-switch input:checked + .tt-switch-slider{background:var(--brand-2)}
.tt-switch input:checked + .tt-switch-slider::before{transform:translateX(16px)}
/* Round 6 §8: a "please wait" state for the toggle switches themselves —
   textContent swap (withBusy's usual trick) doesn't apply to a
   checkbox+span control, so this is a lighter opacity/cursor treatment
   applied to the wrapping .tt-switch while its save is in flight. */
.tt-switch.busy{opacity:.55;cursor:wait}
.tt-switch.busy input{cursor:wait}

/* Sprint Review §2: the Setup > Subject Periods running total, redesigned
   from one small hint line into two always-visible "Expected"/"Scheduled"
   tiles plus a status message — see timetableSetup.mjs's recomputeTotal().
   The Scheduled tile and the message both switch to a red, impossible-to-
   miss treatment the moment the week is full or over, not just on save. */
.tt-cap-tiles{display:flex;gap:14px;margin-top:16px;flex-wrap:wrap}
.tt-cap-tile{flex:1;min-width:160px;border:1.5px solid var(--line);border-radius:10px;padding:14px 16px;text-align:center;transition:border-color .15s,background .15s}
.tt-cap-label{font-size:11px;font-weight:750;letter-spacing:.5px;color:var(--muted)}
.tt-cap-value{font-size:28px;font-weight:800;margin-top:4px;line-height:1.15}
.tt-cap-sub{font-size:11px;color:var(--muted);margin-top:2px}
.tt-cap-tile.over{border-color:var(--danger);background:var(--danger-bg)}
.tt-cap-tile.over .tt-cap-value{color:var(--danger)}
#req-total-msg.danger{color:var(--danger)}

/* Brief §8/§9: printed reports should have sane margins/page sizing and
   never require scrolling on paper. @page sets a consistent margin on every
   printed page. Landscape printing (e.g. the Mark List/broadsheet, which can
   have a dozen-plus subject columns) is handled at print-time by
   app.js's printLandscape() helper, which swaps in a plain @page override —
   NOT by the CSS "page" property + a named @page rule, which turned out to
   be unreliable across browsers (reported bug: printed a blank page). */
@page{size:A4;margin:12mm}
@media print{
  /* Root cause of "on-screen it's perfect, printed/downloaded it loses all
     its color" (reported on the Report Form, but this applies to every
     printable screen — Mark List/Class List/Score Sheet/Exam Analysis all
     use background-color fills too): Chrome and most other browsers do NOT
     print background colors/gradients by default — there's a "Background
     graphics" checkbox in the print dialog's "More settings" that's
     unchecked out of the box, and window.print()/Download (see app.js's
     printWithOptions() — Download is just window.print(), no separate PDF
     pipeline) both go through that same dialog. Borders and text print
     fine regardless (they're not "background"), which is exactly the
     "colors gone, borders/text still there" symptom. print-color-adjust:
     exact (plus the older -webkit- prefix Chrome/Safari still want) tells
     the browser to print backgrounds AS IF that box were ticked, without
     the person printing ever having to find and check it themselves. `*`
     so it's not an easy-to-miss per-element opt-in — every background
     color anywhere in a printed page (the report's teal title bar and
     avatar, its grey summary tiles/table headers/remark boxes, the motto
     strip, every other report's shaded table headers) is covered by one
     rule instead of needing to be listed out and kept in sync by hand. */
  *{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important;color-adjust:exact!important}
  .sidebar,.topbar,.no-print{display:none!important}
  .main{margin-left:0}.content{padding:0;max-width:none}
  .report{border:none;box-shadow:none;max-width:none}
  body{background:#fff}
  .card{box-shadow:none;border:none}
  .batch-page{page-break-after:always}
  .batch-page:last-child{page-break-after:auto}
  /* Wide printable tables (broadsheet, class list, transcript) — shrink to
     fit the page width instead of being clipped or forcing a scrollbar
     that paper obviously can't have. */
  .table-wrap{overflow:visible}
  table.data{font-size:10.5px}
  table.data th,table.data td{padding:5px 7px}
  .print-header{break-inside:avoid;page-break-inside:avoid}
  .print-grid{font-size:10.5px}
  .print-grid th,.print-grid td{padding:5px 7px}
  /* Round 3 §11: the Mark List's Class Grade Summary (+ Gender Grade
     Summary / Grade Breakdown by Subject, part of the same .bs-summary
     block) must always start on its own fresh page on print, even when the
     student grid above it technically leaves enough room on the same page
     — mixing "some of the summary" onto the tail end of the grid's last
     page reads as a cut-off, unfinished-looking printout. break-before is
     the modern property; page-break-before is the same rule for older
     print engines that don't understand break-before yet. */
  .bs-summary{break-before:page;page-break-before:always}
}

/* ---------- Loader ---------- */
.loader{display:grid;place-items:center;padding:60px;gap:14px}
.loader-msg{color:var(--muted);font-size:14px;font-weight:550;margin:0}
.spin{width:34px;height:34px;border:3px solid var(--line);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.skeleton{background:linear-gradient(90deg,#eef1f5 25%,#f6f8fa 50%,#eef1f5 75%);background-size:200% 100%;
  animation:sk 1.2s infinite;border-radius:6px;height:14px}
@keyframes sk{to{background-position:-200% 0}}

/* ---------- Responsive ---------- */
/* "screen and" matters here: without it, this block also matches at PRINT
   time, because an A4 page's content width (~700-750px after margins) is
   under 960px too — so a phone-only breakpoint was silently reshaping the
   Report Form on actual printouts (e.g. the 4 summary tiles collapsing to
   2x2) even though nothing here is meant for print. Found while verifying
   the summary-tile redesign against a real print render. */
@media screen and (max-width:960px){
  .hub-grid.stack{width:100%}
  .sidebar{transform:translateX(-100%)}
  .sidebar.open{transform:none}
  .main{margin-left:0}
  .topbar .menu-toggle{display:inline-flex}
  /* Tailor the same breakpoint to different phone widths rather than one
     flat mobile treatment — a 6.7" phone (~430px) has real room to spare,
     a compact one (~360px) doesn't, and anything at/under ~320px (the
     narrowest phones still in use) needs every extra pixel of padding
     clawed back so content never has to compete with the chrome for
     space. */
  .content{padding:16px}
  .page-head{gap:10px;margin-bottom:16px}
  .page-head h2{font-size:19px}
  .card .card-b,.card.pad{padding:16px}
  .card .card-h{padding:14px 16px}
  /* BUG FIX: these rules used to sit AFTER the max-width:400px/340px blocks
     below, outside of any @media query at all (an orphaned stray closing
     brace here made everything from .auth down through .dash-gender-
     desktop apply globally, at every screen width, not just mobile — e.g.
     .stats-desktop{display:none} and .dash-top-row{display:block} were
     silently breaking the DESKTOP dashboard layout too). Moved back inside
     this 960px block, which is what they were always meant to belong to. */
  /* Mobile sign-in redesign ("Aurora" — approved option 1 of 5): the promo
     panel used to just vanish on mobile (display:none), leaving a single
     small form centered in an otherwise-empty screen — a lot of dead
     whitespace, and the brand/feature tiles were nowhere to be seen. Now
     the whole screen keeps one continuous dark aurora background (soft
     blurred colour blobs, no flat gradient), the promo content becomes a
     compact header instead of a full-height panel, the 4 feature tiles
     become small icon badges in a row, and the form becomes a frosted
     glass card sitting on that same background — using the full screen
     instead of leaving most of it blank. */
  .auth{grid-template-columns:1fr;background:radial-gradient(circle at 20% 0%,#1f8f7e 0%,transparent 55%),
    radial-gradient(circle at 90% 15%,#f5a623 0%,transparent 40%),
    radial-gradient(circle at 45% 60%,#1a9c8a 0%,transparent 45%),#0c2e2f}
  .auth .promo{display:flex;min-height:0;padding:30px 20px 10px;background:none;flex-direction:column;align-items:center;text-align:center}
  .auth .promo::after,.auth .promo::before{display:none}
  .auth .promo-inner{background:none;border:none;box-shadow:none;backdrop-filter:none;padding:0;max-width:none;width:100%}
  .auth .promo .logo{margin:0 auto 10px;width:48px;height:48px;font-size:22px}
  .auth .promo h1{font-size:19px;margin-bottom:4px}
  .auth .promo p{font-size:12.5px;margin:0 auto 18px;max-width:280px}
  .feat-grid{grid-template-columns:repeat(4,1fr);gap:8px}
  .feat-tile{flex-direction:column;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.18);
    border-radius:50%;width:46px;height:46px;padding:0;display:grid;place-items:center;margin:0 auto}
  .feat-tile .ft-ico{font-size:16px}
  .feat-tile .ft-title,.feat-tile .ft-sub{display:none}
  .auth .formside{min-height:0;padding:20px 16px 34px;background:none}
  .auth .formcard{border-radius:20px;box-shadow:0 20px 50px rgba(0,0,0,.35);border:1px solid rgba(255,255,255,.4);
    max-width:none;padding:24px 20px;background:rgba(255,255,255,.95);backdrop-filter:blur(8px)}
  .grid2,.grid3,.grid4{grid-template-columns:1fr}
  .report .r-summary{grid-template-columns:1fr 1fr}
  .report .r-student-row{flex-wrap:wrap}
  .report .r-chart{width:100%}
  .report .r-remarks{grid-template-columns:1fr}
  .stats-desktop{display:none}
  .stats-mobile{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-bottom:22px}
  .gender-panel{padding:18px 16px}
  .gender-panel .g-num{font-size:28px}
  /* The mobile tile set already has its own combined gender tile — don't
     also show the desktop gender panel (it would duplicate that number).
     BUG FIX (round 2 brief): this used to be the bare class `.dash-gender-
     desktop{display:none}`, specificity (0,1,0) — LOWER than the always-on
     base rule `.dash-top-row .dash-gender-desktop{display:flex;...}` at
     (0,2,0) above. CSS specificity beats source order, so that override
     never actually won and the desktop gender card kept rendering
     alongside the mobile grid's own combined tile — exactly the "shows
     twice" bug reported. Matching the same two-class selector here fixes
     it for real. */
  .dash-top-row{display:block;margin-bottom:0}
  .dash-top-row .dash-gender-desktop{display:none}
}
@media screen and (max-width:400px){
  .content{padding:12px}
  .page-head h2{font-size:17px}
  .card .card-b,.card.pad{padding:13px}
}
@media screen and (max-width:340px){
  .content{padding:10px}
}
.scrim{position:fixed;inset:0;background:rgba(0,0,0,.3);z-index:35;display:none}
.scrim.show{display:block}
@media(max-width:960px){.scrim.show{display:block}}

/* ---------- Finance module ---------- */
.fin-progress{display:inline-block;width:100px;height:8px;border-radius:5px;background:var(--line);overflow:hidden;vertical-align:middle;margin-right:6px}
.fin-progress-fill{height:100%;background:var(--primary);border-radius:5px}
.fin-statement table{width:100%;border-collapse:collapse}
.fin-statement th,.fin-statement td{border:1px solid var(--grid-ink);padding:5px 8px;font-size:12px}
/* Next Sprint §Finance "Redesign the Student Statement": one boxed table
   per term (TERM:YYYY/N), each ending with its own shaded "close of term"
   balance row — see financeStudent.mjs's renderStatement()/groupByTerm().
   The box header and the closing row both use background-color (never
   box-shadow — .card{box-shadow:none} at print time would silently drop
   it, see the @media print block above) so they're covered by this file's
   blanket `*{print-color-adjust:exact}` rule and survive printing exactly
   as they look on screen — verified with a real headless print-to-PDF
   render, not just visual review (see tests/print_statement.js). */
.fin-statement-term{margin-bottom:18px;border:1.3px solid var(--grid-ink);border-radius:8px;overflow:hidden;break-inside:avoid;page-break-inside:avoid}
.fin-statement-term-head{background:var(--brand);color:#fff;font-weight:700;padding:8px 12px;font-size:13px;letter-spacing:.2px}
.fin-statement-term table{width:100%;border-collapse:collapse}
.fin-statement-term th,.fin-statement-term td{border:1px solid var(--grid-ink);padding:5px 8px;font-size:12px}
.fin-statement-term thead th{background:#f0f2f5;color:var(--grid-text);font-weight:700}
.fin-statement-term-close td{background:#eef1f5;font-weight:700}
/* Design standard rollout round 2 item 1: the on-screen Statement view is
   simplified ONLY on narrow/phone screens (last 3 terms, one plain line
   per transaction with a running balance) — the desktop screen view and
   the print output both stay exactly the original boxed per-term table
   (.fin-statement-term), untouched. BUG FIX: this used to gate purely on
   .no-print/.print-only (screen vs print), which meant the simplified
   view replaced the desktop table too — wrong, desktop was never meant to
   change. Now gated on viewport width (same 960px breakpoint .fin-tabs
   already uses for its own mobile behaviour), with print forced back to
   the desktop table regardless of viewport. */
.fss-mobile-view{display:none}
.fss-desktop-view{display:block}
@media screen and (max-width:960px){
  .fss-mobile-view{display:block}
  .fss-desktop-view{display:none}
}
@media print{
  .fss-mobile-view{display:none!important}
  .fss-desktop-view{display:block!important}
}
/* Mobile statement view — term switcher rendered as bordered tiles, same
   left/right-accent-border language used everywhere else this round
   (.side-accent/.tile-*), not a filled pill/segmented control. */
.mstate-tabs{display:flex;gap:8px;margin-bottom:12px}
.mstate-tabs button{flex:1;padding:8px 4px;border-radius:8px;border:1.5px solid var(--line);background:#fff;font-family:inherit;font-size:12px;font-weight:600;color:var(--muted);cursor:pointer;text-align:center}
.mstate-tabs button.active{border-color:#2563eb;color:var(--ink)}
.mstate-total{text-align:center;background:var(--brand);color:#fff;border-radius:10px;padding:10px;margin-bottom:6px}
.mstate-total .lbl{font-size:11px;opacity:.8}
.mstate-total .val{font-size:19px;font-weight:700}
.mstate-list .row{display:flex;justify-content:space-between;align-items:baseline;padding:9px 2px;border-bottom:1px solid var(--line);font-size:13px;font-weight:400}
.mstate-list .row:last-child{border-bottom:none}
.mstate-list .row .l{display:flex;gap:8px;align-items:baseline}
.mstate-list .row .date{font-size:11px;color:var(--muted);min-width:40px}
.mstate-list .row .r{display:flex;gap:14px;align-items:baseline}
.mstate-list .row .amt{min-width:64px;text-align:right;display:inline-block;font-weight:400}
.mstate-list .row .amt.plus{color:var(--ok)}
.mstate-list .row .amt.minus{color:var(--danger)}
.mstate-list .row .bal{min-width:56px;text-align:right;display:inline-block;font-weight:400;color:var(--ink)}
.mstate-col-heads{display:flex;justify-content:flex-end;gap:14px;padding:0 2px 4px;font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px}
.mstate-col-heads span:first-child{min-width:64px;text-align:right}
.mstate-col-heads span:last-child{min-width:56px;text-align:right}
/* Design standard rollout round 2 item 9: same mobile-only / desktop-and-
   print-untouched pattern as the Statement view above, now applied to
   Balances (Finance Reports) and Cashbook (Finance Reports) — each report
   keeps its exact print-grid table for desktop screens and printing, and
   only swaps in a simplified view under the same 960px breakpoint. */
.frb-mobile-view,.rcb-mobile-view{display:none}
.frb-desktop-view,.rcb-desktop-view{display:block}
@media screen and (max-width:960px){
  .frb-mobile-view,.rcb-mobile-view{display:block}
  .frb-desktop-view,.rcb-desktop-view{display:none}
}
@media print{
  .frb-mobile-view,.rcb-mobile-view{display:none!important}
  .frb-desktop-view,.rcb-desktop-view{display:block!important}
}
/* Balances — one card per student with a Paid-vs-Expected progress bar
   (approved "Style 3"), balance colored red if still owing, green if
   cleared/overpaid. */
.frb-card{padding:10px 2px;border-bottom:1px solid var(--line);font-size:13px}
.frb-card:last-child{border-bottom:none}
.frb-top{display:flex;justify-content:space-between}
.frb-sub{font-size:11px;color:var(--muted);margin:1px 0 6px}
.frb-bar{height:5px;border-radius:3px;background:var(--line);overflow:hidden}
.frb-bar-fill{height:100%;background:var(--primary)}
.frb-bottom{display:flex;justify-content:space-between;margin-top:4px;font-size:11px;color:var(--muted)}
.frb-bal.owe{color:var(--danger)}
.frb-bal.ok{color:var(--ok)}
/* Cashbook — one plain feed line per entry, same shape as the Statement
   rows already approved. */
.rcb-row{display:flex;justify-content:space-between;align-items:baseline;padding:9px 2px;border-bottom:1px solid var(--line);font-size:13px}
.rcb-row:last-child{border-bottom:none}
.rcb-row .l{display:flex;gap:8px;align-items:baseline}
.rcb-row .date{font-size:11px;color:var(--muted);min-width:40px}
.rcb-row .mode-tag{font-size:9.5px;text-transform:uppercase;color:var(--muted);border:1px solid var(--line);border-radius:5px;padding:1px 5px;margin-left:6px}
.rcb-row .amt{color:var(--ok)}
/* Collections — one line per receipt (student + class/receipt/mode as
   subtitle), amount plus the student's resulting balance: red if they
   still owe, green if cleared or overpaid. Desktop table is untouched;
   this list has no print view of its own (receipts print individually),
   so only the 960px screen-width breakpoint applies, no @media print
   override is needed here. */
.fcol-mobile-view{display:none}
.fcol-desktop-view{display:block}
@media screen and (max-width:960px){
  .fcol-mobile-view{display:block}
  .fcol-desktop-view{display:none}
}
.fcol-row{display:flex;justify-content:space-between;align-items:baseline;padding:9px 2px;border-bottom:1px solid var(--line);font-size:13px;cursor:pointer;border-radius:8px}
.fcol-row:last-child{border-bottom:none}
.fcol-row:active{background:#f2f8f7}
.fcol-row .l{display:flex;flex-direction:column}
.fcol-row .sub{font-size:11px;color:var(--muted)}
.fcol-row .r{text-align:right}
.fcol-row .amt{color:var(--ink)}
.fcol-row .fcol-bal{display:block;font-size:11px;margin-top:1px}
.fcol-row .fcol-bal.owe{color:var(--danger)}
.fcol-row .fcol-bal.ok{color:var(--ok)}
/* Design standard rollout round 2: Student Profile's Collections rows —
   date/receipt/mode/amount, no invented per-purpose label (a receipt can
   span several vote heads), with small Print/Reverse icon buttons on the
   right instead of the whole row being tappable — Reverse is destructive
   and shouldn't be one accidental tap away. */
.fcol-row-student{cursor:default;align-items:center}
.fcol-row-student:active{background:none}
.fcol-icons{display:flex;gap:6px;flex:0 0 auto}
.fcol-ico{width:30px;height:30px;border-radius:8px;border:1.5px solid var(--line);display:flex;align-items:center;justify-content:center;font-size:13px;background:#fff;cursor:pointer}
.fcol-ico.warn{border-color:var(--warn)}
/* BUG FIX: this used to be position:relative, which means it took up real
   space in the page's normal flow — as results loaded in, the dropdown
   grew taller and physically shoved the Finance tabs (Dashboard/Invoicing/
   Collections/.../Transport) and everything below them further down the
   page, then back up again when the box was cleared. It should float
   ON TOP of whatever's below it, not resize the layout around itself —
   every element with this class already sits inside (or a fix below
   makes it sit inside) a position:relative container, so absolute here
   positions it right under its own input without disturbing anything
   else on the page. */
.search-results{position:absolute;top:100%;left:0;right:0;z-index:20;max-height:220px;overflow:auto;border:1px solid var(--line);border-radius:8px;margin-top:4px;background:var(--card-bg,#fff);box-shadow:var(--shadow,0 4px 16px rgba(16,32,45,.12))}
.search-results:empty{border:none;margin-top:0;box-shadow:none}
.search-hit{padding:8px 10px;cursor:pointer;border-bottom:1px solid var(--line)}
.search-hit:last-child{border-bottom:none}
.search-hit:hover{background:var(--hover-bg,#f4f6fa)}

/* ---------- Finance Round 2 §1: top-bar redesign ----------
   The hub tabs previously reused .settings-tabs (max-width:520px), which is
   exactly why they read as clustered/narrow with grey text — that's a
   Settings-specific convention, not meant for a 6-tab module bar. .fin-tabs
   is Finance's own full-width variant: black label text (not grey) on
   both states, and evenly distributed via flex:1 same as .tabs already
   does, just without the width cap. .fin-toolbar is the reusable
   search(~half width)+action(right) row every Finance screen's header
   uses now, via a shared .spacer push. */
.fin-tabs{display:flex;gap:6px;background:#eef1f5;padding:5px;border-radius:10px;margin-bottom:18px;max-width:none}
.fin-tabs button{flex:1;padding:11px 14px;border:none;background:none;border-radius:7px;cursor:pointer;
  color:var(--ink);font-family:inherit;font-size:13.5px;font-weight:600;text-align:center;transition:background .15s}
.fin-tabs button.active{background:#fff;font-weight:700;box-shadow:var(--shadow)}
/* Design standard rollout round 2 (approved — BORDER ONLY, no fill/text
   colour change, same rule as everywhere else this round): a thin blue
   border added around each element, nothing about its existing colour,
   background or text touched. */
.fin-tabs button{border:1.5px solid transparent}
.fin-tabs button.active{border-color:#2563eb}
.btn.secondary.accent-blue{border-color:#2563eb}
.icon-btn.border-accent{border:1.5px solid #2563eb}
.fin-tabs button:not(.active):hover{background:rgba(255,255,255,.5)}
/* Same min-content-width overflow as .tabs above (this bar has up to 6
   labels — Dashboard/Invoicing/Collections/Reports/Transport — plus long
   sub-tab labels like "Subject Periods & Double Lessons" on Timetable
   Setup, which also reuses .fin-tabs): scroll the strip instead of
   squeezing every button to fit. */
@media screen and (max-width:960px){
  .fin-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin}
  .fin-tabs button{flex:0 0 auto;min-width:110px;white-space:normal;line-height:1.2}
  /* Same 2-item exception as .tabs above — Teachers/Staff and Students
     (Active / Left) both reuse .fin-tabs and must always split evenly. */
  .fin-tabs > button:first-child:nth-last-child(2),
  .fin-tabs > button:first-child:nth-last-child(2) ~ button{flex:1;min-width:0;white-space:nowrap}
  .fin-tabs:has(> button:first-child:nth-last-child(2)){overflow-x:visible}
  /* Design standard rollout round 2 item 2: the same gap-on-the-right bug
     as above, but for 3-button bars (Finance student's Profile/
     Collections/Statement, Messaging's Compose/History/SMS Credits,
     Attendance's Mark Students/Mark Staff/Sign In-Out, Timetable's
     Setup/Generate/View, Invoicing's Fee Structures/Debit/Credit Notes,
     Exam Desk's Review/Marks/Bulk) — 3 short labels never need the
     scroll-strip behaviour that the up-to-6-tab bars above need, so they
     should always split evenly with no leftover space, same fix as the
     2-item case. */
  .fin-tabs > button:first-child:nth-last-child(3),
  .fin-tabs > button:first-child:nth-last-child(3) ~ button{flex:1;min-width:0;white-space:nowrap}
  .fin-tabs:has(> button:first-child:nth-last-child(3)){overflow-x:visible}
}
@media screen and (max-width:400px){.fin-tabs button{min-width:92px;font-size:12px;padding:9px 8px}}

/* ---------- Finance Nav B: dedicated left side-nav ----------
   Brief item 8 (approved option B): Finance gets its own side-nav instead
   of the top .fin-tabs strip, shown only while inside the Finance module
   (never on the main app shell before Finance is opened). On desktop it's
   a simple sticky column; on mobile it collapses into a slide-in drawer,
   mirroring the main .sidebar's own open/scrim pattern exactly so the
   interaction is already familiar. .fin-tabs itself is left in place
   above (still used by Timetable Setup's sub-tabs, unrelated to this). */
/* Desktop redesign (Finance_Dashboard_Desktop_Design_Standard.docx item 1):
   fixed + floats OVER page content instead of a sticky column that reserves
   space in the flex row — collapsed by default as a slim rail showing only
   a right-pointing arrow (no label, the icon communicates "expand" on its
   own), expands on click to the full labelled list without pushing
   .fin-side-body to the right. Background now runs the full viewport
   height (not just wrapping the 6 links) and uses a distinct cool tone
   (--fin-nav-bg) instead of white, kept as its own variable since the
   exact hue is still pending sign-off — swap this one line to retune it. */
:root{--fin-nav-bg:#15343a}
.fin-side-toggle{display:none}
/* On mobile this sits directly under the global sidebar's own ☰ toggle in
   the topbar (both left-aligned at the same x) — reading as one duplicated
   button stacked vertically. Nudging it right offsets it from that column
   without changing its plain icon-btn styling. */
.fin-side-toggle{margin-left:8px}
.fin-shell{display:flex;align-items:flex-start;gap:10px}
.fin-side-nav a{display:block;padding:10px 14px;border-radius:8px;color:#cfe3e2;font-weight:600;
  font-size:13px;cursor:pointer;margin:2px 6px;transition:background .12s;white-space:nowrap}
.fin-side-nav a:hover{background:rgba(255,255,255,.1);color:#fff}
.fin-side-nav a.active{background:var(--primary);color:#fff}
.fin-side-nav a.fin-nav-msg{margin-top:10px;padding-top:14px;border-top:1px solid rgba(255,255,255,.12)}
.fin-side-nav .fin-rail-arrow{display:none}
.fin-side-body{flex:1;min-width:0}
/* Desktop-only: fixed rail that overlays content instead of a sticky column
   that reserves row space — scoped to min-width:961px so it can never
   collide with the mobile drawer rules below (which use a different toggle
   class, .open, and must always show full labels). */
@media screen and (min-width:961px){
  .fin-side-nav{position:fixed;top:62px;bottom:0;left:calc(var(--sidebar) + 6px);width:46px;
    background:var(--fin-nav-bg);border-radius:0 14px 14px 0;padding:14px 0;z-index:32;overflow:hidden;
    transition:width .18s ease;box-shadow:2px 0 12px rgba(0,0,0,.12);cursor:pointer}
  .fin-side-nav.expanded{width:150px;cursor:default}
  .fin-side-nav .fin-rail-arrow{display:flex;width:46px;height:28px;align-items:center;justify-content:center;
    color:#cfe3e2;font-size:15px;flex-shrink:0;transition:transform .18s}
  .fin-side-nav.expanded .fin-rail-arrow{transform:rotate(180deg)}
  .fin-side-nav:not(.expanded) a{pointer-events:none;opacity:0}
  /* .fin-side-body's margin only accounts for the COLLAPSED rail width — the
     expanded nav overlays on top rather than reflowing this. */
  .fin-side-body{flex:1;min-width:0;margin-left:56px}
  /* The page header (title + search bar) sits ABOVE .fin-shell as its own
     sibling, so it doesn't inherit .fin-side-body's margin — without this
     the fixed rail overlapped "Finance" and the search box. */
  .fin-page-head{margin-left:56px}
}
@media screen and (max-width:960px){
  .fin-side-toggle{display:inline-flex}
  .fin-shell{gap:0}
  .fin-side-nav{position:fixed;top:0;bottom:0;left:0;width:230px;border-radius:0;background:var(--brand);
    z-index:40;overflow-y:auto;transform:translateX(-100%);transition:transform .25s;box-shadow:var(--shadow);
    padding:14px 0}
  .fin-side-nav .fin-rail-arrow{display:none}
  .fin-side-nav a{opacity:1;pointer-events:auto;font-size:13.5px;padding:10px 13px}
  .fin-side-nav.open{transform:none}
  .fin-side-body{margin-left:0}
}
.fin-toolbar{display:flex;gap:16px;align-items:center;flex-wrap:wrap;margin-bottom:18px}
.fin-toolbar .fin-search{flex:1 1 420px;max-width:560px;margin:0}
.fin-toolbar .fin-search input{width:100%}
.fin-toolbar .fin-filters{display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end;flex:1 1 420px}
.fin-toolbar .fin-filters .field{margin:0;min-width:170px;flex:1 1 170px;max-width:260px}
@media(max-width:720px){
  .fin-toolbar{flex-direction:column;align-items:stretch}
  .fin-toolbar .spacer{display:none}
  /* THE gap bug: .fin-search/.fin-filters keep their row-layout
     flex:1 1 420px here. In column mode a flex child's basis is read as a
     target HEIGHT, not width — the child still renders at its real
     (small) content height, but because .fin-toolbar's own height is
     auto, the browser's intrinsic-size calc reserves the full 420px for
     it anyway, leaving a large empty gap before whatever comes next
     (e.g. "+ Add New Teacher", or the Academic Year/Term filters on
     Finance Dashboard). Resetting the basis to auto makes each child's
     footprint match what's actually rendered. */
  .fin-toolbar .fin-search,
  .fin-toolbar .fin-filters{flex:1 1 auto;max-width:none}
  .fin-toolbar .fin-filters .field{flex:1 1 140px}
}
@media(max-width:400px){.fin-toolbar{gap:10px}.fin-toolbar .fin-filters{gap:8px}}
/* Design standard rollout round 2 item 3: on phones, filter fields stack
   in one clean single column instead of wrapping two-per-row — reads as
   organised rather than scattered, same intent as the Reports/Fee
   Structures sketches. .fin-report-actions (Excel + the now print-only
   button) becomes one even two-button row rather than left-over inline
   buttons of different widths. */
@media(max-width:600px){
  .fin-toolbar .fin-filters .field{flex:1 1 100%;max-width:none}
  .fin-report-actions{width:100%}
  .fin-report-actions,.fin-report-actions .print-opts-simple{flex:1 1 auto}
  .fin-report-actions .btn,.fin-report-actions .print-opts-simple .btn{flex:1}
}
/* Design standard rollout round 2 item 2: Reports' own tab bar (Balances/
   Vote Head Balances/Cashbook/Trial Balance/Opening Balances) wraps into
   rows instead of scrolling sideways like the generic .fin-tabs bars with
   many buttons (e.g. the top-level Finance hub nav) still do — opted in
   per-screen via this extra class rather than changing .fin-tabs itself,
   so nothing else that relies on the scroll-strip behaviour is affected. */
.fin-tabs.wrap-tabs{flex-wrap:wrap}
@media screen and (max-width:960px){
  .fin-tabs.wrap-tabs{overflow-x:visible}
  .fin-tabs.wrap-tabs button{flex:1 1 30%;min-width:90px;white-space:normal}
}

/* Design standard rollout round 3 — Exam Desk (approved sketch, "Design 4"
   accordion): border accent added to every exam card (side-accent/tile-*,
   cycled per exam like Reports/Classes/Subjects already do), the two
   icon-heavy secondary actions (Learning Area Papers, Subject Combination)
   plus "+ Add classes" moved off the crowded title row into their own
   toolbar strip, and — mobile screens only, desktop table and printing
   left exactly as they were — every class is rendered as a collapsed
   accordion row instead of a horizontal table, tapping one open to reveal
   its actions. */
.ed-toolbar{display:flex;gap:8px;flex-wrap:wrap;padding:10px 16px;border-bottom:1px solid var(--line)}
.ed-desktop-view{display:block}
.ed-mobile-view{display:none}
@media screen and (max-width:960px){
  .ed-desktop-view{display:none}
  .ed-mobile-view{display:block}
}
@media print{
  .ed-desktop-view{display:block !important}
  .ed-mobile-view{display:none !important}
}
.ed-m-head{padding:14px;position:relative}
.ed-m-editrow{position:absolute;top:14px;right:14px;display:flex;gap:6px}
.ed-m-editrow button{width:32px;height:32px;border:1.5px solid var(--line);border-radius:7px;background:#fff}
.ed-m-name{font-size:16px;font-weight:800;margin:0 76px 6px 0}
.ed-m-badges{display:flex;gap:6px;flex-wrap:wrap}
.ed-m-toolbar{display:flex;gap:8px;padding:12px 14px 0}
.ed-m-toolbar button{flex:1;padding:9px 6px;border:1.5px solid var(--line);border-radius:8px;background:#fff;font-size:11px;font-weight:700;color:#334155}
.ed-m-acc{border-top:1px solid var(--line);margin-top:12px}
.ed-m-acc-head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;cursor:pointer}
.ed-m-acc-head.static{cursor:default}
.ed-m-acc-name{font-weight:700;font-size:14px}
.ed-m-acc-meta{font-size:11px;color:var(--muted)}
.ed-m-acc-body{display:none;padding:0 14px 12px}
.ed-m-acc.open .ed-m-acc-body{display:block}
.ed-m-cgrid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.ed-m-cgrid button,.ed-m-full{padding:9px;border:1.5px solid var(--line);border-radius:8px;background:#fff;font-weight:700;font-size:12px}
.ed-m-full{width:100%}

/* Design standard rollout round 3 continued — Exam Desk's own sub-panels
   (Review & Publish / Marks Entry / Bulk Upload), approved sketches: a
   shared bordered "icon-chip" button used instead of the plain ghost .btn
   for these panels' actions. */
.icon-chip{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 12px;border:1.5px solid var(--line);border-radius:8px;background:#fff;cursor:pointer;font-size:13px;font-weight:600;color:#334155}
.icon-chip:hover{background:#f8fafc}
.icon-chip.primary{background:#0f172a;color:#fff;border-color:#0f172a}
.icon-chip.primary:hover{background:#1e293b}
.icon-chip.danger{border-color:#fecaca;color:#b91c1c}
.icon-chip[disabled]{opacity:.5;cursor:default}

/* Review & Publish — mobile-only view (approved "Design A", consolidated
   card): one bordered card holds everything with thin dividers + section
   labels instead of several stacked, separately-bordered cards; no
   explanatory hint text next to the Publish button (approved feedback: an
   admin already knows what "Publish Results" does). Reuses the exact same
   .ed-m-acc accordion the Exam Desk board uses for its subject list. */
.rp-m-top{padding:14px;display:flex;justify-content:flex-end}
.rp-m-strip{margin:0 14px 4px;padding:10px 12px;border-radius:8px;font-size:12px;display:flex;align-items:center;justify-content:space-between;gap:8px}
.rp-m-strip.warn{background:#fffbeb;border:1px solid #fde68a}
.rp-m-strip.ok{background:#f0fdf4;border:1px solid #bbf7d0}
.rp-m-strip a{font-weight:700;text-decoration:none;white-space:nowrap}
.rp-m-strip.warn a{color:#b45309}
.rp-m-strip.ok a{color:#15803d}

/* Marks Entry — mobile-only view (approved "Design 3", compact two-line
   list): no Grade column (feedback: the grade is only meaningful once a
   grading scale finalizes it, so showing one at raw entry time is
   misleading) — score input only, name+admission stacked in one column. */
.mk-m-head{padding:12px 14px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.mk-m-name{font-weight:800;font-size:15px}
.mk-m-row{padding:10px 14px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px}
.mk-m-num{width:20px;color:var(--muted);font-size:12px;flex:0 0 auto}
.mk-m-info{flex:1;min-width:0}
.mk-m-info .name{font-weight:700;font-size:13px}
.mk-m-info .adm{font-size:11px;color:var(--muted)}
.mk-m-score{width:72px;padding:9px 4px;border:1.5px solid var(--line);border-radius:8px;text-align:center;font-weight:700;font-size:15px;flex:0 0 auto}

/* ===========================================================================
   Messaging_Overhaul.docx — Compose recipient picker (item 5), separated
   To/Message blocks (item 10), exam-results template + toggle (items 3 & 6),
   and the redesigned SMS History table + detail view (items 7 & 8). Mobile
   treatment (item 11) reuses Exam Desk's own .ed-desktop-view/.ed-mobile-view
   breakpoint pattern under new .mh-* names, so History collapses to cards at
   the same width Exam Desk already does.
   =========================================================================== */
.compose-block{margin-bottom:14px}
.compose-block h4{margin:0 0 12px;font-size:12.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:.04em;color:var(--muted)}

.rp-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px}
@media (max-width:760px){.rp-grid{grid-template-columns:repeat(3,1fr)}}
@media (max-width:480px){.rp-grid{grid-template-columns:repeat(2,1fr)}}
/* Compose review round: no emoji, each tile keeps its own accent color
   (--c, set inline per RECIPIENT_TYPES entry) permanently, not just when
   selected — selection is shown by thickening that SAME border only,
   never by changing the fill or text color. */
.rp-opt{border:1.5px solid var(--c, var(--line));border-radius:var(--radius-sm);padding:16px 8px;text-align:center;
  cursor:pointer;transition:border-width .1s;background:#fff;display:flex;align-items:center;justify-content:center;min-height:52px}
.rp-opt.sel{border-width:2.5px;padding:14.5px 8px}
.rp-opt .lab{font-size:12px;font-weight:650;line-height:1.25}
.rp-inline{margin-top:14px;border-top:1px dashed var(--line);padding-top:14px}
.rp-inline:empty{display:none;margin-top:0;border-top:none;padding-top:0}

.charcount{text-align:right;font-size:11.5px;color:var(--muted);margin-top:6px;font-variant-numeric:tabular-nums}
.charcount b{color:var(--ink)}
.charcount.over b{color:var(--danger)}

/* Compose "send a copy to" — a real second SMS batch to picked staff, not
   a silent bcc. Multi-select as chips: picking a name ADDS them below
   without removing anyone already added; each chip's × removes only that
   one person. */
.send-row{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
.copy-to-wrap{display:flex;flex-direction:column;gap:6px;align-items:flex-start}
.copy-to-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.copy-to-top label{font-size:12.5px;color:var(--ink);cursor:pointer}
.copy-to-top input[type=checkbox]{width:15px;height:15px;accent-color:var(--primary)}
.copy-to-top select{border:1px solid var(--line);border-radius:7px;padding:6px 8px;font-size:12.5px;font-family:inherit;color:var(--ink);background:#fff}
.copy-to-top select:disabled{color:var(--muted);background:#f5f6f8}
.chips{display:flex;flex-wrap:wrap;gap:6px}
.chip{display:flex;align-items:center;gap:6px;background:#eef1f5;border:1px solid var(--line);border-radius:99px;padding:3px 6px 3px 10px;font-size:12px}
.chip button{border:none;background:none;cursor:pointer;color:var(--muted);font-size:14px;line-height:1;padding:2px;border-radius:50%}
.chip button:hover{color:var(--danger);background:#fdecec}

.mode-toggle{display:flex;border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;width:fit-content}
.mode-toggle button{border:none;background:#fff;padding:8px 14px;font-size:12.5px;font-weight:650;
  color:var(--muted);cursor:pointer;font-family:inherit}
.mode-toggle button.on{background:var(--primary);color:#fff}

.sms-preview{background:#0b1013;border-radius:12px;padding:14px 16px;color:#e9f3ef;font-size:12.5px;
  line-height:1.55;font-family:ui-monospace,'SFMono-Regular',Menlo,monospace;max-width:360px;white-space:pre-wrap}
.sms-preview b{color:#7fd9b6;letter-spacing:.02em}

.processed-toast{display:flex;align-items:center;gap:10px;background:var(--ok-bg);color:#1c7a52;
  font-size:13px;font-weight:600}
.processed-toast .ico{width:20px;height:20px;border-radius:50%;background:var(--ok);color:#fff;
  display:flex;align-items:center;justify-content:center;font-size:12px;flex-shrink:0}

/* ---- SMS History (items 7 & 8) ---- */
.mh-desktop-view{display:block}
.mh-mobile-view{display:none}
@media screen and (max-width:860px){
  .mh-desktop-view{display:none}
  .mh-mobile-view{display:block}
}
.mh-card{padding:14px 16px;border-bottom:1px solid var(--line);cursor:pointer}
.mh-card:last-child{border-bottom:none}
.mh-card:active{background:var(--bg)}
.hist-pager{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 16px;border-top:1px solid var(--line)}
.mh-card-top{display:flex;justify-content:space-between;align-items:flex-start;gap:10px}
.mh-card-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-top:12px}
.mh-card-stats div{text-align:center;background:var(--bg);border-radius:8px;padding:8px 4px}
.mh-card-stats b{display:block;font-size:15px}
.mh-card-stats span{display:block;font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.03em;margin-top:2px}

.mh-detail-meta{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:16px}
.mh-detail-meta div{background:var(--bg);border-radius:var(--radius-sm);padding:10px 12px}
.mh-detail-meta span{display:block;font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.03em}
.mh-detail-meta b{font-size:17px}
@media (max-width:560px){.mh-detail-meta{grid-template-columns:repeat(2,1fr)}}

/* ---------------------------------------------------------------------
   #seo-landing (index.html) — static pre-JS marketing content. Normally
   removed by app.js before first paint (see init()), but this CSS also
   makes it look like a real, finished page in the rare case JS fails to
   run at all, rather than leaving visitors staring at a blank body.
   --------------------------------------------------------------------- */
#seo-landing{max-width:760px;margin:0 auto;padding:32px 20px 60px}
.sl-top{margin-bottom:28px}
.sl-brand{font-size:19px;font-weight:800;color:var(--brand);display:flex;align-items:center;gap:8px}
.sl-logo{font-size:22px}
.sl-hero h1{font-size:28px;line-height:1.25;color:var(--ink);margin:0 0 10px}
.sl-sub{font-size:15px;color:var(--muted);line-height:1.6;max-width:640px}
.sl-features{margin-top:30px;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:22px 24px;box-shadow:var(--shadow)}
.sl-features h2{font-size:16px;margin:0 0 12px;color:var(--ink)}
.sl-features ul{margin:0;padding-left:18px}
.sl-features li{margin-bottom:9px;font-size:13.5px;color:var(--ink);line-height:1.55}
.sl-cta{margin-top:26px;font-size:13.5px;color:var(--muted)}
.sl-cta a{color:var(--primary);font-weight:600;text-decoration:none}
.sl-cta a:hover{text-decoration:underline}
.sl-foot{margin-top:40px;font-size:12px;color:var(--muted)}
