/* ==================================================================
 * The Classroom Management System's stylesheet, used as-is.
 *
 * Not "inspired by" and not re-derived: this is the same file that
 * dresses the CMS in OneDrive\Documents\Classroom Management System,
 * copied whole so the two systems are the same system to look at. The
 * only edit is the mask-image paths, which lose their "assets/" prefix
 * because this file lives inside assets/ rather than beside it.
 *
 * Anything csms needs and the CMS has no equivalent for is added at the
 * bottom, under "csms only", written in the same idiom. Keep it that
 * way: if a rule could belong upstream, put it upstream.
 * ================================================================== */

/* ==================================================================
   Classroom Management System — Cream / Olive / Terracotta
   Flat, page-based layout: no card boxes, no nested panels.

   Three families, each with one job:
     --brand-*   deep olive green — structure: bars, headers, primary actions
     --accent-*  terracotta       — emphasis: highlights, warnings, destructive
     --sand-*    warm cream       — surfaces, borders, soft fills
   Terracotta is used sparingly on purpose; that restraint is what keeps the
   scheme calm even though the colour itself is saturated.
   ================================================================== */

:root {
  --brand-900: #262F17;
  --brand-800: #333F1E;
  --brand-700: #435228;   /* the headline olive */
  --brand-600: #556634;
  --brand-100: #D8E0C7;
  --brand-050: #EDF1E3;

  --accent-700: #8E2F1A;
  --accent-600: #AC3B21;
  --accent-500: #C8492C;  /* the terracotta */
  --accent-400: #DD6B47;
  --accent-100: #F4D8CD;
  --accent-050: #FBEAE3;

  --sand-300: #CDBC9B;
  --sand-200: #E0D3B9;
  --sand-100: #EDE3CE;
  --sand-050: #F6F0E2;

  /* Inverted: the page is the light cream and the things sitting on it —
     the breadcrumb bar, cards, attendance rows — are the warmer sand.
     `--field` keeps inputs and small controls light so they still read as
     wells against those sand panels.
     `--white` is true white, used only for text on olive or terracotta. */
  --canvas: #FAF5EA;
  --surface: #EBE1CF;
  --surface-alt: #E2D7C0;
  --field: #FDFAF3;
  /* Attendance rows deliberately share the breadcrumb bar's colour — bound to
     the same value rather than copied, so the two can never drift apart. */
  --att-card: var(--surface);
  --white: #FFFFFF;

  --ink: #2B3020;
  --ink-soft: #4E4F39;   /* clears 4.5:1 even on the deepest sand panel */
  --rule: #DBCEB2;

  --radius: 6px;
}

* { box-sizing: border-box; }

html, body { height: 100%; margin: 0; }

body {
  background: var(--canvas);
  color: var(--ink);
  font: 15px/1.5 "Segoe UI", system-ui, -apple-system, Roboto, Arial, sans-serif;
  display: flex;
  flex-direction: column;
}

h1, h2 { margin: 0; font-weight: 700; letter-spacing: -.015em; }

a { color: inherit; text-decoration: none; }

/* Present to assistive tech, invisible on screen. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------------- Top bar ---------------- */

/* Circuit-trace texture over the olive gradient — inline SVG so it stays
   self-contained and offline. Kept near-transparent so it reads as texture
   rather than decoration competing with the wordmark. */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.15rem 2rem;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='60' viewBox='0 0 120 60'%3E%3Cg fill='none' stroke='%23ffffff' stroke-opacity='.10' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M0 18h30l10-10h32'/%3E%3Cpath d='M72 8v26h20l10 10h18'/%3E%3Cpath d='M0 46h22l8-8'/%3E%3Cpath d='M46 60v-14l8-8h18'/%3E%3Cpath d='M96 0v10'/%3E%3Cpath d='M108 22h12'/%3E%3C/g%3E%3Cg fill='%23ffffff' fill-opacity='.14'%3E%3Ccircle cx='30' cy='18' r='2'/%3E%3Ccircle cx='72' cy='34' r='2'/%3E%3Ccircle cx='22' cy='46' r='2'/%3E%3Ccircle cx='96' cy='10' r='2'/%3E%3Ccircle cx='108' cy='22' r='2'/%3E%3C/g%3E%3Cg fill='none' stroke='%23ffffff' stroke-opacity='.12'%3E%3Crect x='52' y='11' width='8' height='8' rx='1.5'/%3E%3Crect x='100' y='40' width='8' height='8' rx='1.5'/%3E%3C/g%3E%3C/svg%3E"),
    linear-gradient(100deg, var(--brand-900) 0%, var(--brand-700) 60%, var(--brand-600) 100%);
  background-repeat: repeat, no-repeat;
  border-bottom: 3px solid var(--accent-500);
  color: var(--white);
  flex: 0 0 auto;
}

/* Icon-only, pinned to the far right of the bar. */
.topbar-nav {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: var(--radius);
  border: 1px solid rgba(255, 255, 255, .28);
  color: var(--white);
  transition: background .12s, border-color .12s;
}

.topbar-nav:hover { background: rgba(255, 255, 255, .13); border-color: var(--accent-400); }
.topbar-nav.is-current { background: var(--accent-500); border-color: var(--accent-600); }
.topbar-nav:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 2px; }

.brand { display: flex; align-items: center; gap: .8rem; margin-right: auto; }

/* The seal carries its own ring and edge, so it needs no frame around it. */
.brand-mark {
  width: 54px;
  height: 54px;
  flex: 0 0 auto;
  object-fit: contain;
}

.brand-name { display: block; font-size: 1.12rem; font-weight: 700; }

.brand-sub {
  display: block;
  font-size: .7rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--sand-200);
}

/* ---------------- Breadcrumbs ---------------- */

/* The address bar takes every pixel the two button clusters don't, the way a
   browser's does. The clusters are near enough the same width that this leaves
   it visually centred as well as full-width. */
.crumbs {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 1.15rem;
  /* Deliberately tighter than the page's 2rem gutter: the outer buttons hug
     the window edge the way a browser toolbar's do, rather than lining up
     with the content below. */
  padding: .5rem .75rem;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}

/* Home / back / forward sit together as one cluster, browser-fashion. */
.navbtns {
  display: flex;
  align-items: center;
  gap: .1rem;
  justify-self: start;
}

/* Tools live at the far right, mirroring the nav cluster. */
.crumb-actions {
  display: flex;
  align-items: center;
  gap: .25rem;
  justify-self: end;
}

/* Sized to the address bar's own height so the whole row reads as one band. */
.navbtn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--brand-700);
  cursor: pointer;
  transition: background .12s;
}

.navbtn:hover:not(:disabled) { background: var(--brand-100); }
.navbtn:active:not(:disabled) { background: var(--brand-050); }
/* Greyed when there is nowhere to go, the way a browser greys its arrows. */
.navbtn:disabled { opacity: .3; cursor: default; }
.navbtn:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 1px; }
.navbtn.is-current { background: var(--accent-500); color: var(--white); }

/* The supplied artwork is black PNG, so it is used as a mask rather than an
   image: the colour comes from currentColor, which means hover, focus and
   is-current all recolour the icon for free and nothing is hard-coded. */
.navbtn-img {
  width: 19px;
  height: 19px;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}

.i-home       { -webkit-mask-image: url("home.png");       mask-image: url("home.png"); }
.i-database   { -webkit-mask-image: url("database.png");   mask-image: url("database.png"); }
.i-attendance { -webkit-mask-image: url("attendance.png"); mask-image: url("attendance.png"); }
.i-noclass    { -webkit-mask-image: url("no-class.png");   mask-image: url("no-class.png"); }
.i-list       { -webkit-mask-image: url("list.png");       mask-image: url("list.png"); }
.i-calendar   { -webkit-mask-image: url("calendar.png");   mask-image: url("calendar.png"); }
.i-table      { -webkit-mask-image: url("table.png");      mask-image: url("table.png"); }
.i-classes    { -webkit-mask-image: url("Classes.png");    mask-image: url("Classes.png"); }

/* ---------------- Current Classes (a day's timetable) ---------------- */

/* --- Day / full-schedule switch. A control, so it may carry a fill. --- */

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

.viewswitch-btn {
  padding: .3rem .75rem .34rem;
  border: 1px solid var(--brand-600);
  background: none;
  font: inherit;
  font-size: .78rem;
  font-weight: 700;
  color: var(--brand-700);
  cursor: pointer;
  transition: background .12s, color .12s;
}

/* Jump to one date, sitting inline in the toolbar beside the record count it
   narrows rather than claiming a band of its own. */
.datefilter {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
}

.datefilter input[type="date"] {
  width: auto;
  min-width: 9.5rem;
  min-height: 0;
  padding: .28rem .45rem;
  font-size: .8rem;
}

/* --- Import attendance: the picked file's details sit below a rule, so the
       dialog reads as "choose a file" then "here is what it holds". --- */

#importAttResult {
  margin-top: 1rem;
  padding-top: .95rem;
  border-top: 1px solid var(--rule);
}

.import-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .25rem 1rem;
}

.import-head-class { font-size: .95rem; font-weight: 800; color: var(--brand-800); }
.import-head-meta { font-size: .8rem; color: var(--ink-soft); }

.import-mode {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .7rem;
  margin-top: .95rem;
}

#importAttRows { margin-top: .9rem; }

/* One button, a short chooser under it. */
.datamenu-wrap { position: relative; display: flex; }

.datamenu {
  position: absolute;
  top: calc(100% + .35rem);
  right: 0;
  z-index: 40;
  min-width: 12rem;
  display: flex;
  flex-direction: column;
  gap: .1rem;
  padding: .25rem;
  background: var(--canvas);
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  box-shadow: 0 10px 26px rgba(43, 48, 32, .16);
}

.datamenu-item {
  padding: .45rem .6rem;
  border: 0;
  border-radius: var(--radius);
  background: none;
  font: inherit;
  font-size: .82rem;
  font-weight: 600;
  color: var(--brand-700);
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
  transition: background .12s;
}

.datamenu-item:hover { background: var(--brand-050); }
.datamenu-item:focus-visible { outline: 3px solid var(--accent-400); outline-offset: -2px; }

/* Matched to the view switch beside it, so the pair reads as one row of
   controls rather than a button parked next to some icons. */
#exportBtn {
  height: 30px;
  padding: 0 .7rem;
  font-size: .78rem;
  border-radius: var(--radius);
}

/* Icon-only variant: the mask takes currentColor, so the active state
   recolours the artwork along with the button. */
.viewswitch-btn.is-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 30px;
  padding: 0;
}

.viewswitch-btn:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.viewswitch-btn:last-child  { border-radius: 0 var(--radius) var(--radius) 0; border-left: 0; }
.viewswitch-btn:hover:not(.is-on) { background: var(--brand-050); }
.viewswitch-btn.is-on { background: var(--brand-700); color: var(--white); }
.viewswitch-btn:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 1px; }

.toolbar-spacer { flex: 1 1 auto; }

/* --- Term chips: prelim / midterm / finals, six weeks each. --- */

/* Its own line above the register, so it reads as the register's control
   rather than another thing wedged beside the view switch. */
.termrow {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-top: 1.1rem;
  padding-bottom: .6rem;
  border-bottom: 1px solid var(--rule);
}

/* Classworks puts outlined cards or a bordered grid straight underneath, so
   the rule would be a second edge saying the same thing. The register keeps
   its rule, because a bare table needs the anchor. */
#panelClassworks .termrow { border-bottom: 0; }

/* In table view the view switch moves into this row. It takes no margin of its
   own: the search already carries margin-left:auto, and a second auto would
   split the free space between them and leave the search stranded mid-row. */
#panelClassworks .termrow .viewswitch { flex: 0 0 auto; }
/* With no search to push it (a term with no register), the switch still goes
   to the end. */
#panelClassworks .termrow #cwSearch[hidden] ~ .viewswitch { margin-left: auto; }

/* Its own line, ending where the view switch above it ends. */
.cw-export-row { display: flex; justify-content: flex-end; margin-top: .55rem; }

/* 30px, matching the view switch and the search field beside it, so the row
   reads level rather than nearly level. */
.btn.is-tall { height: 30px; padding-top: 0; padding-bottom: 0; font-size: .76rem; }

.termrow-label {
  font-size: .66rem;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.termfilter { display: flex; gap: .25rem; }

.termrow-hint { font-size: .78rem; font-style: italic; color: var(--ink-soft); }

/* Sits at the far end of the term row, matching the roster's search field. */
.register-search {
  margin-left: auto;
  width: auto;
  min-width: 15rem;
  max-width: 22rem;
  min-height: 0;
  padding: .32rem .55rem;
  font-size: .82rem;
}

.termchip {
  padding: .28rem .6rem .32rem;
  border: 1px solid var(--brand-600);
  border-radius: var(--radius);
  background: none;
  font: inherit;
  font-size: .74rem;
  font-weight: 700;
  color: var(--brand-700);
  white-space: nowrap;
  cursor: pointer;
  transition: background .12s, color .12s;
}

/* Sits beside the chips but is an action, not a fourth term: same metrics so
   the row reads level, with a gap so the group of three stays a group. */
#gsOpenBtn {
  margin-left: .55rem;
  padding: .28rem .7rem .32rem;
  font-size: .74rem;
  /* The chips take the page's line height; .btn pins its own, which left this
     one a couple of pixels short of them. */
  line-height: inherit;
}

/* It toggles, so it has to show which way it is set — the same filled state
   the term chips and the view switch use. */
#gsOpenBtn.is-on {
  background: var(--brand-700);
  border-color: var(--brand-800);
  color: var(--white);
}

.termchip:hover:not(.is-on):not(:disabled) { background: var(--brand-050); }
.termchip.is-on { background: var(--brand-700); color: var(--white); }
.termchip:disabled { opacity: .35; cursor: default; }
.termchip:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 1px; }

/* --- Calendar view --- */

.cal-month { margin-top: 1.3rem; }

/* Month name between its two arrows, centred over the grid. */
.cal-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .9rem;
  margin-bottom: .7rem;
}

.cal-title { margin: 0; font-size: .95rem; font-weight: 800; color: var(--brand-800); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: .3rem; }

.cal-head {
  padding-bottom: .2rem;
  font-size: .66rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-align: center;
}

.cal-cell {
  min-height: 3.5rem;
  padding: .3rem .35rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: none;
  font: inherit;
  text-align: left;
  color: var(--ink-soft);
}

.cal-cell.is-blank { border-color: transparent; }
.cal-daynum { display: block; font-size: .8rem; font-weight: 700; }
.cal-tally { display: block; margin-top: .15rem; font-size: .65rem; font-weight: 700; }

/* A day the class doesn't meet stays quiet. */
.cal-cell.is-off { opacity: .4; }

/* A day it meets but has no record: outlined, still clearly a school day. */
.cal-cell.is-meets { border-color: var(--brand-600); color: var(--brand-700); }

/* A day with a real roll call: filled, so it carries at a glance. */
.cal-cell.is-taken {
  background: var(--brand-700);
  border-color: var(--brand-800);
  color: var(--white);
  cursor: pointer;
}
.cal-cell.is-taken .cal-daynum { color: var(--white); }
.cal-cell.is-taken:hover { background: var(--brand-600); }

.cal-cell.is-noclass {
  background: var(--accent-500);
  border-color: var(--accent-600);
  color: var(--white);
  cursor: pointer;
}
.cal-cell.is-noclass .cal-daynum { color: var(--white); }
.cal-cell.is-noclass:hover { background: var(--accent-600); }
.cal-cell:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 1px; }

.cal-legend {
  display: flex; flex-wrap: wrap; gap: .9rem;
  margin-top: 1.1rem; font-size: .73rem; color: var(--ink-soft);
}
.cal-key { display: inline-flex; align-items: center; gap: .35rem; }
.cal-swatch {
  display: inline-block; width: 12px; height: 12px;
  border-radius: 3px; border: 1px solid var(--rule);
}
.cal-swatch.is-taken   { background: var(--brand-700); border-color: var(--brand-800); }
.cal-swatch.is-noclass { background: var(--accent-500); border-color: var(--accent-600); }
.cal-swatch.is-meets   { border-color: var(--brand-600); }
.cal-swatch.is-off     { opacity: .4; }

/* --- Register view: students down, dates across. --- */

/* Layout only: the register sits at the page colour and is held together by
   its rules, not by filled rows. Doubled class so these beat the base .grid
   fills, which are declared later in the file. */
.grid.register-grid { background: none; }
.grid.register-grid tbody tr { background: none; }
.grid.register-grid tbody tr:hover { background: var(--brand-050); }
.grid.register-grid tbody td { border-bottom: 1px solid var(--sand-300); }

/* Fixed layout: the name and total columns hold their stated widths and the
   dates share whatever is left, so the totals always finish at the right edge
   and the date columns are what widen or narrow as a term fills up. */
.grid.register-grid { width: 100%; table-layout: fixed; }

/* The olive band stays; only the body rows drop their fill. */
.grid.register-grid thead th {
  vertical-align: bottom;
  padding: .45rem .3rem .5rem;
}

/* Wide enough for the longest surname, and no wider — so the centred label
   sits over the names instead of over the dates as well. min-width matters as
   much as width: once a term fills with dates the browser will otherwise
   squeeze this column and the names start colliding with the marks. */
.grid.register-grid th.reg-name-head,
.grid.register-grid td.primary-cell {
  width: 17rem;
  min-width: 17rem;
}

.grid.register-grid th.reg-name-head {
  vertical-align: middle;
  text-align: center;
  padding-left: .75rem;
  padding-right: .75rem;
}

/* Names read as a list, not as headings — the marks are what carry weight. */
.grid.register-grid td.primary-cell {
  font-weight: 500;
  color: var(--ink);
}

/* The name column stays put while the dates scroll under it. It needs its own
   background because the body rows are deliberately unfilled — without one the
   scrolling marks would show straight through the names. */
.grid.register-grid th.reg-name-head,
.grid.register-grid td.primary-cell {
  position: sticky;
  left: 0;
  z-index: 2;
  border-right: 1px solid var(--sand-300);
}

.grid.register-grid td.primary-cell { background: var(--canvas); z-index: 1; }
.grid.register-grid tbody tr:hover td.primary-cell { background: var(--brand-050); }

/* Body type is smaller than the rest of the app's tables so a long term fits.
   Scoped to tbody on purpose: the headers keep their own size. */
.grid.register-grid tbody td { font-size: .76rem; }
.grid.register-grid td.primary-cell { font-size: .78rem; }
.grid.register-grid td.mk { font-size: .74rem; }
.grid.register-grid td.reg-total { font-size: .74rem; }

/* Upright headers keep the date and total columns narrow. */
.upright {
  display: inline-block;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  white-space: nowrap;
  font-size: .68rem;
  letter-spacing: .02em;
}

/* Named reg-total, not tally: ".tally" is the pill used by the attendance day
   rows, and reusing it here dressed every total column as a lozenge. */
/* Dates carry no width, so fixed layout hands them the leftover space. */
.register-grid th.mk-head { text-align: center; }
.register-grid th.reg-total { width: 2.375rem; text-align: center; }
.register-grid td.mk { text-align: center; font-weight: 800; font-size: .8rem; padding: .4rem .25rem; }
.register-grid td.reg-total {
  text-align: center;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  padding: .4rem .25rem;
}
.register-grid .sep { border-left: 2px solid var(--sand-300); }

/* One colour per status, so a row reads without checking the letter. */
.register-grid .mk-P { color: var(--brand-600); }
.register-grid .mk-L { color: var(--accent-500); }
.register-grid .mk-E { color: var(--brand-800); }
.register-grid .mk-A { color: var(--accent-700); }
.register-grid .mk-x { color: var(--sand-300); }

/* A day the class did not meet. Tinted down its whole length so the column
   reads as one cancelled date, rather than as a column of blanks that happen
   to look like dates nobody has marked. Terracotta is what "no class" wears
   everywhere else in the app. */
.register-grid .mk-nc {
  color: var(--accent-700);
  background: var(--accent-050);
  font-size: .68rem;
  letter-spacing: .02em;
}

.register-grid th.mk-head.is-noclass {
  background: var(--accent-600);
  border-bottom-color: var(--accent-700);
}

/* Tap it and it says why. Underlined rather than made to look like a button,
   because it still has to read as a register cell. */
.register-grid .is-tappable { cursor: pointer; }
.register-grid td.is-tappable:hover { background: var(--accent-100); }
.register-grid td.is-tappable > * , .register-grid td.is-tappable { text-decoration-line: underline; text-decoration-style: dotted; text-underline-offset: 2px; }
.register-grid th.is-tappable:hover { background: var(--accent-700); }
.register-grid .is-tappable:focus-visible { outline: 3px solid var(--accent-400); outline-offset: -3px; }

/* --- The week as a grid: classes down, days across. --- */

.table-scroll { overflow-x: auto; }

/* Fixed layout so the eight columns divide the width between them instead of
   growing past it — the whole week has to fit without a sideways scroll. */
.schedule-grid { table-layout: fixed; width: 100%; font-size: .8rem; }
.schedule-grid th, .schedule-grid td { padding: .45rem .4rem; }
.schedule-grid td { vertical-align: top; }
.schedule-grid .sched-cell { min-width: 0; }
.schedule-grid th:first-child, .schedule-grid td:first-child { width: 6.5rem; }
.schedule-grid .sched-stack { display: flex; flex-direction: column; gap: .25rem; }
.schedule-grid .sched-sub { font-size: .76rem; font-weight: 400; color: var(--ink-soft); }

/* Today's column is the one you are most likely looking for. */
.schedule-grid th.is-today { background: var(--accent-600); }
.schedule-grid td.is-today { background: var(--brand-050); }
.schedule-grid tbody tr:hover td.is-today { background: var(--brand-100); }

.schedule-grid .is-free { color: var(--ink-soft); }

/* A class sitting in a time slot. */
.sched-class {
  display: block;
  padding: .22rem .4rem .26rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--field);
  min-width: 0;
}

.sched-class-code { display: block; font-size: .74rem; font-weight: 700; color: var(--brand-800); }
.sched-class-name {
  display: block;
  font-size: .66rem;
  line-height: 1.25;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}

/* ---------------- Classworks ---------------- */

.cw-list { display: flex; flex-direction: column; gap: .55rem; }

/* Each activity stands on its own card — page colour inside a hairline
   outline, so the separation comes from the rule and the gap, not a fill. */
.cw-card {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: .8rem 1rem;
  border: 1px solid var(--sand-300);
  border-radius: 10px;
  background: var(--canvas);
  cursor: pointer;
  transition: border-color .12s, background .12s;
}

.cw-card:hover { background: var(--field); border-color: var(--brand-600); }
.cw-card:focus-visible { outline: 3px solid var(--accent-400); outline-offset: -3px; }

.cw-main { flex: 1 1 auto; min-width: 0; }
.cw-title { margin: 0; font-size: .98rem; font-weight: 700; color: var(--brand-800); }

.cw-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .2rem 1rem;
  margin: .3rem 0 0;
  font-size: .78rem;
  color: var(--ink-soft);
}

.cw-bit { display: inline-flex; align-items: center; gap: .3rem; }
.cw-bit svg { flex: 0 0 auto; opacity: .8; }
.cw-bit.is-done { color: var(--brand-600); font-weight: 600; }
/* Terracotta only where it means something: this one is the term's exam. */
.cw-bit.is-exam { color: var(--accent-700); font-weight: 600; }
.cw-bit.is-done svg, .cw-bit.is-exam svg { opacity: 1; }

.cw-actions { display: flex; align-items: center; gap: .15rem; flex: 0 0 auto; }

.cw-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background .12s, color .12s;
}

.cw-iconbtn:hover { background: var(--brand-050); color: var(--brand-700); }
.cw-iconbtn.is-danger:hover { background: var(--accent-050); color: var(--accent-700); }
.cw-iconbtn:focus-visible { outline: 3px solid var(--accent-400); outline-offset: -2px; }

/* The way in, at the end of the list — the school-year tile's dashed
   terracotta edge, laid out as a row rather than a square. */
.cw-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .9rem 1rem;
  border: 2px dashed var(--accent-400);
  border-radius: 10px;
  background: var(--canvas);
  font: inherit;
  font-size: .92rem;
  font-weight: 700;
  color: var(--brand-700);
  cursor: pointer;
  transition: border-color .15s, background .15s;
}

.cw-add:hover, .cw-add:focus-visible {
  background: var(--sand-100);
  border-color: var(--accent-600);
  outline: none;
}

.cw-add-plus { font-size: 1.35rem; line-height: 1; font-weight: 400; }

/* Score sheet: a number box and an excused tick per student. */
.cw-score-input {
  width: 4.5rem;
  min-height: 0;
  padding: .25rem .4rem;
  font-size: .85rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.cw-score-input:disabled { opacity: .4; }
.cw-out-of { font-size: .78rem; color: var(--ink-soft); white-space: nowrap; }

/* A select sits taller than a baseline-aligned row expects, so the score sheet
   centres its rows where the import list does not. */
#cwScoreRows .import-row { align-items: center; }
#cwScoreRows .imp-name { font-weight: 600; }
#cwScoreRows .import-row.is-excused .imp-name { color: var(--ink-soft); }

.cw-status {
  flex: 0 0 auto;
  width: auto;
  min-width: 7.5rem;
  min-height: 0;
  padding: .25rem .4rem;
  font-size: .8rem;
}

/* ---------------- Marking against criteria ----------------
   The class down one side, one student's criteria on the other. Six boxes a
   student will not fit on a row beside a name, and a criterion is worth
   nothing to mark against if you cannot read what it asks for. */

.cw-board {
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  gap: 1rem;
  margin-top: .9rem;
  border-top: 1px solid var(--rule);
}

.cw-board-list {
  display: flex;
  flex-direction: column;
  max-height: 26rem;
  overflow-y: auto;
  padding: .5rem .4rem 0 0;
  border-right: 1px solid var(--rule);
}

.cw-board-detail { max-height: 26rem; overflow-y: auto; padding: .8rem .2rem 0 0; }

/* --- the class list --- */

.cw-who {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: .4rem .45rem;
  border: 0;
  border-radius: var(--radius);
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background .12s;
}

.cw-who:hover { background: var(--brand-050); }
.cw-who.is-on { background: var(--brand-700); color: var(--white); }
.cw-who:focus-visible { outline: 3px solid var(--accent-400); outline-offset: -2px; }

.cw-who-mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: var(--sand-100);
  font-size: .72rem;
  font-weight: 800;
  color: var(--brand-700);
}

.cw-who.is-on .cw-who-mark { background: var(--brand-600); color: var(--white); }

.cw-who-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: .82rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cw-who-score {
  flex: 0 0 auto;
  font-size: .78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.cw-who-score.is-blank { font-weight: 400; opacity: .55; }

.cw-who-flag {
  flex: 0 0 auto;
  padding: 0 .35rem;
  border-radius: 999px;
  background: var(--sand-100);
  font-size: .62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-soft);
}

.cw-who-flag.is-absent { background: var(--accent-050); color: var(--accent-700); }
.cw-who.is-on .cw-who-flag { background: rgba(255, 255, 255, .2); color: var(--white); }

/* --- the student being marked --- */

.cw-mark-head {
  display: flex;
  align-items: flex-start;
  gap: .8rem;
  padding-bottom: .7rem;
  border-bottom: 1px solid var(--rule);
}

.cw-mark-who { flex: 1 1 auto; min-width: 0; }
.cw-mark-name { margin: 0; font-size: 1.02rem; font-weight: 800; color: var(--brand-800); }
.cw-mark-no { margin: .1rem 0 .3rem; font-size: .76rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

.cw-mark-tally { flex: 0 0 auto; text-align: right; }

.cw-mark-score {
  margin: 0;
  font-size: 1.35rem;
  font-weight: 800;
  color: var(--brand-800);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.cw-mark-score.is-over { color: var(--accent-700); }
.cw-mark-percent { margin: 0; font-size: .76rem; color: var(--ink-soft); }

/* What the students were asked to do, kept in front of whoever is marking. */
.cw-mark-brief {
  margin-top: .8rem;
  padding: .6rem .75rem;
  border-left: 3px solid var(--accent-400);
  background: var(--sand-050);
}

.cw-mark-brief-head {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  margin-bottom: .2rem;
}

.cw-mark-brief-label {
  margin: 0;
  font-size: .66rem;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent-700);
}

.cw-mark-brief-toggle {
  margin-left: auto;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: .72rem;
  font-weight: 700;
  color: var(--accent-700);
  text-decoration: underline;
  cursor: pointer;
}

.cw-mark-brief-toggle:hover { color: var(--accent-600); }
.cw-mark-brief-toggle:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 2px; }

.cw-mark-brief-text { margin: 0; font-size: .82rem; line-height: 1.45; white-space: pre-wrap; }

/* Folded to three lines — enough to recognise the brief, not enough to push
   the criteria off the screen. */
.cw-mark-brief-text.is-folded {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* --- one card per criterion --- */

.cw-crit-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: .3rem .8rem;
  margin-top: .7rem;
  padding: .65rem .75rem .7rem;
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  background: var(--canvas);
}

.cw-crit-card.is-over { border-color: var(--accent-500); }
.cw-crit-card.is-excused { opacity: .5; }

.cw-crit-text { min-width: 0; }
.cw-crit-title { margin: 0; font-size: .88rem; font-weight: 700; color: var(--brand-800); }
.cw-crit-desc { margin: .15rem 0 0; font-size: .78rem; line-height: 1.4; color: var(--ink-soft); }

.cw-crit-mark { display: flex; align-items: center; gap: .35rem; }

.cw-mark-input {
  width: 3.6rem;
  min-height: 0;
  padding: .3rem .35rem;
  font-size: .95rem;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.cw-mark-input:disabled { opacity: .4; }
.cw-crit-out { font-size: .8rem; color: var(--ink-soft); white-space: nowrap; }

/* How much of the criterion has been awarded, at a glance down the column. */
.cw-crit-bar {
  grid-column: 1 / -1;
  height: 4px;
  margin-top: .35rem;
  border-radius: 999px;
  background: var(--sand-200);
  overflow: hidden;
}

.cw-crit-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--brand-600);
  transition: width .12s;
}

.cw-crit-card.is-over .cw-crit-fill { background: var(--accent-500); }

/* Narrow screens cannot hold two panes, so the list goes above. */
@media (max-width: 720px) {
  .cw-board { grid-template-columns: minmax(0, 1fr); }
  .cw-board-list { max-height: 11rem; border-right: 0; border-bottom: 1px solid var(--rule); }
}

/* Absence explains a blank score, so it is worth a word. Being present or late
   explains nothing, and gets no mark. */
.cw-absent {
  flex: 0 0 auto;
  padding: .05rem .45rem;
  border: 1px solid var(--accent-100);
  border-radius: 999px;
  background: var(--accent-050);
  font-size: .7rem;
  font-weight: 700;
  color: var(--accent-700);
  white-space: nowrap;
}

.score-search {
  width: 100%;
  min-height: 0;
  padding: .4rem .6rem;
  font-size: .85rem;
}

/* The count sits above the search and needs no rule under it — the board
   below draws its own, and two lines there wasted a band of space. */
#cwScoreDialog .listbar {
  margin-top: .5rem;
  padding-bottom: 0;
  border-bottom: 0;
}

#cwScoreDialog .cw-board { margin-top: .6rem; }
#cwScoreDialog .import-rows { margin-top: .2rem; }

/* The term grade needs room for "100.0%", which the register's 2.4rem totals
   column wraps onto two lines. */
#cwGrid th.reg-total, #cwGrid td.reg-total { width: 4.4rem; white-space: nowrap; }

/* Pick what kind of work it is before filling anything in. */
.cw-picker { display: flex; flex-direction: column; margin-top: .6rem; }

.cw-pick {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .5rem .4rem;
  border: 0;
  border-bottom: 1px solid var(--sand-100);
  background: none;
  font: inherit;
  font-size: .92rem;
  font-weight: 600;
  color: var(--brand-800);
  text-align: left;
  cursor: pointer;
  transition: background .12s;
}

.cw-pick:hover { background: var(--brand-050); }
.cw-pick:focus-visible { outline: 3px solid var(--accent-400); outline-offset: -2px; }

/* The icon sits in a soft disc so the names line up on one edge whatever
   shape the glyph is. */
.cw-pick-disc {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  background: var(--brand-050);
  color: var(--brand-700);
}

.cw-pick-name { flex: 1 1 auto; min-width: 0; }
.cw-pick-note { font-size: .74rem; font-weight: 400; font-style: italic; color: var(--ink-soft); }

/* The chevron — last child, and the only bare svg in the row. */
.cw-pick > svg { flex: 0 0 auto; color: var(--sand-300); }
.cw-pick:hover > svg { color: var(--brand-600); }

.cw-pick.is-custom { color: var(--accent-700); border-bottom: 0; }
.cw-pick.is-custom .cw-pick-disc { background: var(--accent-050); color: var(--accent-700); }
.cw-pick.is-custom:hover { background: var(--accent-050); }

.cw-topic { margin: .18rem 0 0; font-size: .82rem; color: var(--brand-700); }
.cw-topic.is-blank { color: var(--ink-soft); opacity: .65; }
.cw-exam-check { margin-top: .3rem; }

/* Rubric editor: title, what it earns, and the marks. */
.cw-rubrics { display: flex; flex-direction: column; }

.cw-rubric-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem 0;
  border-bottom: 1px solid var(--sand-100);
}

.cw-rubric-title { flex: 0 0 11rem; min-height: 0; padding: .25rem .4rem; font-size: .85rem; }
.cw-rubric-desc { flex: 1 1 auto; min-height: 0; padding: .25rem .4rem; font-size: .85rem; }
.cw-rubric-score {
  flex: 0 0 4.5rem;
  min-height: 0;
  padding: .25rem .4rem;
  font-size: .85rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Max points is derived once a rubric exists, so it stops looking editable. */
.is-derived { background: var(--surface); color: var(--ink-soft); }

/* --- The class record, laid out as the college's own form --- */

/* Doubled class so this beats .grid.register-grid's fixed layout. Fixed would
   take its widths from the first row, which here is the banded header with a
   colspan across the class-standing columns — and everything else collapses. */
.grid.record-grid { table-layout: auto; width: 100%; font-size: .72rem; }

/* Ruled all the way through, like the form it copies: every cell has edges. */
.grid.record-grid th,
.grid.record-grid td {
  border: 1px solid var(--sand-300);
  padding: .18rem .3rem;
}

.grid.record-grid tbody td { font-size: .72rem; }
.grid.record-grid tbody td.primary-cell { font-size: .74rem; font-weight: 500; }
.grid.record-grid td.mk { font-size: .72rem; padding: .18rem .2rem; }

/* Unused columns recede — but only their body cells. The header band stays
   solid olive the whole way across, or the numbering looks half-disabled. */
.grid.record-grid tbody td.is-empty { background: var(--sand-050); }

.record-grid .rec-corner { background: none; border-bottom: 0; }

.record-grid .rec-standing {
  text-align: center;
  font-size: .74rem;
  letter-spacing: .04em;
  text-transform: none;
  white-space: normal;
  padding: .35rem .5rem;
}

.record-grid .rec-tall { width: 2.6rem; text-align: center; vertical-align: bottom; }

/* Max Score sits between the band and the numbers, the way the paper does. */
.record-grid .rec-max th {
  background: var(--surface);
  color: var(--brand-800);
  border-bottom: 1px solid var(--sand-300);
}

.record-grid .rec-maxlabel {
  text-align: right;
  font-size: .68rem;
  letter-spacing: .06em;
  padding-right: .5rem;
}

.record-grid .rec-maxval {
  text-align: center;
  font-size: .72rem;
  font-variant-numeric: tabular-nums;
}

.record-grid .rec-numbers th {
  text-align: center;
  font-size: .68rem;
  padding: .25rem .2rem .3rem;
}

.record-grid .rec-no { width: 2.2rem; min-width: 2.2rem; text-align: center; }
.record-grid .rec-num { width: 1.8rem; }

/* The register's 17rem rule keys off a different class name, so the name
   column needs its own width here or the names wrap to three lines. */
/* Capped rather than pinned, so a long roster never forces the table wider
   than the page — the name clips instead of the grid scrolling. */
.grid.record-grid th.rec-name,
.grid.record-grid td.primary-cell {
  width: 12rem;
  /* The register pins this column at 17rem; released here so max-width can
     actually bite and the record fits the page instead of scrolling. */
  min-width: 0;
  max-width: 12rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.grid.record-grid th.rec-name { text-align: center; }

.record-grid td.rec-no {
  font-size: .74rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.record-grid td.rec-val {
  text-align: center;
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The name column stays put; so does the numbering beside it. */
.record-grid td.rec-no, .record-grid th.rec-no { position: sticky; left: 0; z-index: 2; background: var(--canvas); }
.record-grid td.primary-cell, .record-grid th.rec-name { left: 2.4rem; }
.record-grid th.rec-no, .record-grid th.rec-name { background: var(--brand-800); }
.record-grid .rec-max th.rec-maxlabel { background: var(--surface); }

.cw-term-grade { font-weight: 800; color: var(--brand-800); }
.field-hint { font-weight: 400; color: var(--ink-soft); text-transform: none; letter-spacing: 0; }

/* --- Stepping across the week. Flat, on a hairline, not a boxed panel. --- */

.daynav {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-top: 1.2rem;
  padding-bottom: .7rem;
  border-bottom: 1px solid var(--rule);
}

.daynav-arrow {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  padding: 0 0 .18rem;
  border: 1px solid var(--brand-600);
  border-radius: 50%;
  background: none;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--brand-700);
  cursor: pointer;
  transition: background .12s;
}

.daynav-arrow:hover:not(:disabled) { background: var(--brand-050); }
.daynav-arrow:disabled { opacity: .3; cursor: default; }
.daynav-arrow:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 2px; }

.daynav-strip { display: flex; flex: 1 1 auto; gap: .3rem; min-width: 0; }

.daypip {
  flex: 1 1 0;
  min-width: 0;
  padding: .3rem .2rem .35rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  font: inherit;
  line-height: 1.15;
  text-align: center;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}

.daypip:hover { background: var(--surface); }
.daypip:focus-visible { outline: 3px solid var(--accent-400); outline-offset: -2px; }
.daypip.is-viewing { background: var(--surface); border-color: var(--brand-600); }

.daypip-day  { display: block; font-size: .66rem; font-weight: 800; letter-spacing: .05em;
               text-transform: uppercase; color: var(--ink-soft); }
.daypip-date { display: block; font-size: .95rem; font-weight: 800; color: var(--brand-800); }
.daypip.is-today .daypip-date { color: var(--accent-600); }

/* A dot means the day has classes; no dot means a free day. */
.daypip-dot { display: block; width: 5px; height: 5px; margin: .18rem auto 0;
              border-radius: 50%; background: var(--brand-600); }
.daypip-dot.is-free { background: none; }

/* --- One card per class. Not the banner card: that belongs to school
       years and classes. This is a worklist, so the status leads. --- */

.today-card { display: flex; flex-direction: column; text-align: left; }

.today-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .6rem;
  padding: .5rem .9rem .45rem;
  border-bottom: 1px solid var(--sand-300);
}

.today-flag { font-size: .67rem; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; }
.today-when { font-size: .74rem; font-weight: 700; color: var(--ink-soft); white-space: nowrap; }

.today-card.is-now      .today-flag { color: var(--accent-600); }
.today-card.is-upcoming .today-flag { color: var(--brand-700); }
.today-card.is-done     .today-flag { color: var(--ink-soft); }

/* The class happening right now is the one worth finding at a glance. */
.today-card.is-now { border-color: var(--accent-500); }
.today-card.is-done { opacity: .74; }
.today-card.is-done:hover { opacity: 1; }

.today-body { padding: .85rem 1rem .95rem; flex: 1 1 auto; }
.today-code { font-size: 1.02rem; font-weight: 800; color: var(--brand-800); }
.today-title { margin-top: .05rem; font-size: .85rem; color: var(--ink); }
.today-meta { margin-top: .3rem; font-size: .77rem; color: var(--ink-soft); }

.today-times { margin-top: .6rem; display: flex; flex-wrap: wrap; gap: .3rem; }
.today-slot {
  padding: .16rem .5rem .2rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--field);
  font-size: .73rem;
  font-weight: 700;
  color: var(--brand-800);
  white-space: nowrap;
}
.today-slot.is-now { border-color: var(--accent-500); color: var(--accent-700); }

/* Status on the left, the roll-call action on the right. */
.today-foot-row {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: .85rem 1rem 0;
  padding: .55rem 0 .8rem;
  border-top: 1px solid var(--sand-300);
}

.today-foot-row .btn { margin-left: auto; flex: 0 0 auto; }

.today-foot {
  min-width: 0;
  font-size: .77rem;
  font-weight: 700;
  color: var(--brand-700);
}
.today-foot.is-taken   { color: var(--ink-soft); }
.today-foot.is-noclass { color: var(--accent-700); }

/* The address bar. A control, so it takes --field like every other input. */
.crumbbar {
  width: 100%;
  min-width: 0;
  min-height: 38px;
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .4rem .9rem;
  background: var(--field);
  border: 1px solid var(--rule);
  border-radius: 10px;
}

.crumbbar-mark {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  object-fit: contain;
}

.crumbs ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: .45rem;
  font-size: .82rem;
  min-width: 0;
  /* On a narrow screen the trail scrolls rather than wrapping or truncating,
     and setCrumbs pins it to the right so "where you are" stays in view. */
  overflow-x: auto;
  scrollbar-width: none;
}

.crumbs ol::-webkit-scrollbar { display: none; }

/* The trail never wraps inside the pill; the last crumb gives way first. */
.crumbs li { display: flex; align-items: center; gap: .45rem; white-space: nowrap; }
.crumbs li:last-child { min-width: 0; }

.crumbs li + li::before {
  content: "›";
  color: var(--accent-600);
  font-weight: 700;
}

/* Only the trail links underline — the nav buttons are controls, not crumbs. */
.crumbs ol a { color: var(--brand-700); font-weight: 600; }
.crumbs ol a:hover { text-decoration: underline; }

/* Where you are, truncated rather than wrapped when the pill runs out. */
.crumbs .here {
  color: var(--ink-soft);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

.page {
  flex: 1 1 auto;
  padding: 1.6rem 2rem 3rem;
  min-height: 0;
  overflow: auto;
}

.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.25rem;
  flex-wrap: wrap;
  padding-bottom: .8rem;
  border-bottom: 3px solid var(--accent-500);
}

.page-head h1 { font-size: 1.6rem; color: var(--brand-800); }

.page-sub { margin: .25rem 0 0; font-size: .88rem; color: var(--ink-soft); }

.head-actions { display: flex; gap: .5rem; flex-wrap: wrap; }

.toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .6rem 1rem;
  padding: 1rem 0 .25rem;
}

.toolbar input[type="search"] { max-width: 26rem; }

/* Roster actions sit at the far right of the search row. */
.toolbar-actions {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-left: auto;
  flex-wrap: wrap;
}

/* With actions present the count sits beside the search instead of being
   pushed right, so only one element claims the free space. */
#panelStudents .toolbar .count { margin-left: 0; }

/* Upstream scopes that reset to one panel by id. This app has no such ids, and
   without the reset both the count and .toolbar-actions claim `margin-left:
   auto` — two auto margins split the free space between them and the count
   ends up stranded mid-row. When the count opens the bar it belongs at the
   left; when it follows a search box the auto margin is still what we want. */
.toolbar .count:first-child,
.listbar .count:first-child { margin-left: 0; }

.count {
  margin-left: auto;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--accent-700);
  white-space: nowrap;
}

/* ---------------- Tabs ---------------- */

/* Tab strip and the page's actions share one row, and that row's rule is the
   only separator the class page needs. */
.tabs-bar {
  display: flex;
  align-items: flex-end;
  gap: 0;
  flex-wrap: wrap;
  margin-top: .6rem;
  padding-bottom: .5rem;
  border-bottom: 2px solid var(--rule);
}

.tabs-bar .head-actions { padding-bottom: .1rem; }


.tabs {
  display: flex;
  gap: .25rem;
  /* overflow-x alone would make overflow-y compute to `auto`, which paints a
     stray vertical scrollbar on the strip. Pin it shut. */
  overflow-x: auto;
  overflow-y: hidden;
  margin-bottom: -.5rem;
}

.tab {
  position: relative;
  padding: .6rem 1.15rem .65rem;
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
  border-bottom: 3px solid transparent;
  transition: color .12s, background .12s, border-color .12s;
}

.tab:hover { color: var(--brand-700); background: var(--sand-050); }

.tab[aria-selected="true"] {
  color: var(--brand-800);
  border-bottom-color: var(--accent-500);
}

.tab:focus-visible { outline: 3px solid var(--accent-400); outline-offset: -3px; }

/* ---------------- Empty-tab placeholder ---------------- */

.placeholder {
  max-width: 34rem;
  margin: 3.5rem auto;
  text-align: center;
  color: var(--ink-soft);
}

.placeholder-mark {
  display: grid;
  place-items: center;
  width: 3.6rem;
  height: 3.6rem;
  margin: 0 auto .9rem;
  border-radius: 50%;
  background: var(--sand-050);
  border: 2px solid var(--sand-300);
  color: var(--accent-600);
  font-size: 1.6rem;
  line-height: 1;
}

.placeholder h2 {
  font-size: 1.15rem;
  color: var(--brand-800);
  margin-bottom: .4rem;
}

.placeholder p { margin: 0 0 .5rem; font-size: .9rem; }

.placeholder-note {
  font-size: .83rem;
  font-style: italic;
  color: var(--accent-700);
}

/* ---------------- Card grid ---------------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  gap: 1.4rem;
  margin-top: 1.5rem;
}

.card {
  display: flex;
  flex-direction: column;
  /* Card bodies sit at page colour, so the outline is what separates them. */
  border: 1px solid var(--sand-300);
  border-radius: 10px;
  background: var(--canvas);
  overflow: hidden;
  cursor: pointer;
  transition: box-shadow .15s, transform .15s, border-color .15s;
}

.card:hover,
.card:focus-visible {
  border-color: var(--accent-500);
  box-shadow: 0 10px 26px rgba(43, 48, 32, .16);
  transform: translateY(-2px);
  outline: none;
}

/* --- Banner: the coloured strip that carries the title --- */

.card-banner {
  position: relative;
  padding: .8rem 1rem .85rem;
  min-height: 5rem;
  color: var(--white);
  overflow: hidden;
}

/* Decorative arc, echoing Google Classroom's illustrated header. */
.card-banner::after {
  content: "";
  position: absolute;
  right: -3.5rem;
  bottom: -5.5rem;
  width: 12rem;
  height: 12rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, .09);
  pointer-events: none;
}

.card-banner::before {
  content: "";
  position: absolute;
  right: 1.2rem;
  top: -3.2rem;
  width: 7rem;
  height: 7rem;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .16);
  pointer-events: none;
}

/* One treatment for every card, so colour never implies a difference that
   isn't there. The other three were dropped along with the rotation. */
.banner-a { background: linear-gradient(135deg, var(--brand-900), var(--brand-700)); }

.card-eyebrow {
  display: block;
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--sand-200);
  margin-bottom: .3rem;
}

.card-title {
  position: relative;
  margin: 0;
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -.01em;
  overflow-wrap: break-word;
  padding-right: 4.5rem; /* clears the status flag */
}

.card-meta {
  position: relative;
  margin: .35rem 0 0;
  font-size: .8rem;
  color: rgba(255, 255, 255, .85);
  overflow-wrap: break-word;
}

/* The flag sits on a coloured banner, so it needs its own light treatment
   rather than the status colours it would otherwise inherit. */
.card-flag {
  position: absolute;
  top: .9rem;
  right: .9rem;
  z-index: 1;
  background: var(--sand-050);
  color: var(--brand-800);
  border-color: var(--sand-300);
}

/* --- Body: stats and details --- */

.card-body {
  flex: 1 1 auto;
  padding: .7rem 1rem;
  display: grid;
  gap: .4rem;
}

.card-stats { display: flex; gap: 1.9rem; }

.stat-num {
  display: block;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.1;
  color: var(--brand-700);
  font-variant-numeric: tabular-nums;
}

.stat-label {
  display: block;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.card-details { display: grid; gap: .3rem; }

.detail {
  display: flex;
  gap: .5rem;
  font-size: .84rem;
  align-items: baseline;
}

.detail-label {
  flex: 0 0 4.6rem;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent-700);
}

.detail-value { color: var(--ink); overflow-wrap: break-word; min-width: 0; }
.detail-value.muted { color: var(--ink-soft); }

/* --- Footer: actions --- */

.card-foot {
  display: flex;
  gap: .4rem;
  align-items: center;
  padding: .5rem 1rem .6rem;
  border-top: 1px solid var(--rule);
  flex-wrap: wrap;
}

.card-foot .spacer { flex: 1 1 auto; }

/* --- "Add new" tile --- */

.card-add {
  display: grid;
  place-content: center;
  gap: .4rem;
  min-height: 11rem;
  padding: 1.5rem;
  text-align: center;
  border: 2px dashed var(--accent-400);
  border-radius: 10px;
  background: var(--canvas);
  color: var(--brand-700);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  transition: background .15s, border-color .15s;
}

.card-add:hover, .card-add:focus-visible {
  background: var(--sand-100);
  border-color: var(--accent-600);
  outline: none;
}

.card-add .plus {
  font-size: 2rem;
  line-height: 1;
  font-weight: 400;
}

.card-add .hint {
  font-size: .74rem;
  font-weight: 600;
  color: var(--ink-soft);
}

/* ---------------- Data grid ---------------- */

.grid {
  width: 100%;
  border-collapse: collapse;
  margin-top: 1.1rem;
  font-size: .9rem;
}

.grid thead th {
  text-align: left;
  padding: .55rem .75rem;
  background: var(--brand-800);
  color: var(--white);
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 700;
  white-space: nowrap;
  border-bottom: 2px solid var(--accent-500);
}

.grid tbody td {
  padding: .6rem .75rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: middle;
  overflow-wrap: break-word;
}

.grid tbody tr { background: var(--surface); }
.grid tbody tr:hover { background: var(--brand-050); }

.num, .grid td.num { text-align: right; font-variant-numeric: tabular-nums; }
.col-actions, .grid td.col-actions { text-align: right; white-space: nowrap; }

.primary-cell {
  font-weight: 700;
  color: var(--brand-700);
  font-size: .95rem;
}

/* ---------------- Student roster (directory list) ---------------- */

/* The class page reads as one centred column rather than stretching edge to
   edge — a roster row has nothing useful to put in 1200px of width. */
#viewClass {
  width: 100%;
  max-width: 900px;
  margin-inline: auto;
}

.roster { margin-top: .9rem; }

.roster-letter {
  padding: .3rem .3rem .22rem;
  background: var(--canvas);
  border-bottom: 2px solid var(--sand-200);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .16em;
  color: var(--accent-700);
}

.roster-group + .roster-group { margin-top: .7rem; }

.roster-row {
  display: flex;
  align-items: center;
  gap: .85rem;
  padding: .5rem .3rem;
  border-bottom: 1px solid var(--sand-100);
  transition: background .12s;
}

.roster-row:hover { background: var(--surface); }

/* Nothing below it to divide from. */
.roster-group.is-single .roster-row { border-bottom: 0; }

.roster-index {
  flex: 0 0 1.6rem;
  text-align: right;
  font-size: .8rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

/* Name and student number stack; the number is the quieter of the two. */
.roster-name {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: .08rem;
  overflow-wrap: break-word;
}

.roster-name .fullname {
  font-size: .95rem;
  color: var(--ink);
}

.roster-name .studentno {
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

.roster-status { flex: 0 0 6.5rem; }

.roster-enrolled {
  flex: 0 0 11rem;
  display: flex;
  flex-direction: column;
  gap: .08rem;
  font-size: .78rem;
  color: var(--ink-soft);
}

.roster-enrolled .course-year { color: var(--brand-700); font-weight: 700; }

/* Row selection checkbox, shown only on the database page. */
.roster-pick {
  flex: 0 0 auto;
  width: auto;
  margin: 0;
  accent-color: var(--brand-700);
  cursor: pointer;
}

.roster-row.is-picked { background: var(--brand-050); }

/* The whole row is the hit target for selection, not just the checkbox. */
.roster-row.pickable { cursor: pointer; user-select: none; }
.roster-row.pickable:hover { background: var(--brand-050); }

/* Filter dropdowns sit inline in the toolbar and size to their content. */
.toolbar select {
  width: auto;
  min-width: 9.5rem;
  min-height: 0;
  padding: .32rem .5rem;
  font-size: .85rem;
}

/* ---------------- List bar (select-all + bulk actions) ----------------
   Deliberately flat — a hairline rule, no panel and no background fill. */

.listbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .6rem .9rem;
  margin-top: .9rem;
  padding: .35rem .3rem .5rem;
  border-bottom: 2px solid var(--sand-200);
}

.listbar-check {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: .82rem;
  font-weight: 700;
  color: var(--brand-800);
  white-space: nowrap;
  cursor: pointer;
}

.listbar-check input {
  width: auto;
  min-height: 0;
  margin: 0;
  accent-color: var(--brand-700);
  cursor: pointer;
}

.listbar-bulk {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .45rem;
}

.listbar-bulk select {
  width: auto;
  min-width: 9.5rem;
  min-height: 0;
  padding: .25rem .45rem;
  font-size: .8rem;
}

.listbar .count { margin-left: auto; }

.roster-actions { flex: 0 0 auto; display: inline-flex; gap: .3rem; }

/* Narrow screens: drop the index, let the name take the row. */
@media (max-width: 700px) {
  .roster-row { flex-wrap: wrap; gap: .5rem .7rem; }
  .roster-index { display: none; }
  .roster-name { flex: 1 1 60%; order: 1; }
  .avatar { order: 0; }
  .roster-status { flex: 0 0 auto; order: 2; }
  .roster-actions { order: 3; margin-left: auto; }
}

.avatar {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .02em;
  border: 1px solid;
}

.avatar-0 { background: var(--brand-700);  color: var(--white);      border-color: var(--brand-800); }
.avatar-1 { background: var(--accent-500); color: var(--white);      border-color: var(--accent-700); }
.avatar-2 { background: var(--brand-100);  color: var(--brand-800);  border-color: var(--brand-600); }
.avatar-3 { background: var(--sand-100);   color: var(--accent-700); border-color: var(--sand-300); }

.mono { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--brand-700); white-space: nowrap; }
.muted { color: var(--ink-soft); }

/* ---------------- Status pills ---------------- */

.pill {
  display: inline-block;
  padding: .12rem .55rem;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 700;
  border: 1px solid;
  white-space: nowrap;
}

.pill-active       { background: var(--brand-700);  color: var(--white);     border-color: var(--brand-800); }
.pill-inactive     { background: var(--field);      color: var(--ink-soft);  border-color: var(--rule); }
.pill-transferred  { background: var(--sand-100);   color: var(--accent-700); border-color: var(--sand-300); }
.pill-dropped      { background: var(--accent-050); color: var(--accent-700); border-color: var(--accent-500); }

.empty {
  margin: 2.5rem 0 0;
  text-align: center;
  color: var(--ink-soft);
  font-style: italic;
}

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

/* `.btn { display }` would otherwise beat the UA's [hidden] rule. */
[hidden] { display: none !important; }

.btn {
  display: inline-block;
  font: inherit;
  font-size: .82rem;
  font-weight: 700;
  line-height: 1.35;
  border-radius: var(--radius);
  border: 1px solid transparent;
  padding: .38rem .8rem;
  cursor: pointer;
  white-space: nowrap;
  transition: filter .12s, background .12s;
}

.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn:not(:disabled):hover { filter: brightness(1.08); }

.btn:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
.grid tbody tr:focus-visible {
  outline: 3px solid var(--accent-400);
  outline-offset: 1px;
}

.btn-primary    { background: var(--brand-700);  color: var(--white); border-color: var(--brand-800); }
.btn-accent   { background: var(--accent-400); color: var(--brand-900); border-color: var(--accent-600); }
/* Outline buttons sit beside the green primary, so they take a green outline
   to match it. The exception is further down: row-level Edit, which pairs with
   a terracotta Delete and keeps a terracotta outline. */
.btn-ghost  { background: var(--field);    color: var(--brand-700); border-color: var(--brand-600); }
.btn-danger { background: var(--accent-600); color: var(--white); border-color: var(--accent-700); }

.btn-sm { padding: .22rem .55rem; font-size: .72rem; }

/* Row actions pair Edit with a terracotta Delete, not a green primary. */
.roster-actions .btn-ghost,
.row-btns .btn-ghost { border-color: var(--accent-500); }

.row-btns { display: inline-flex; gap: .3rem; }

/* ---------------- Dropdown menu ---------------- */

.menu-wrap { position: relative; display: inline-block; }

.menu {
  position: absolute;
  right: 0;
  top: calc(100% + .3rem);
  z-index: 30;
  min-width: 12rem;
  padding: .25rem;
  display: grid;
  gap: .1rem;
  background: var(--field);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: 0 10px 26px rgba(43, 48, 32, .2);
}

.menu button {
  font: inherit;
  font-size: .85rem;
  text-align: left;
  padding: .45rem .6rem;
  border: none;
  border-radius: var(--radius);
  background: none;
  color: var(--ink);
  cursor: pointer;
}

.menu button:hover { background: var(--sand-050); color: var(--brand-800); }
.menu button:focus-visible { outline: 2px solid var(--accent-500); outline-offset: -2px; }

/* ---------------- Attendance roll call ---------------- */

.roll-top { display: flex; align-items: flex-end; gap: 1.3rem; flex-wrap: wrap; }
.roll-date { flex: 0 0 12rem; }
.roll-noclass { padding-bottom: .55rem; }

.roll-note { margin: .55rem 0 0; font-size: .82rem; color: var(--ink-soft); }

/* Progress, count and shortcuts read as one band, divided from the setup
   above it by a hairline rather than by guesswork spacing. */
.roll-meter {
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid var(--rule);
}

.roll-meter-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .7rem;
  margin-top: .5rem;
}

/* Locked: an existing record is on file and overwriting hasn't been agreed to
   yet. Dimmed, but still clickable — the click re-asks instead of marking. */
.roll-marks.is-locked .mark-btn,
.roll-bulk.is-locked .btn {
  opacity: .4;
}

.roll-marks.is-locked .mark-btn:hover,
.roll-bulk.is-locked .btn:hover {
  opacity: .55;
}

/* Overwriting a saved day is the one destructive thing this dialog can do, so
   the notice carries terracotta rather than sitting in the quiet grey. */
.roll-note.is-warning {
  padding-left: .55rem;
  border-left: 3px solid var(--accent-500);
  color: var(--accent-700);
  font-weight: 700;
}

.roll-progress {
  height: 5px;
  border-radius: 999px;
  background: var(--sand-200);
  overflow: hidden;
  margin-top: .4rem;
}

.roll-progress-fill {
  height: 100%;
  width: 0;
  background: var(--brand-600);
  transition: width .18s;
}

.roll-count {
  margin: 0;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent-700);
}

.roll-card {
  display: flex;
  align-items: center;
  gap: .9rem;
  padding: 1.1rem .2rem .9rem;
}

.roll-card .avatar { width: 3.2rem; height: 3.2rem; font-size: 1.05rem; }

.roll-name { margin: 0; font-size: 1.35rem; font-weight: 700; color: var(--brand-800); }
.roll-no { margin: .1rem 0 0; font-size: .82rem; color: var(--ink-soft); }

/* Bulk marking, sitting with the roll call rather than in the footer. */
.roll-bulk {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0;
}

.roll-bulk-label {
  margin-right: .2rem;
  font-size: .66rem;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.roll-marks {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: .5rem;
}

/* Big targets — this gets tapped once per student. */
.mark-btn {
  font: inherit;
  font-size: .9rem;
  font-weight: 700;
  padding: .75rem .5rem;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  background: var(--field);
  color: var(--ink);
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}

.mark-btn:hover { border-color: var(--brand-600); background: var(--brand-050); }
.mark-btn:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 1px; }

.mark-btn[data-mark="Present"].is-on { background: var(--brand-700); border-color: var(--brand-800); color: var(--white); }
.mark-btn[data-mark="Late"].is-on    { background: var(--sand-300); border-color: var(--sand-300); color: var(--brand-900); }
.mark-btn[data-mark="Excused"].is-on { background: var(--accent-100); border-color: var(--accent-500); color: var(--accent-700); }
.mark-btn[data-mark="Absent"].is-on  { background: var(--accent-600); border-color: var(--accent-700); color: var(--white); }

.roll-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .6rem;
  padding-top: .9rem;
}

.roll-current { font-size: .78rem; color: var(--ink-soft); }

.roll-summary {
  margin: 0 0 .6rem;
  font-size: .88rem;
  color: var(--brand-800);
  font-weight: 700;
}

/* ---------------- Attendance history ---------------- */

.history { margin-top: .5rem; }

/* Week heading: flat rule, matching the roster's letter headings. */
.week-head {
  display: flex;
  align-items: baseline;
  gap: .7rem;
  flex-wrap: wrap;
  margin-top: 1.4rem;
  padding: .3rem .3rem .25rem;
  border-bottom: 2px solid var(--sand-200);
}

.week-head:first-child { margin-top: .4rem; }

.week-label {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent-700);
}

.week-range { font-size: .78rem; color: var(--ink-soft); }

.week-meetings {
  margin-left: auto;
  font-size: .74rem;
  font-weight: 700;
  color: var(--ink-soft);
  white-space: nowrap;
}

.week-meetings.is-short { color: var(--accent-700); }

/* Attendance days: full-width rows with their own quiet layout — deliberately
   not the banner card used for school years and classes. */
.att-list { display: grid; gap: .55rem; margin-top: .9rem; }

.att-day {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: .7rem 1rem .7rem .8rem;
  /* Against the light page the row needs a real edge — the old sand-100
     border was within a shade of the fill, so the card had no outline. */
  border: 1px solid var(--sand-300);
  border-radius: 10px;
  background-color: var(--att-card);
  box-shadow: 0 1px 3px rgba(43, 48, 32, .07);
  cursor: pointer;
  overflow: hidden;
  transition: border-color .12s, box-shadow .12s;
}

/* An olive panel washing in from the right, with the same arc motif the school
   year and class banners use. It stops short of the text so the row keeps both
   colours — sand under the labels, green at the tail. */
.att-day::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 78% 145%, rgba(255, 255, 255, .10) 0 84px, transparent 85px),
    radial-gradient(circle at 96% -60%, rgba(255, 255, 255, .075) 0 62px, transparent 63px),
    linear-gradient(90deg,
      rgba(67, 82, 40, 0) 50%,
      rgba(67, 82, 40, .88) 76%,
      rgba(51, 63, 30, 1) 100%);
  pointer-events: none;
}

.att-day.is-noclass::before {
  background:
    radial-gradient(circle at 78% 145%, rgba(255, 255, 255, .12) 0 84px, transparent 85px),
    radial-gradient(circle at 96% -60%, rgba(255, 255, 255, .09) 0 62px, transparent 63px),
    linear-gradient(90deg,
      rgba(172, 59, 33, 0) 50%,
      rgba(172, 59, 33, .88) 76%,
      rgba(142, 47, 26, 1) 100%);
}

/* Content sits above the texture. */
.att-day > * { position: relative; z-index: 1; }

.att-day:hover, .att-day:focus-visible {
  border-color: var(--brand-600);
  box-shadow: 0 3px 12px rgba(43, 48, 32, .13);
  outline: none;
}

/* Cancelled days keep the same fill but take a terracotta edge, matching how
   terracotta signals an exception everywhere else. */
.att-day.is-noclass { border-color: var(--accent-500); }
.att-day.is-noclass:hover { border-color: var(--accent-600); }

/* Delete is terracotta, which would vanish into the terracotta panel on a
   cancelled row — flip it to a light button there. */
.att-day.is-noclass .btn-danger {
  background: var(--field);
  color: var(--accent-700);
  border-color: var(--field);
}

/* Calendar leaf */
.att-leaf {
  flex: 0 0 auto;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: .05rem;
  width: 3.4rem;
  padding: .35rem .2rem .4rem;
  border-radius: 8px;
  background: var(--field);
  border: 1px solid var(--sand-200);
  border-top: 4px solid var(--brand-700);
  line-height: 1;
}

.att-day.is-noclass .att-leaf { border-top-color: var(--accent-500); }

.leaf-day {
  font-size: .58rem;
  font-weight: 700;
  letter-spacing: .1em;
  color: var(--brand-700);
}

.att-day.is-noclass .leaf-day { color: var(--accent-700); }

.leaf-num {
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.leaf-mon {
  font-size: .55rem;
  font-weight: 700;
  letter-spacing: .09em;
  color: var(--ink-soft);
}

.att-main { flex: 1 1 auto; min-width: 0; display: grid; gap: .18rem; }

.att-line { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }

.att-date { font-size: .96rem; font-weight: 700; color: var(--brand-800); }

.att-badge {
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .04em;
  padding: .1rem .5rem;
  border-radius: 999px;
  background: var(--accent-050);
  color: var(--accent-700);
  border: 1px solid var(--accent-500);
  white-space: nowrap;
}

.att-stats { display: flex; gap: .9rem; flex-wrap: wrap; font-size: .8rem; }

.stat-present { color: var(--brand-700); font-weight: 700; }
.stat-late    { color: var(--accent-700); font-weight: 700; }
.stat-excused { color: var(--accent-700); font-weight: 700; }
.stat-absent  { color: var(--accent-600); font-weight: 700; }
.stat-muted   { color: var(--ink-soft); }

.att-reason {
  margin: .1rem 0 0;
  font-size: .79rem;
  font-style: italic;
  color: var(--ink-soft);
  overflow-wrap: break-word;
}

.att-actions { flex: 0 0 auto; }

/* Per-student rows inside the day modal. Centre-aligned, since a baseline
   against a taller select drops the checkbox out of line. */
.day-row { align-items: center; }

.day-row select {
  flex: 0 0 8rem;
  width: auto;
  min-height: 0;
  padding: .2rem .4rem;
  font-size: .78rem;
}

.tally {
  font-size: .72rem;
  font-weight: 700;
  padding: .1rem .5rem;
  border-radius: 999px;
  border: 1px solid;
  white-space: nowrap;
}

.tally-present { background: var(--brand-050); color: var(--brand-800); border-color: var(--brand-600); }
.tally-late    { background: var(--sand-050);  color: var(--accent-700); border-color: var(--sand-300); }
.tally-excused { background: var(--accent-050); color: var(--accent-700); border-color: var(--accent-100); }
.tally-absent  { background: var(--accent-050); color: var(--accent-700); border-color: var(--accent-500); }

.h-Present { color: var(--brand-700); }
.h-Late    { color: var(--accent-700); }
.h-Excused { color: var(--accent-700); }
.h-Absent  { color: var(--accent-600); }

/* ---------------- Import preview ---------------- */

.import-help {
  margin: 0;
  font-size: .84rem;
  color: var(--ink-soft);
  line-height: 1.55;
}

.import-help code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: .92em;
}

.import-summary {
  margin: 0;
  padding: .5rem .7rem;
  border-radius: var(--radius);
  background: var(--brand-050);
  border-left: 4px solid var(--brand-600);
  font-size: .86rem;
  color: var(--brand-800);
}

.import-toolbar {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding-bottom: .5rem;
}

.import-count {
  margin-left: auto;
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--accent-700);
}

/* Flat scrolling list — hairline dividers only, no panel around it. */
.import-rows {
  max-height: 20rem;
  overflow-y: auto;
  border-top: 1px solid var(--rule);
}

.import-row {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: .35rem .6rem;
  border-bottom: 1px solid var(--sand-100);
  font-size: .86rem;
}

.import-row:last-child { border-bottom: none; }

/* Without min-height:0 the checkbox inherits the 2.45rem control height and
   pushes the row to two lines. */
.import-row input[type="checkbox"] {
  width: auto;
  min-height: 0;
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--brand-700);
}
.import-row .imp-index { flex: 0 0 1.8rem; text-align: right; color: var(--ink-soft); font-size: .76rem; }
.import-row .imp-name { flex: 1 1 auto; min-width: 0; }
.import-row .imp-name b { font-weight: 700; }
.import-row .imp-note { flex: 0 0 auto; font-size: .74rem; font-style: italic; color: var(--accent-700); }
.import-row.is-dupe { background: var(--sand-050); }
.import-row.is-dupe .imp-name { color: var(--ink-soft); }

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

input, select {
  font: inherit;
  width: 100%;
  /* Inputs and selects render at slightly different intrinsic heights; pin
     both so side-by-side fields line up exactly. */
  min-height: 2.45rem;
  line-height: 1.4;
  padding: .45rem .6rem;
  /* Fields are near the page colour, so the outline is what defines them. */
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  background: var(--field);
  color: var(--ink);
}

/* Left to the browser a textarea comes out monospace with its own border and a
   drag handle. Match the inputs, and grow by scrolling rather than by resizing
   — a dragged-out box pushes the buttons off the bottom of the dialog. */
textarea {
  font: inherit;
  width: 100%;
  line-height: 1.45;
  padding: .45rem .6rem;
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  background: var(--field);
  color: var(--ink);
  resize: none;
  overflow-y: auto;
}

input::placeholder, textarea::placeholder { color: #97977E; }

/* Fields sharing a line. The children carry their own flex basis. */
.form-row { display: flex; flex-wrap: wrap; gap: .75rem; }
.form-row > .field { min-width: 0; }

/* A named division inside a long dialog: a hairline and a small label, with
   any note and control riding along on the same line. */
.form-sec {
  display: flex;
  align-items: center;
  gap: .7rem;
  margin-top: .3rem;
  padding-top: .8rem;
  border-top: 1px solid var(--rule);
}

.form-sec-note {
  margin-left: auto;
  font-size: .76rem;
  font-style: italic;
  color: var(--ink-soft);
}

/* A section at the very top of a dialog has nothing above it to divide. */
.form-sec.is-first { margin-top: 0; padding-top: 0; border-top: 0; }

/* `align-content: start` keeps rows at their natural height. Without it a
   field carrying a hint line stretches its neighbour's control, which is what
   knocked Student No. and Status out of line. */
.field { display: grid; gap: .25rem; align-content: start; }

.field > span {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--brand-700);
}

.req { color: var(--accent-600); }

.field-hint {
  font-size: .74rem;
  color: var(--ink-soft);
  font-style: italic;
}

.input-mask {
  font-variant-numeric: tabular-nums;
  letter-spacing: .08em;
  font-weight: 600;
}

.field-row { display: flex; gap: .75rem; flex-wrap: wrap; }
.field-row > .field { flex: 1 1 9rem; min-width: 0; }

/* ---------------- Schedule builder ---------------- */

/* An outline, not a fill: the block sits at the dialog's own colour so it
   reads as part of the form rather than a panel dropped into it. */
.sched-fieldset {
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  padding: .3rem .9rem .9rem;
  margin: 0;
  background: none;
}

.sched-fieldset legend {
  padding: 0 .45rem;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--brand-700);
}

.sched-help {
  margin: 0 0 .7rem;
  font-size: .78rem;
  color: var(--ink-soft);
}

.sched-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .75rem;
  padding: .7rem 0;
  border-top: 1px dashed var(--rule);
}

.sched-row:first-child { border-top: none; padding-top: .2rem; }

/* Course and the code that course uses, side by side and equally weighted. */
.class-course-row > .field { flex: 1 1 10rem; min-width: 0; }
.class-course-row > .btn { flex: 0 0 auto; }

/* Days take the slack so the whole block sits on one line and fills the width. */
.sched-days { flex: 1 1 21rem; min-width: 0; }

/* Matched to the height of the time boxes it sits beside. The row aligns on
   its bottom edge, so equal heights are what puts their centres on one line —
   a shorter button would hang below them. */
.sched-row .btn-danger {
  align-self: flex-end;
  height: 2.45rem;
  display: inline-flex;
  align-items: center;
}

.sched-label {
  display: block;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--brand-700);
  margin-bottom: .3rem;
}

.day-boxes { display: flex; flex-wrap: wrap; gap: .28rem; }

/* Checkbox styled as a toggle chip; the real input stays for a11y. */
.day-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.6rem;
  padding: .32rem .45rem;
  border: 1px solid var(--sand-300);
  border-radius: 999px;
  background: var(--field);
  color: var(--ink-soft);
  font-size: .76rem;
  font-weight: 700;
  cursor: pointer;
  user-select: none;
  transition: background .12s, color .12s, border-color .12s;
}

.day-chip input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.day-chip:hover { background: var(--sand-100); }

.day-chip:has(input:checked) {
  background: var(--brand-700);
  border-color: var(--brand-800);
  color: var(--white);
}

.day-chip:has(input:focus-visible) {
  outline: 3px solid var(--accent-400);
  outline-offset: 1px;
}

.sched-time { flex: 0 1 8.5rem; min-width: 7rem; }
.sched-time select { font-variant-numeric: tabular-nums; }

.sched-remove { flex: 0 0 auto; margin-bottom: .05rem; }

.form-error {
  margin: 0;
  padding: .5rem .7rem;
  border-radius: var(--radius);
  background: var(--brand-050);
  border-left: 4px solid var(--brand-600);
  color: var(--brand-800);
  font-size: .86rem;
  font-weight: 600;
}

/* ---------------- Modals ---------------- */

.modal {
  width: min(600px, calc(100vw - 2rem));
  padding: 0;
  border: none;
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(43, 48, 32, .28);
  background: var(--canvas);
  color: var(--ink);
}

.modal-wide { width: min(720px, calc(100vw - 2rem)); }
.modal-xl { width: min(900px, calc(100vw - 2rem)); }
.modal-narrow { width: min(430px, calc(100vw - 2rem)); }

.modal::backdrop { background: rgba(38, 47, 23, .45); }

.modal-head {
  padding: .85rem 1.25rem;
  background: var(--brand-700);
  border-bottom: 3px solid var(--accent-500);
  color: var(--white);
}

.modal-head h2 { font-size: 1.02rem; }
.modal-head.danger { background: var(--accent-600); border-bottom-color: var(--accent-700); }

.modal-body { padding: 1.1rem 1.25rem; display: grid; gap: .85rem; }
/* pre-line so a confirm can put its reassurance on a line of its own. */
.modal-body p { margin: 0; white-space: pre-line; }

.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: .6rem;
  padding: .8rem 1.25rem;
  background: var(--canvas);
  border-top: 1px solid var(--rule);
}

/* ---------------- Toasts ---------------- */

.toast-stack {
  position: fixed;
  right: 1.1rem;
  bottom: 1.1rem;
  display: grid;
  gap: .5rem;
  z-index: 50;
  max-width: min(24rem, calc(100vw - 2rem));
}

.toast {
  padding: .6rem .9rem;
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(43, 48, 32, .16);
  font-size: .88rem;
  font-weight: 600;
  border-left: 5px solid;
  background: var(--field);
  overflow-wrap: anywhere;
}

.toast-ok    { color: var(--brand-700);  border-color: var(--brand-600); }
.toast-error { color: var(--accent-700); border-color: var(--accent-500); }

/* ---------------- Responsive ---------------- */

/* Below this width the grids scroll sideways instead of widening the page. */
@media (max-width: 1000px) {
  .topbar, .page { padding-left: 1rem; padding-right: 1rem; }
  /* The nav bar keeps its tight gutter here too, so the outer buttons stay
     hard against the edge instead of being inset further than on desktop. */
  .crumbs { padding-left: .5rem; padding-right: .5rem; }

  .view { min-width: 0; }

  .grid {
    display: block;
    overflow-x: auto;
    white-space: nowrap;
  }
  .grid thead, .grid tbody { display: table; width: 100%; min-width: 52rem; }

  /* These two already sit in their own scroll wrapper and must stay real
     tables. Turning a table into a block makes its head and body lay out
     independently, so the columns stop lining up with each other. */
  .grid.register-grid, .grid.schedule-grid { display: table; white-space: normal; }
  .grid.register-grid thead, .grid.schedule-grid thead {
    display: table-header-group; width: auto; min-width: 0;
  }
  .grid.register-grid tbody, .grid.schedule-grid tbody {
    display: table-row-group; width: auto; min-width: 0;
  }
}

/* Below this the schedule block can't hold one line — let days take their own. */
@media (max-width: 820px) {
  .sched-days { flex: 1 1 100%; }
}

/* Keep the header on one tidy line once space gets tight. */
@media (max-width: 560px) {
  .topbar { padding-top: .55rem; padding-bottom: .55rem; gap: .6rem; }
  .brand { gap: .55rem; }
  .brand-mark { width: 32px; height: 32px; }
  .brand-mark svg { width: 19px; height: 19px; }
  .brand-name { font-size: .95rem; line-height: 1.2; }
  .brand-sub { display: none; }
  .topbar-nav { width: 32px; height: 32px; }
  .active-year-label { font-size: .58rem; letter-spacing: .08em; }
  .badge-accent { font-size: .78rem; padding: .18rem .6rem; white-space: nowrap; }
  .page-head h1 { font-size: 1.35rem; }
}

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

/* ---------------- Grade sheet ----------------
   The college's paper form, sitting in the same space the register does.
   This one block deliberately steps outside the cream palette: it is white
   paper with black rules, because that is what it is a picture of. One
   printed page is shown at a time, stepped through with the arrows. */

/* The sheet is a picture of a 960px page and the class column is 900, so the
   whole view bleeds 30px either side rather than the page being shrunk to
   fit. Narrower windows fall back to scrolling it. */
#gsView { margin-inline: -40px; }

/* Three columns of equal outer weight, so the pager lands on the middle of
   the bar rather than being pushed off centre by the actions beside it. */
/* Held to the page's own width and centred on it, so the actions end exactly
   where the sheet's right edge does — the view is bled wider than the page,
   and left to itself the bar would run past it. */
.gs-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: .5rem;
  width: 960px;
  max-width: 100%;
  margin: 1rem auto 0;
}

.gs-nav-side { min-width: 0; }
.gs-pager { display: flex; align-items: center; gap: .8rem; }

.gs-nav-label {
  min-width: 8.5rem;
  font-size: .82rem;
  font-weight: 700;
  text-align: center;
  color: var(--brand-800);
}

.gs-nav-actions {
  display: flex;
  align-items: center;
  justify-self: end;
  gap: .4rem;
  min-width: 0;
}

/* Icon buttons, sized to sit level with the paper dropdown beside them. */
.gs-tool {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  flex: 0 0 auto;
  padding: 0;
  border: 1px solid var(--brand-600);
  border-radius: var(--radius);
  background: none;
  color: var(--brand-700);
  cursor: pointer;
  transition: background .12s, color .12s;
}

.gs-tool:hover { background: var(--brand-050); }
.gs-tool:focus-visible { outline: 3px solid var(--accent-400); outline-offset: 1px; }
.gs-tool.is-primary { background: var(--brand-700); border-color: var(--brand-800); color: var(--white); }
.gs-tool.is-primary:hover { background: var(--brand-600); }

/* Terracotta, because it throws work away — the one signal an icon-only
   button can give before it is pressed, and the only one a tablet gets,
   since there is no hover there to show a tooltip. */
.gs-tool.is-danger { border-color: var(--accent-500); color: var(--accent-700); }
.gs-tool.is-danger:hover { background: var(--accent-050); }

/* The artwork is black PNG, so it is worn as a mask and takes its colour from
   the button — the same trick the nav icons use. */
.gs-tool-img {
  display: block;
  width: 17px;
  height: 17px;
  background: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

/* A button that carries artwork beside its label. */
.btn.has-icon { display: inline-flex; align-items: center; gap: .35rem; }

.btn-icon {
  display: block;
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  background: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.i-excel  { -webkit-mask-image: url("excel.png");     mask-image: url("excel.png"); }
.i-pdf    { -webkit-mask-image: url("exportPDF.png"); mask-image: url("exportPDF.png"); }
.i-revert { -webkit-mask-image: url("revert.png");    mask-image: url("revert.png"); }

/* Sized to the icon buttons beside it, so the three read as one group. */
.gs-paper {
  width: auto;
  min-width: 9rem;
  height: 2rem;
  min-height: 0;
  padding: 0 .45rem;
  font-size: .76rem;
}

.gs-pages { display: flex; justify-content: center; padding: 1.1rem 0 .5rem; overflow-x: auto; }

/* A 960-wide page holding two sheets. The old system left 96px either side
   of the fold and set 19px type, which reads far larger than its own
   printed sheet does; the gap is narrower here and the type sized to the
   sheet, so a full name fits its column without being clipped. */
.gs-page {
  position: relative;
  display: grid;
  /* minmax(0,…) or a long unbroken name would push its column wider than
     the page, because a grid track will not shrink below min-content. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  /* The side margins are half the fold, so the space outside a sheet and the
     space from a sheet to the centre line are the same 36px. */
  gap: 72px;
  box-sizing: border-box;
  width: 960px;
  flex: 0 0 auto;
  padding: 28px 36px;
  background: #FFF;
  border: 1px solid var(--sand-300);
  box-shadow: 0 6px 18px rgba(43, 48, 32, .18);
}

/* The fold, drawn as faintly as theirs. */
.gs-page::before {
  content: "";
  position: absolute;
  top: 28px;
  bottom: 28px;
  left: 50%;
  border-left: 1px solid rgba(0, 0, 0, .15);
}

.gs-sheet {
  font-family: "Times New Roman", Times, serif;
  font-size: 14px;
  line-height: 1.3;
  color: #000;
}

.gs-sheet.is-blank { visibility: hidden; }

/* Two columns rather than a centred group: the seal is pinned to the sheet's
   left edge, in line with the TERM label below it, and the name centres in
   what is left. Working from the edge keeps it there whatever the school is
   renamed to — a centred group would drift with the length of the name. */
.gs-head {
  display: grid;
  grid-template-columns: 52px 1fr;
  align-items: center;
  column-gap: 1.25rem;
}

.gs-seal { position: relative; top: -6px; width: 52px; height: 52px; object-fit: contain; }
/* min-width:0 so the flex item can be narrower than the name it holds, and
   visible overflow so the name shows anyway. */
.gs-titles { flex: 0 1 auto; min-width: 0; text-align: center; overflow: visible; }

/* The masthead and the signatures are typed, but must print as plain text —
   so the fields carry no border, no fill and no box of their own. */
.gs-college, .gs-place, .gs-sign-title, .gs-curryr, .gs-cell {
  min-height: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font: inherit;
  color: #000;
}

.gs-sign-title { display: block; width: 100%; padding: 0; text-align: center; }

/* Centred on the space beside the seal, and allowed to overhang it — the
   college's full name is wider than that space at this size. */
.gs-college, .gs-place { display: block; text-align: center; }

/* Everything on the sheet is the sheet's own size — the old form sets one
   size for the whole page and lets the bold carry the masthead. */
.gs-college { font-weight: 700; text-transform: uppercase; white-space: nowrap; }
.gs-kind { margin: .25rem 0 0; text-align: center; }

/* Typed fields only show they are typeable once you are in them. */
.gs-college:hover, .gs-place:hover, .gs-sign-title:hover,
.gs-curryr:hover, .gs-cell:hover { background: #F2F4F8; }

.gs-college:focus-visible, .gs-place:focus-visible, .gs-sign-title:focus-visible,
.gs-curryr:focus-visible, .gs-cell:focus-visible {
  outline: 2px solid var(--accent-500);
  outline-offset: -1px;
  background: #FFF;
}

/* Two label/value pairs per line: TERM with CREDITS, COURSE with CURR/YR. */
.gs-facts {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .05rem .5rem;
  margin: .7rem 0 .35rem;
  font-weight: 700;
}

.gs-fact { display: flex; gap: .3rem; white-space: nowrap; }
.gs-fact-label { flex: 0 0 auto; }
.gs-fact-value { font-weight: 400; }

/* The descriptive title runs under both columns and is the one fact long
   enough to need wrapping. */
.gs-fact.is-wide { grid-column: 1 / -1; white-space: normal; }
.gs-fact.is-wide .gs-fact-value { overflow-wrap: anywhere; }

/* Typed, not derived. */
.gs-curryr { width: 3.5rem; padding: 0 .15rem; font-weight: 400; }

/* Heavier rule round the outside and under the headings than between the
   rows, the way the old sheet draws it. */
.gs-table {
  width: 100%;
  /* Fixed, so the column widths below hold and a long name is clipped
     rather than widening the sheet. */
  table-layout: fixed;
  border-collapse: collapse;
  border: 2px solid #000;
}

.gs-table th, .gs-table td {
  border: 1.25px solid #000;
  padding: 1px 4px;
}

.gs-table th { border-width: 2px; }

.gs-table th { font-weight: 700; text-align: center; }
.gs-table .gs-no { width: 6%; text-align: center; }
.gs-table .gs-mark { width: 11%; text-align: center; padding: 0; }
/* One line a row, whatever the name's length — the old sheet clips rather
   than letting a long name make its row taller than the rest. */
.gs-table td.gs-name {
  max-width: 0;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The grade boxes fill their cell so the ruled square is what you click. */
.gs-cell {
  width: 100%;
  padding: 1px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* A merged cell is for writing in, so it says so by being wider, not by
   being marked. */
.gs-table td.gs-mark.is-merged { width: auto; }

.gs-count { margin: .5rem 0 0; }

/* Prompt, then a ruled line to sign on, then the office under it. The rule
   is only as wide as the name, so the block is centred and the field sizes
   itself — which is why the name is contenteditable rather than an input. */
.gs-sign { margin-top: 1.1rem; }
.gs-sign-prompt { margin: 0 0 1.6rem; }
.gs-sign-line { text-align: center; }

.gs-sign-name {
  display: inline-block;
  min-width: 12ch;
  padding: 0 .3rem 2px;
  border-bottom: 1px solid #000;
  font-weight: 700;
  text-align: center;
  white-space: nowrap;
  color: #000;
}

.gs-sign-name:focus-visible { outline: 2px solid var(--accent-500); outline-offset: 2px; }
.gs-sign-name:hover { background: #F2F4F8; }
.gs-sign-title { margin-top: 2px; }

/* There is deliberately no print stylesheet: the PDF is written by the
   server, in gradesheet-pdf.js, so nothing here has to survive a printer. */

/* --- The class list dropdown --- */

/* A heading inside the menu: which year these classes belong to, and
   whatever the menu has to say when there are none. */
.datamenu-note {
  margin: 0;
  padding: .45rem .7rem;
  font-size: .68rem;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--sand-100);
}

/* Two lines per class: the code carries it, the title explains it. */
#classListMenu .datamenu-item { display: block; text-align: left; line-height: 1.25; }
.datamenu-code { display: block; font-weight: 700; color: var(--brand-800); }
.datamenu-sub { display: block; font-size: .74rem; font-weight: 400; color: var(--ink-soft); }

/* The class already open, so the menu says where you are. */
#classListMenu .datamenu-item.is-on { background: var(--brand-050); }
#classListMenu .datamenu-item.is-on .datamenu-code { color: var(--brand-700); }

/* Search and the status filter share one line: they answer different halves of
   the same question ("which student" vs "which of them still needs work"), and
   stacking them cost a whole band above a list that wants the height. */
.score-searchrow {
  display: flex;
  align-items: center;
  gap: .5rem;
}

.score-searchrow .score-search { flex: 1; min-width: 0; }

/* One dropdown rather than a row of chips: six marks made a button per mark,
   which read as a toolbar in its own right next to the list it was filtering. */
.filter-select {
  width: auto;
  min-height: 0;
  padding: .34rem 1.6rem .34rem .55rem;
  font-size: .82rem;
  font-weight: 600;
  color: var(--brand-700);
}

/* A filter that is hiding rows says so, so an empty-looking list is never a
   mystery. */
.filter-select.is-narrowing {
  border-color: var(--accent-500);
  color: var(--accent-700);
}

/* --- Two rosters, side by side -------------------------------------
   The import refuses on any difference, so the job of this block is to
   show which name causes it. Both lists are shown in full rather than
   only the offenders: seeing "the workbook stops at Rapisura" is what
   explains a missing person, and a bare list of names does not. */
.gs-cmp {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: .9rem;
  margin-top: .8rem;
}

.gs-cmp-head {
  margin: 0 0 .35rem;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.gs-cmp-n { color: var(--brand-700); }

.gs-cmp-list {
  max-height: 15rem;
  overflow-y: auto;
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  background: var(--canvas);
}

.gs-name {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .3rem .55rem;
  font-size: .85rem;
  border-bottom: 1px solid var(--sand-200);
}

.gs-name:last-child { border-bottom: 0; }
.gs-name-no { min-width: 1.6rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.gs-name-text { flex: 1; min-width: 0; }

/* The whole point of the dialog: the ones that do not appear on the other
   side. Terracotta because it is the thing stopping the import. */
.gs-name.is-missing { background: var(--accent-050); }
.gs-name.is-missing .gs-name-text { color: var(--accent-700); font-weight: 600; }

.gs-name-tag {
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--accent-700);
  white-space: nowrap;
}

/* The whole plan, in a line. */
.gs-plan-sum {
  margin: .9rem 0 0;
  font-size: .85rem;
  font-weight: 600;
  color: var(--brand-700);
}

.gs-plan-note { margin: .5rem 0 0; font-size: .82rem; color: var(--ink-soft); }
.gs-plan-note.is-problem { color: var(--accent-700); font-weight: 600; }

/* Names the workbook has and the class does not. Terracotta throughout: this
   block is the one place the import writes something other than marks, so it
   should not read as an aside. */
.gs-add {
  margin-top: .8rem;
  padding: .6rem .7rem;
  border: 1px solid var(--accent-400);
  border-radius: var(--radius);
  background: var(--accent-050);
}

.gs-add-line { margin: 0 0 .45rem; font-size: .85rem; color: var(--accent-700); font-weight: 600; }

.gs-add-list { display: flex; flex-wrap: wrap; gap: .35rem; }

.gs-add-name {
  display: inline-flex;
  align-items: baseline;
  gap: .35rem;
  padding: .2rem .45rem;
  border: 1px solid var(--accent-100);
  border-radius: var(--radius);
  background: var(--canvas);
  font-size: .82rem;
}

/* Enrolling someone already on file is routine; creating a database record
   from a name typed into a spreadsheet is the part worth reading twice. */
.gs-add-where { font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); }
.gs-add-where.is-new { color: var(--accent-700); font-weight: 700; }

.gs-add-confirm {
  display: flex;
  align-items: center;
  gap: .45rem;
  margin-top: .6rem;
  font-size: .85rem;
  font-weight: 600;
  cursor: pointer;
}

.gs-add-confirm input { width: 1rem; height: 1rem; accent-color: var(--accent-500); }

/* The matching case says so plainly rather than showing nothing. */
.roll-note.is-agreed { color: var(--brand-700); font-weight: 600; }


/* ==================================================================
 * csms only
 *
 * Everything above is the CMS's stylesheet untouched. These are the
 * pieces it has no equivalent for — submissions, sign-in, the flash
 * strip — written with its own variables so they sit in the same
 * world. A rule that could live upstream belongs upstream, not here.
 * ================================================================== */

/* The signed-in name and the way out, on the right of the title bar.
   The CMS keeps that corner free; here it has to say who you are. */
.topbar-who {
  display: flex;
  align-items: center;
  gap: .7rem;
  flex: 0 0 auto;
}

.topbar-name {
  font-size: .82rem;
  font-weight: 600;
  color: var(--sand-200);
}

.topbar-who .btn {
  border-color: rgba(255, 255, 255, .35);
  color: var(--white);
  background: none;
}

.topbar-who .btn:hover { background: rgba(255, 255, 255, .13); }

.section-title {
  font-size: .68rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .1em; color: var(--accent-700); margin: 30px 0 0;
}
/* ---------------- List cards (one per activity, full width) ------------- */

.list-cards { display: grid; gap: .7rem; margin-top: 1.1rem; }
.list-card {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: .85rem 1.1rem;
  border: 1px solid var(--sand-300); border-radius: 8px;
  background: var(--canvas); text-decoration: none; color: inherit;
  transition: border-color .15s, box-shadow .15s;
}
a.list-card:hover, a.list-card:focus-visible {
  border-color: var(--accent-500);
  box-shadow: 0 6px 18px rgba(43,48,32,.12);
  outline: none;
}
.list-card .lc-main { flex: 1 1 16rem; min-width: 0; }
.list-card h3 { font-size: .98rem; overflow-wrap: break-word; }
.list-card .lc-meta { margin: .2rem 0 0; font-size: .8rem; color: var(--ink-soft); }
.list-card .lc-side { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.btn-row { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }
/* ---------------- Modal (a form reached by ?new=1 or ?edit=N) ---------- */

.overlay {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(38, 47, 23, .45);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 4vh 1rem; overflow-y: auto;
}
/* ---------------- Tables (genuinely tabular data) ---------------- */

.table-wrap { margin-top: 1.1rem; }
td.tight { white-space: nowrap; width: 1%; }
/* ---------------- Badges and notices ---------------- */

.badge {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: .72rem; font-weight: 700; white-space: nowrap;
  border: 1px solid transparent;
}
.badge-ok { background: var(--brand-050);  color: var(--brand-800);  border-color: var(--brand-100); }
.badge-warn { background: var(--sand-100);   color: var(--ink-soft);   border-color: var(--sand-300); }
.badge-missing { background: transparent;       color: var(--ink-soft);   border-color: var(--sand-300); }
.badge-late { background: var(--accent-050); color: var(--accent-700); border-color: var(--accent-100); }
.flash {
  padding: 10px 14px; border-radius: var(--radius); margin: 1rem 0 0;
  font-size: .87rem; border: 1px solid;
}
.flash-success { background: var(--brand-050);  border-color: var(--brand-100);  color: var(--brand-800); }
.flash-error { background: var(--accent-050); border-color: var(--accent-100); color: var(--accent-700); }
.flash-info { background: var(--sand-100);   border-color: var(--sand-300);   color: var(--ink-soft); }
/* ---------------- Centered auth pages ---------------- */

.auth {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px; background: linear-gradient(160deg, var(--sand-100), var(--canvas));
}
.auth-card {
  width: 100%; max-width: 400px; background: var(--canvas);
  border: 1px solid var(--sand-300); border-radius: 10px;
  box-shadow: 0 18px 44px rgba(43,48,32,.14); overflow: hidden;
}
.auth-head {
  padding: 1.1rem 1.6rem; color: var(--white);
  background: linear-gradient(135deg, var(--brand-900), var(--brand-700));
  border-bottom: 3px solid var(--accent-500);
}
.auth-head h1 { font-size: 1.15rem; }
.auth-head p { margin: .25rem 0 0; font-size: .82rem; color: rgba(255,255,255,.85); }
.auth-body { padding: 1.4rem 1.6rem 1.6rem; }
.auth-body .btn { width: 100%; margin-top: .4rem; }
.auth-body .flash { margin-top: 0; margin-bottom: 1rem; }
/* ---------------- Term chips ---------------- */

.termchips { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-top: 1rem; }
.daybox { position: relative; }
.daybox input { position: absolute; opacity: 0; width: 0; height: 0; }
.daybox span {
  display: inline-block; padding: .25rem .45rem; min-width: 2.3rem; text-align: center;
  border: 1px solid var(--sand-300); border-radius: var(--radius);
  font-size: .74rem; font-weight: 700; color: var(--ink-soft); cursor: pointer;
}
.daybox input:checked + span { background: var(--brand-700); border-color: var(--brand-700); color: var(--white); }
.daybox input:focus-visible + span { outline: 2px solid var(--accent-400); outline-offset: 1px; }
.sched-times { display: flex; gap: .4rem; align-items: center; }
.sched-times select { width: auto; min-width: 7rem; }
.course-row { margin-bottom: .6rem; }
.exam-note {
  font-size: .78rem; font-weight: 700; color: var(--accent-700);
  background: var(--accent-050); border: 1px solid var(--accent-100);
  border-radius: var(--radius); padding: .5rem .7rem; margin-bottom: .85rem;
}
/* A labelled rule between two ways of doing the same thing. */
.or-rule {
  display: flex; align-items: center; gap: .7rem;
  margin: 1rem 0; color: var(--ink-soft);
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .09em;
}
.or-rule::before, .or-rule::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--rule);
}

/* Row actions in a table sit side by side and do not wrap — a narrow actions
   column otherwise stacks Edit above Remove and doubles the row height. The
   CMS calls this .row-btns; csms's markup calls it .btn-row, and inside a cell
   it should behave the same way. */
td .btn-row {
  display: inline-flex;
  flex-wrap: nowrap;
  gap: .3rem;
}

/* Width, exactly as the CMS does it.
 *
 * The page itself is never constrained — School Years, Classes and the Student
 * Database run edge to edge with the same 2rem gutter as upstream. Only a
 * class page is held to a column, which is upstream's #viewClass rule and its
 * reasoning: "a roster row has nothing useful to put in 1200px of width".
 *
 * The number is theirs too: 900px. */
.page.is-column {
  width: 100%;
  max-width: calc(900px + 4rem);
  margin-inline: auto;
}

/* A checkbox reads as a line of text with a box on it, not as a form field:
   .field stacks its children and stretches inputs, which turns a lone
   checkbox into a large square floating above its own label. */
.check-inline {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: .88rem;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
}

.check-inline input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--brand-700);
}

/* The activate control sits with the card's other actions; a form is a block
   by default and would push the pencil onto its own line. */
.cw-activate { display: inline-flex; margin: 0; }

/* The submitted file, named rather than labelled "File". It is a link, so it
   is coloured like one, and it truncates instead of pushing the status pill
   off the row. */
.sub-file {
  max-width: 16rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .82rem;
  font-weight: 600;
  color: var(--brand-700);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.sub-file:hover { color: var(--accent-700); }

/* --- A submission row -----------------------------------------------
   Four columns rather than a queue of spans: name takes the slack, then
   the file, then when it arrived, then the status. Fixed widths on the
   last three keep every row's status pill on the same vertical line, so
   the column reads down instead of jittering with the length of a
   filename. */
.sub-row { gap: 1rem; }

.sub-cell {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: .45rem;
  min-width: 0;
}

.sub-cell-file   { flex: 0 1 13rem; justify-content: flex-start; }
.sub-cell-when   { flex: 0 0 10.5rem; justify-content: flex-end;
                   font-size: .78rem; color: var(--ink-soft);
                   font-variant-numeric: tabular-nums; white-space: nowrap; }
.sub-cell-status { flex: 0 0 7rem; justify-content: flex-end; }

/* The name gives way before the columns do — it is the one part that can be
   read from a prefix, so it truncates rather than wrapping to two lines and
   making every row a different height. */
.sub-row .imp-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sub-row .sub-file { max-width: 100%; }

.sub-size { font-size: .74rem; color: var(--ink-soft); white-space: nowrap; }

@media (max-width: 760px) {
  .sub-cell-file, .sub-cell-when, .sub-cell-status { flex: 1 1 auto; }
}

/* A submitted file, shown where the marker already is. */
.sub-preview-by { margin: 0 0 .7rem; font-size: .84rem; color: var(--ink-soft); }

.sub-preview {
  display: grid;
  place-items: center;
  padding: .6rem;
  background: var(--sand-100);
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
}

.sub-preview img { max-width: 100%; max-height: 62vh; object-fit: contain; }

.sub-preview-frame {
  width: 100%;
  height: 62vh;
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  background: var(--sand-100);
}

/* --- The student's view of one activity ------------------------------
   Three plain bands — instructions, questions, your work — rather than a
   run of headings and bare paragraphs. Each band owns its heading, so the
   page reads as sections instead of a wall. */
.work-sec { margin-top: 1.25rem; }

.work-sec-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .6rem;
  margin: 0 0 .6rem;
  padding-bottom: .35rem;
  border-bottom: 2px solid var(--sand-200);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent-700);
}

.work-sec-note {
  margin-left: auto;
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ink-soft);
}

.work-text {
  margin: 0;
  padding: .55rem .8rem;
  border-left: 3px solid var(--sand-300);
  background: var(--surface);
  border-radius: 0 var(--radius) var(--radius) 0;
  white-space: pre-wrap;
  /* .85rem is the size the CMS uses for body text inside a panel; the page
     default is for prose, not for a boxed note. */
  font-size: .85rem;
}

.work-empty {
  margin: 0;
  padding: .6rem .2rem;
  font-size: .84rem;
  font-style: italic;
  color: var(--ink-soft);
}

/* The deadline, on its own line — the one fact that changes what to do next. */
.due-strip {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-top: .8rem;
  padding: .42rem .7rem;
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: .82rem;
  color: var(--ink-soft);
}

.due-strip.is-overdue {
  border-color: var(--accent-400);
  background: var(--accent-050);
  color: var(--accent-700);
}

.due-strip strong { color: inherit; }

/* --- The files handed in --------------------------------------------- */
.file-list {
  border: 1px solid var(--sand-300);
  border-radius: var(--radius);
  background: var(--canvas);
  overflow: hidden;
}

.file-row {
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: .4rem .6rem;
  border-bottom: 1px solid var(--sand-200);
}

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

.file-num {
  flex: 0 0 1.4rem;
  font-size: .76rem;
  color: var(--ink-soft);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.file-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }

.file-name {
  font-size: .86rem;
  font-weight: 600;
  color: var(--brand-700);
  text-decoration: underline;
  text-underline-offset: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-name:hover { color: var(--accent-700); }
.file-meta { font-size: .74rem; color: var(--ink-soft); }
.file-remove { flex: 0 0 auto; margin: 0; }

/* --- Adding one ------------------------------------------------------ */
.upload-box {
  margin-top: .8rem;
  padding: .7rem .8rem;
  border: 2px dashed var(--accent-400);
  border-radius: var(--radius);
}

.upload-label {
  display: block;
  margin-bottom: .5rem;
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--brand-700);
}

.upload-row { display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; }
.upload-row input[type="file"] { flex: 1 1 16rem; min-width: 0; }
.upload-hint { margin: .6rem 0 0; font-size: .78rem; color: var(--ink-soft); }

/* Only shown when a student handed in more than one file — otherwise the
   status pill already says everything. */
.file-count {
  font-size: .74rem;
  font-weight: 600;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* Several files stack inside the one cell rather than widening the row. */
.sub-cell-file { flex-wrap: wrap; row-gap: .15rem; }
.sub-cell-file .sub-file { flex: 1 1 100%; }

/* Staged files are the student's own pile — visibly not handed in yet, so the
   difference between "on my screen" and "with my teacher" is never in doubt. */
.file-list.is-staged {
  border-style: dashed;
  border-color: var(--accent-400);
  /* The dashed line already says "not handed in". A fill on top of it made
     two heavy blocks stacked on each other. */
}

.file-name.is-plain {
  color: var(--ink);
  text-decoration: none;
  font-weight: 600;
}

/* The one control that actually hands work over. */
.turnin-bar {
  display: flex;
  align-items: center;
  gap: .9rem;
  flex-wrap: wrap;
  margin: .8rem 0 0;
  padding: .55rem .8rem;
  border: 1px solid var(--brand-600);
  border-radius: var(--radius);
  background: var(--brand-050);
}

.turnin-note { flex: 1 1 14rem; font-size: .82rem; color: var(--brand-800); }

/* A paperclip that has something on it says how many. */
.cw-iconbtn.has-files { color: var(--brand-700); }
.clip-n {
  font-size: .68rem;
  font-weight: 700;
  margin-left: .1rem;
  vertical-align: super;
}

/* Forms that exist only so a button elsewhere can submit them. */
.is-hidden { display: none; }

/* A finished activity's title in the history list — a link, but it should not
   shout over the section heading it sits in. */
.history-title {
  font-size: .84rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: none;
  color: var(--brand-800);
}
.history-title:hover { color: var(--accent-700); }

/* The typed-course box in the bulk bar: narrow, so the row still reads as a
   row of controls rather than a form. */
.bulk-newcourse {
  width: auto;
  min-width: 0;
  flex: 0 1 11rem;
  min-height: 0;
  padding: .34rem .55rem;
  font-size: .82rem;
}

/* The two narrowing dropdowns on the import dialog, as a compact row under
   the search field — auto width, not a stack of full-width controls. */
.dir-filters {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem;
  margin: -.3rem 0 .9rem;
}

.dir-filters .filter-select { flex: 0 0 auto; }

.dir-filters .btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 1.9rem;
}

/* Sits where the Download all button would be when there is nothing to
   download, so the bar does not change shape as a term fills up. */
.listbar-note {
  font-size: .8rem;
  font-style: italic;
  color: var(--ink-soft);
}

/* The class menu is a <details> so it works without scripts. A <summary>
   carries a disclosure triangle and its own display mode, neither of which
   belongs on a round icon button. */
#classListWrap > summary {
  list-style: none;
  cursor: pointer;
}

#classListWrap > summary::-webkit-details-marker { display: none; }
#classListWrap > summary::marker { content: ""; }

/* Upstream's menu rows are <button>s; these are links, which arrive with
   underlines and inline layout. */
.datamenu a.datamenu-item {
  display: block;
  text-decoration: none;
}

/* The number box on the bulk-numbers dialog: fixed width at the end of the
   row, so the boxes line up in a column whatever the names do. */
.no-box {
  flex: 0 0 7.5rem;
  min-height: 0;
  padding: .3rem .5rem;
  font-size: .85rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* A row whose number clashes: the reason sits under the name, and the box to
   change it is on the same line. Terracotta, as everywhere that warns. */
.import-row.is-clash { background: var(--accent-050); }
.no-box.is-clash { border-color: var(--accent-500); }

.clash-why {
  display: block;
  font-size: .74rem;
  font-style: italic;
  color: var(--accent-700);
}

.clash-list { margin: .4rem 0 0; padding-left: 1.1rem; font-size: .82rem; }
.clash-list li { margin: .2rem 0; }
.clash-list .clash-why { display: inline; font-style: italic; }

/* The class header on the student side: the title, then everything else on
   one quiet line beneath it. It was three stacked rows, which read as three
   separate statements rather than one class. */
.class-meta {
  margin: 1em 0 0;
  font-size: .88rem;
  color: var(--ink-soft);
  line-height: 1.6;
}

/* The code stays at full strength — it is what identifies the class at a
   glance, and everything after it is context. */
.class-meta .mono { color: var(--ink); font-weight: 700; }

/* The student class page draws one horizontal rule, not three.
   Upstream's class page has only the tab strip's underline — the heading is
   screen-reader-only there and the tab strip doubles as the page header. Here
   the class name is worth showing, but the head and the count row were each
   bringing a rule of their own, so three stacked up before any content did. */
.page-head.is-plain {
  padding-bottom: 0;
  border-bottom: 0;
}

.listbar.is-plain {
  margin-top: .7rem;
  padding-bottom: .15rem;
  border-bottom: 0;
}

/* Active-classwork list: an activity nobody has finished and whose date has
   passed is the one thing on this page worth catching the eye. Terracotta,
   as everywhere that warns — a tint rather than a border, so a page of them
   does not read as an emergency. */
.cw-card.is-late { background: var(--accent-050); }

/* The count once everyone is in: the row can be stopped worrying about. */
.cw-bit.is-agreed { color: var(--brand-700); font-weight: 600; }
