/* EB Garamond, the typeface eis-tagamo3.org uses. Self-hosted so the CSP can
   stay at default-src 'self' and the app makes no third-party requests. One
   variable file covers every weight. Headings only: the system sans stays on
   interface text and data tables, which are more legible at small sizes in a
   dense grid. */
@font-face {
  font-family: "EB Garamond";
  src: url("/fonts/eb-garamond.woff2") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: light;
  --ink: #262626;
  --muted: #6b6b6b;   /* 5.31:1 on white */
  --line: #e4e0e0;
  /* 3.71:1 — control boundaries must be perceivable; #cfc8c8 was 1.65:1. */
  --line-strong: #8a8384;
  --surface: #ffffff;
  --surface-soft: #faf7f7;
  --page: #f2eeee;
  /* The SYSTEM's colour, and only the system's.
     It was #b02a2f, sampled from eis-tagamo3.org -- one school's brand, worn by
     every page this software serves. Correct while it served that school, and
     wrong the moment it served two: another school's teachers would sign in
     under a colour taken from a school they have never heard of.
     #2f4b7c carries white text at 8.68:1, against 6.53:1 for the red it
     replaces, so nothing became harder to read. A school with a colour of its
     own overrides all four of these from /brand.css. */
  --primary: #2f4b7c;
  --primary-strong: #263c63;
  --primary-soft: #eef1f5;
  /* The tint behind the sign-in page. A token because it used to be this
     school's red written into a gradient, where no school could override it. */
  --primary-glow: rgb(47 75 124 / 0.16);
  /* The same colour again, fainter, behind every signed-in page. */
  --primary-wash: rgb(47 75 124 / 0.06);
  --accent: #0f766e;
  /* Destructive actions sit in the same hue family as the brand, so they are
     separated by treatment rather than colour: primary is a solid fill, danger
     is a soft fill with a border. Never make them differ by hue alone. */
  --danger: #98181d;
  --danger-soft: #fdf1f1;
  --warning: #a15c00;
  --warning-soft: #fff7e8;
  --success: #0b7a4b;
  --success-soft: #eaf8f1;
  --shadow-sm: 0 1px 2px rgb(38 24 24 / 0.06), 0 1px 3px rgb(38 24 24 / 0.08);
  --shadow-lg: 0 18px 48px rgb(60 30 32 / 0.16);
  --radius: 14px;
  --radius-sm: 9px;
  --display: "EB Garamond", Georgia, "Times New Roman", serif;
}


* {
  box-sizing: border-box;
}

html {
  min-width: 320px;
  /* The topbar is sticky and 68px tall, so an anchor or a focused element would
     otherwise land underneath it. */
  scroll-padding-top: 5rem;
  background: var(--page);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: var(--ink);
}

body {
  margin: 0;
  min-height: 100vh;
  background:
    radial-gradient(circle at 8% 0%, var(--primary-wash), transparent 30rem),
    var(--page);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

button,
input,
select,
textarea {
  font: inherit;
}

button,
select {
  cursor: pointer;
}

a {
  color: var(--primary);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1,
h2,
.card-title,
.brand-text,
.auth-brand h1 {
  font-family: var(--display);
  letter-spacing: 0;
}

h1 {
  margin-bottom: 0.25rem;
  font-size: clamp(1.55rem, 3vw, 2.15rem);
  line-height: 1.15;
  letter-spacing: -0.03em;
}

h2 {
  line-height: 1.25;
}

.topbar {
  position: sticky;
  z-index: 50;
  top: 0;
  border-bottom: 1px solid rgb(219 210 211 / 0.8);
  background: rgb(255 255 255 / 0.92);
  box-shadow: 0 1px 0 rgb(255 255 255 / 0.8);
  backdrop-filter: blur(14px);
}

.topbar-inner {
  display: grid;
  grid-template-columns: minmax(150px, 1fr) auto minmax(150px, 1fr);
  align-items: center;
  gap: 1rem;
  width: min(1160px, calc(100% - 2rem));
  min-height: 68px;
  margin: 0 auto;
}

.topbar-inner.wide {
  width: min(1500px, calc(100% - 2rem));
}

.brand,
.user-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  min-width: 0;
  color: var(--ink);
  font-weight: 750;
}

.brand:hover,
.user-chip:hover {
  text-decoration: none;
}

.brand-mark {
  display: block;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  object-fit: contain;
  /* The supplied logo is a JPEG on white, so it needs a white plate to sit on
     rather than a transparent one. */
  border-radius: 8px;
  background: #ffffff;
}

.brand-mark-lg {
  width: 84px;
  height: 84px;
  margin: 0 auto 0.85rem;
  border-radius: 12px;
}

/* A school that has not supplied a logo gets its own initials rather than
   somebody else's crest (D-036). Sized from the box so one rule serves both the
   38px header mark and the 84px sign-in mark. */
.brand-initials {
  display: grid;
  place-items: center;
  background: var(--accent, #1f3d7a);
  color: #ffffff;
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  user-select: none;
}

.brand-mark-lg.brand-initials {
  font-size: 2rem;
}

.brand-text,
.user-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav {
  display: flex;
  align-self: stretch;
  gap: 0.2rem;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  border-bottom: 3px solid transparent;
  padding: 0.15rem 0.8rem 0;
  color: var(--muted);
  font-size: 0.92rem;
  font-weight: 650;
}

.nav-link:hover {
  color: var(--primary);
  text-decoration: none;
}

.nav-link.is-active {
  border-color: var(--primary);
  color: var(--primary);
}

.topbar-user {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.6rem;
  min-width: 0;
}

.avatar {
  display: inline-grid;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  place-items: center;
  border: 1px solid #f0d9da;
  border-radius: 50%;
  background: var(--primary-soft);
  color: var(--primary);
  font-size: 0.78rem;
  font-weight: 800;
}

.page {
  width: min(1160px, calc(100% - 2rem));
  margin: 0 auto;
  padding: 2.25rem 0 4rem;
}

.page.wide {
  width: min(1500px, calc(100% - 2rem));
}

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

.page-sub,
.muted,
.hint,
.auth-sub,
.auth-official,
.assign-meta {
  color: var(--muted);
}

/* The formal name, under the name staff know the school by. Quieter than the
   heading on purpose: it is there for the person checking they are at the right
   school, not for everyday use. */
.auth-official {
  margin: 0.15rem 0 0;
  font-size: 0.85rem;
  text-wrap: balance;
}

/* A school's own mark is whatever shape the school sent. Bound the height and
   let the width follow, so a wide banner does not push the sign-in card apart
   and a tall crest does not walk down the page. */
.brand-mark,
.brand-mark-lg {
  object-fit: contain;
}

.page-sub {
  margin-bottom: 0;
}

.small {
  font-size: 0.85rem;
}

.inline,
.inline-form {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}

.stack {
  display: grid;
  gap: 1.1rem;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem;
}

.card,
.assign-card,
.auth-card,
.empty {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.card,
.empty {
  padding: 1.35rem;
}

.card-narrow {
  max-width: 650px;
}

.card-title {
  margin-bottom: 1rem;
  font-size: 1.05rem;
}

.rule {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 1.35rem 0;
}

.empty {
  padding: 3.5rem 1.5rem;
  text-align: center;
}

.empty h2 {
  margin-bottom: 0.5rem;
}

.btn {
  display: inline-flex;
  min-height: 40px;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.85rem;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 1px 1px rgb(38 24 24 / 0.03);
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1.1;
  text-align: center;
}

.btn:hover {
  border-color: #d99a9d;
  color: var(--primary);
  text-decoration: none;
}

/* An outline, not a shadow: box-shadow is discarded in forced-colors mode, so
   the previous ring left high-contrast users with no focus indicator at all.
   #262626 is 15.13:1 on white, and the 2px offset reads 6.53:1 on crimson. */
:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 2px;
}

.btn:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 2px;
}

.btn-primary {
  border-color: var(--primary);
  background: var(--primary);
  color: white;
}

.btn-primary:hover {
  border-color: var(--primary-strong);
  background: var(--primary-strong);
  color: white;
}

.btn-ghost {
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}

.btn-danger {
  border-color: #f0b7b2;
  background: var(--danger-soft);
  color: var(--danger);
}

.btn-sm {
  min-height: 34px;
  padding: 0.4rem 0.65rem;
  font-size: 0.82rem;
}

.btn-block {
  width: 100%;
}

.field {
  display: grid;
  gap: 0.38rem;
}

.field-inline {
  display: grid;
  gap: 0.25rem;
}

.label {
  color: #3b3536;
  font-size: 0.82rem;
  font-weight: 750;
}

.hint {
  font-size: 0.8rem;
}

input,
select,
textarea {
  width: 100%;
  min-height: 42px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 0.55rem 0.7rem;
  background: var(--surface);
  color: var(--ink);
}

input:hover,
select:hover,
textarea:hover {
  border-color: #b8b0b1;
}

input::placeholder,
textarea::placeholder {
  color: #767676;
}

.flash {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.8rem 1rem;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.flash[hidden] {
  display: none;
}

.flash-success {
  border-color: #a9dec9;
  background: var(--success-soft);
  color: #086146;
}

.flash-error {
  border-color: #f3b8b2;
  background: var(--danger-soft);
  color: #912018;
}

.flash-info {
  border-color: #eec9cb;
  background: var(--primary-soft);
  color: #7a2027;
}

.auth-body {
  display: grid;
  min-height: 100vh;
  place-items: center;
  padding: 1rem;
  background:
    radial-gradient(circle at 50% -10%, var(--primary-glow), transparent 32rem),
    linear-gradient(150deg, var(--surface-soft), var(--page) 70%);
}

.auth-body .page {
  width: min(440px, 100%);
  padding: 0;
}

/* ---------------------------------------------------------------- landing

   The system's front door, at any address not bound to a school. Longer than
   the sign-in card, so it scrolls from the top rather than sitting centred in
   the viewport, and it keeps the same quiet surface: this page is read once by
   a school considering the system and passed through daily by teachers whose
   link lost its school. Neither is served by decoration.                     */

.landing-body {
  min-height: 100vh;
  padding: 1rem;
  background:
    radial-gradient(circle at 50% -10%, var(--primary-glow), transparent 34rem),
    linear-gradient(150deg, var(--surface-soft), var(--page) 70%);
}

.landing-body .page {
  width: min(46rem, 100%);
  margin-inline: auto;
  padding: 0;
}

.landing {
  display: grid;
  gap: 1.5rem;
  padding-block: clamp(1.5rem, 6vw, 3rem);
}

.landing-head {
  text-align: center;
}

.landing-head h1 {
  margin: 0.6rem 0 0;
  font-size: clamp(1.5rem, 4.5vw, 2rem);
  text-wrap: balance;
}

.landing-lede {
  max-width: 34rem;
  margin: 0.6rem auto 0;
  color: var(--muted);
  text-wrap: balance;
}

/* The way in comes first and looks like the thing to use: a teacher whose link
   lost its school is the visitor in a hurry. */
.landing-enter {
  padding: clamp(1.35rem, 4vw, 1.85rem);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}

.landing-enter h2,
.landing-what h2 {
  margin: 0 0 0.9rem;
  font-size: 1.05rem;
}

.landing-what {
  padding-inline: 0.25rem;
}

.landing-points {
  display: grid;
  gap: 0.9rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.landing-points li {
  padding-left: 0.9rem;
  border-left: 2px solid var(--line-strong);
  color: var(--muted);
  line-height: 1.55;
}

.landing-points strong {
  color: var(--ink);
}

.landing-foot {
  padding-top: 0.25rem;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
}

.landing-foot p {
  max-width: 34rem;
  margin: 0.9rem auto 0;
}

.auth-card {
  padding: clamp(1.5rem, 5vw, 2.25rem);
  box-shadow: var(--shadow-lg);
}

.auth-brand {
  margin-bottom: 1.5rem;
  text-align: center;
}

.auth-brand h1 {
  margin-bottom: 0.2rem;
  font-size: 1.65rem;
}

.auth-help {
  margin: 1.25rem 0 0;
  color: var(--muted);
  font-size: 0.82rem;
  text-align: center;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
}

.assign-card {
  position: relative;
  overflow: hidden;
  padding: 1.2rem;
  transition: transform 140ms ease, box-shadow 140ms ease;
}

.assign-card::before {
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: var(--primary);
  content: "";
}

.assign-card.is-done::before {
  background: var(--success);
}

.assign-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 22px rgb(60 30 32 / 0.10);
}

.assign-card-top,
.assign-actions,
.save-actions,
.toolbar-actions {
  display: flex;
  align-items: center;
  gap: 0.55rem;
}

.assign-card-top {
  justify-content: space-between;
}

.assign-class {
  margin: 1rem 0 0.25rem;
  font-size: 1.35rem;
}

.assign-actions {
  margin-top: 1rem;
}

.pill {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 0.25rem 0.55rem;
  font-size: 0.74rem;
  font-weight: 800;
}

.pill-subject {
  background: var(--primary-soft);
  color: var(--primary);
}

.pill-done {
  background: var(--success-soft);
  color: var(--success);
}

.pill-todo {
  background: var(--warning-soft);
  color: var(--warning);
}

.pill-muted {
  background: #eef1f5;
  color: #5f5658;
}

.table-wrap {
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.table {
  width: 100%;
  border-collapse: collapse;
  white-space: nowrap;
}

.table th,
.table td {
  border-bottom: 1px solid var(--line);
  padding: 0.8rem 0.9rem;
  text-align: left;
}

.table th {
  background: var(--surface-soft);
  color: #4d4547;
  font-size: 0.76rem;
  letter-spacing: 0.025em;
  text-transform: uppercase;
}

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

.table tbody tr:hover,
.table .is-highlight {
  background: #fafaff;
}

.table .num {
  text-align: right;
}

.row-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.4rem;
}

/* Administration workspace */

.admin-nav {
  display: flex;
  /* Wraps rather than scrolling: nine pills in a horizontal scroller put three
     of them off-screen on a phone with nothing to say they were there. */
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: -0.35rem 0 1.25rem;
  padding: 0.35rem 0 0.65rem;
  scrollbar-width: thin;
}

.admin-nav a {
  flex: 0 0 auto;
  border-radius: 999px;
  padding: 0.45rem 0.75rem;
  color: var(--muted);
  font-size: 0.84rem;
  font-weight: 750;
}

.admin-nav a:hover,
.admin-nav a.is-active {
  background: var(--primary-soft);
  color: var(--primary);
  text-decoration: none;
}

.admin-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(145px, 1fr));
  gap: 0.85rem;
  margin-bottom: 1.25rem;
}

.admin-stat {
  display: grid;
  gap: 0.25rem;
  color: var(--ink);
}

.admin-stat:hover {
  border-color: #d99a9d;
  text-decoration: none;
}

.admin-stat strong {
  color: var(--ink);
  font-size: 1.85rem;
  line-height: 1;
}

.admin-stat span {
  color: var(--muted);
  font-size: 0.84rem;
  font-weight: 700;
}

.admin-split {
  display: grid;
  grid-template-columns: minmax(260px, 0.36fr) minmax(0, 1fr);
  gap: 1rem;
}

.admin-form-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(180px, 1fr));
  align-items: end;
  gap: 0.9rem;
}

.admin-form-wide {
  grid-column: 1 / -1;
}

.admin-form-submit {
  justify-self: start;
}

.admin-details summary {
  color: var(--primary);
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 700;
}

.admin-reset-form {
  margin-top: 0.5rem;
}

.admin-reset-form input {
  width: 190px;
}

.import-ok {
  color: var(--success);
  font-weight: 700;
}

.import-errors,
.import-row-error td:last-child {
  color: var(--danger);
  font-weight: 650;
}

.import-row-error {
  background: var(--danger-soft);
}


/* ------------------------------------------------------------ Grading */

/* The screen renders a session as a DIFF, not as a form.
 *
 * Every student is one collapsed row showing their five current scores. The
 * default is pre-selected, so a freshly loaded page is entirely uncoloured and
 * every spot of colour means "something was changed here". That is the question
 * a teacher actually asks before saving, and the old design could not answer it:
 * 110 identical crimson blocks encoded the answer in their POSITION.
 *
 * Colour therefore encodes DISTANCE FROM THE DEFAULT, never the value itself.
 * A 5 is never red. A 1 is, and because that is rare it is loud.
 */

/* Ordered by lightness first, so the ramp survives greyscale, print and
   red-green colour blindness. Every ink clears 7:1 on its own fill. */
:root {
  --mark-fill: #efeceb;
  --mark-line: #b3aaa8;
  --mark-ink: #2a2422;
  --dev1-fill: #fdf1da;
  --dev1-line: #d8b26a;
  --dev1-ink: #7a5200;
  --dev2-fill: #f7e3e6;
  --dev2-line: #c98793;
  --dev2-ink: #6f1a26;
  --devup-fill: #e4f3ec;
  --devup-line: #7cbfa1;
  --devup-ink: #0a6b45;
  --t-row: 15px;
  --t-grid: 13px;
  --t-meta: 11px;
}

/* ------------------------------------------------------- session header */

.sess-head {
  margin-bottom: 0.9rem;
}

.sess-back {
  display: inline-block;
  margin-bottom: 0.35rem;
  font-size: 0.85rem;
  font-weight: 600;
}

.sess-title {
  margin-bottom: 0.15rem;
  font-size: clamp(1.3rem, 2.6vw, 1.8rem);
}

.sess-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  align-items: center;
  margin-bottom: 0;
  color: var(--muted);
  font-size: 0.92rem;
}

.sess-note {
  margin-bottom: 0.9rem;
  color: var(--muted);
  font-size: 0.88rem;
}

/* The class, date and period arrive from the dashboard link. Changing them is
   rare, so it is one line rather than four fields above the register. */
.sess-change {
  margin-bottom: 1rem;
}

.sess-change > summary {
  display: inline-block;
  border-radius: var(--radius-sm);
  padding: 0.35rem 0.6rem;
  color: var(--primary);
  font-size: 0.85rem;
  font-weight: 650;
  cursor: pointer;
}

.sess-change > summary:hover {
  background: var(--primary-soft);
}

.sess-change[open] > summary {
  margin-bottom: 0.6rem;
}

/* ------------------------------------------------------------ the roster */

.roster-head {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 0.75rem;
  align-items: end;
  padding: 0 0.75rem 0.4rem;
  color: var(--muted);
  font-size: var(--t-meta);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.rh-strip {
  display: grid;
  grid-auto-flow: column;
  gap: 4px;
}

.rh-item {
  width: 34px;
  text-align: center;
}

.rh-scale {
  width: 3ch;
  text-align: right;
}

.roster {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.srow + .srow {
  border-top: 1px solid var(--line);
}

/* A row the teacher has actually changed carries a rail, so "who did I already
   adjust?" is answered by one vertical scan. */
.srow.is-changed,
.srow:has(.chip[data-rel="d1"] input:checked, .chip[data-rel="d2"] input:checked, .chip[data-rel="up"] input:checked) {
  box-shadow: inset 3px 0 0 var(--dev1-line);
}

.srow-sum {
  display: grid;
  grid-template-columns: 1.6rem 1fr auto;
  gap: 0.75rem;
  align-items: center;
  min-height: 56px;
  padding: 0.35rem 0.75rem;
  cursor: pointer;
  list-style: none;
}

.srow-sum::-webkit-details-marker {
  display: none;
}

.srow-sum:hover {
  background: var(--surface-soft);
}

.srow[open] > .srow-sum {
  border-bottom: 1px solid var(--line);
  background: var(--surface-soft);
}

.srow-num {
  color: var(--muted);
  font-size: var(--t-meta);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.srow-name {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  min-width: 0;
  font-size: var(--t-row);
  font-weight: 600;
}

.srow-flag {
  border-radius: 4px;
  padding: 0.05rem 0.3rem;
  background: var(--mark-fill);
  color: var(--mark-ink);
  font-size: var(--t-meta);
  font-weight: 700;
}

.srow-flag.flag-absent {
  background: var(--dev2-fill);
  color: var(--dev2-ink);
}

.srow-flag.flag-late {
  background: var(--dev1-fill);
  color: var(--dev1-ink);
}

/* A child who joined this class after the lesson being edited. Neutral, not an
   alarm: it marks something to notice, not something wrong. Left untouched they
   are not recorded at all -- see src/routes/grade.js. */
.srow-flag.flag-new {
  background: var(--surface-soft);
  color: var(--muted);
  border: 1px solid var(--line-strong);
}

.srow-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--dev1-line);
}

/* ----------------------------------------------------------- score strip */

.srow-strip {
  display: grid;
  grid-auto-flow: column;
  gap: 4px;
}

.st {
  display: grid;
  width: 34px;
  height: 44px;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 7px;
  color: var(--muted);
  font-size: var(--t-grid);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

/* Tap a digit to lower it by one. A pointer accelerator only: the keyboard
   path is the real radio group inside the row, which needs no extra ARIA. */
.st.is-tappable {
  cursor: pointer;
}

.st.is-tappable:hover {
  border-color: var(--line-strong);
}

/* Both statuses that carry no scores, and the .is-away class paintRow() adds.
   This rule had neither: the collapsed row's strip was dimmed only for
   'absent', so an excused child's default digits sat at full opacity in the
   roster — indistinguishable from a child actually being marked, though both
   are saved with no scores at all. See UNSCORED_STATUSES in src/lib/util.js. */
.srow.is-away .srow-strip,
.srow:has(.att input[value="absent"]:checked) .srow-strip,
.srow:has(.att input[value="excused"]:checked) .srow-strip {
  opacity: 0.35;
}

/* The open row, too: an absent or excused student's scores are deleted on save,
   so the chips should not look like they are still taking marks. :has() covers
   the no-JavaScript path; paintRow() adds .is-away and disables the inputs when
   the script is running. */
.srow.is-away .gitem,
.srow:has(.att input[value="absent"]:checked) .gitem,
.srow:has(.att input[value="excused"]:checked) .gitem {
  opacity: 0.45;
}


/* Strip colour, two ways so it is never a lie.
   :has() keeps the resting strip honest when a value is changed inside an
   open row with JavaScript off; the .rel-* classes are what the script sets
   when it is on. Both resolve to the same declarations. */
.srow:has(.gi-1 .chip[data-rel="d1"] input:checked) .st-1,
.srow:has(.gi-2 .chip[data-rel="d1"] input:checked) .st-2,
.srow:has(.gi-3 .chip[data-rel="d1"] input:checked) .st-3,
.srow:has(.gi-4 .chip[data-rel="d1"] input:checked) .st-4,
.srow:has(.gi-5 .chip[data-rel="d1"] input:checked) .st-5,
.srow:has(.gi-6 .chip[data-rel="d1"] input:checked) .st-6,
.srow:has(.gi-7 .chip[data-rel="d1"] input:checked) .st-7,
.srow:has(.gi-8 .chip[data-rel="d1"] input:checked) .st-8,
.srow:has(.gi-9 .chip[data-rel="d1"] input:checked) .st-9,
.srow:has(.gi-10 .chip[data-rel="d1"] input:checked) .st-10,
.st.rel-d1 {
  border-color: var(--dev1-line);
  background: var(--dev1-fill);
  color: var(--dev1-ink);
  box-shadow: inset 0 -2px 0 var(--dev1-line);
}

.srow:has(.gi-1 .chip[data-rel="d2"] input:checked) .st-1,
.srow:has(.gi-2 .chip[data-rel="d2"] input:checked) .st-2,
.srow:has(.gi-3 .chip[data-rel="d2"] input:checked) .st-3,
.srow:has(.gi-4 .chip[data-rel="d2"] input:checked) .st-4,
.srow:has(.gi-5 .chip[data-rel="d2"] input:checked) .st-5,
.srow:has(.gi-6 .chip[data-rel="d2"] input:checked) .st-6,
.srow:has(.gi-7 .chip[data-rel="d2"] input:checked) .st-7,
.srow:has(.gi-8 .chip[data-rel="d2"] input:checked) .st-8,
.srow:has(.gi-9 .chip[data-rel="d2"] input:checked) .st-9,
.srow:has(.gi-10 .chip[data-rel="d2"] input:checked) .st-10,
.st.rel-d2 {
  border-color: var(--dev2-line);
  background: var(--dev2-fill);
  color: var(--dev2-ink);
  box-shadow: inset 0 -3px 0 var(--dev2-line);
}

.srow:has(.gi-1 .chip[data-rel="up"] input:checked) .st-1,
.srow:has(.gi-2 .chip[data-rel="up"] input:checked) .st-2,
.srow:has(.gi-3 .chip[data-rel="up"] input:checked) .st-3,
.srow:has(.gi-4 .chip[data-rel="up"] input:checked) .st-4,
.srow:has(.gi-5 .chip[data-rel="up"] input:checked) .st-5,
.srow:has(.gi-6 .chip[data-rel="up"] input:checked) .st-6,
.srow:has(.gi-7 .chip[data-rel="up"] input:checked) .st-7,
.srow:has(.gi-8 .chip[data-rel="up"] input:checked) .st-8,
.srow:has(.gi-9 .chip[data-rel="up"] input:checked) .st-9,
.srow:has(.gi-10 .chip[data-rel="up"] input:checked) .st-10,
.st.rel-up {
  border-color: var(--devup-line);
  background: var(--devup-fill);
  color: var(--devup-ink);
  box-shadow: inset 0 -2px 0 var(--devup-line);
}

/* ------------------------------------------------------------ open row */

/* Scoped to [open] deliberately. Setting `display` unconditionally overrides the
   browser's own hiding of closed <details> content, which leaves every "closed"
   row fully laid out: 31 focusable elements each, all in the tab order, and the
   collapse only apparent because .roster clips the overflow. */
.srow[open] > .srow-body {
  display: grid;
  gap: 0.6rem;
  padding: 0.85rem 0.75rem 1rem;
}

.srow-full {
  margin-bottom: 0.15rem;
  font-size: 0.9rem;
}

.srow-full .muted {
  margin-left: 0.5rem;
  font-size: var(--t-meta);
}

.gitem,
.att {
  display: grid;
  grid-template-columns: 9.5rem 1fr;
  gap: 0.6rem;
  align-items: center;
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

.gc-label {
  color: var(--ink);
  font-size: var(--t-grid);
  font-weight: 650;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Unselected chips are invisible until touched. This deletes 440 boxes with one
   declaration and is the single largest density win on the page; the hit area
   is unchanged. */
.chip {
  position: relative;
  display: grid;
  min-width: 40px;
  min-height: 40px;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 0 0.55rem;
  color: #767676;
  font-size: var(--t-grid);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  cursor: pointer;
}

.chip input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.chip:hover {
  border-color: var(--line-strong);
}

.chip input:focus-visible + span {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

.chip span {
  display: block;
  line-height: 1;
}

/* Selected, at the default: unmistakable, but no hue. */
.chip input:checked + span {
  color: var(--mark-ink);
}

.chip:has(input:checked) {
  border-color: var(--mark-line);
  background: var(--mark-fill);
  font-weight: 700;
}

/* Selected and deviating. The bottom rule thickens with distance, so the signal
   survives greyscale and print without relying on colour. */
.chip[data-rel="d1"]:has(input:checked) {
  border-width: 2px;
  border-color: var(--dev1-line);
  background: var(--dev1-fill);
  box-shadow: inset 0 -3px 0 var(--dev1-line);
}

.chip[data-rel="d1"] input:checked + span {
  color: var(--dev1-ink);
}

.chip[data-rel="d2"]:has(input:checked) {
  border-width: 2px;
  border-color: var(--dev2-line);
  background: var(--dev2-fill);
  box-shadow: inset 0 -4px 0 var(--dev2-line);
}

.chip[data-rel="d2"] input:checked + span {
  color: var(--dev2-ink);
}

.chip[data-rel="up"]:has(input:checked) {
  border-width: 2px;
  border-color: var(--devup-line);
  background: var(--devup-fill);
  box-shadow: inset 0 -2px 0 var(--devup-line);
}

.chip[data-rel="up"] input:checked + span {
  color: var(--devup-ink);
}

/* Attendance is four labelled chips, not a native picker: it is the most common
   non-default action and a select costs two taps and a system sheet. */
.chip-att {
  min-width: 5.5rem;
}

.chip-att[data-att="absent"]:has(input:checked) {
  border-color: var(--dev2-line);
  background: var(--dev2-fill);
}

.chip-att[data-att="absent"] input:checked + span {
  color: var(--dev2-ink);
}

.chip-att[data-att="late"]:has(input:checked) {
  border-color: var(--dev1-line);
  background: var(--dev1-fill);
}

.chip-att[data-att="late"] input:checked + span {
  color: var(--dev1-ink);
}

.srow-foot {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.75rem;
  align-items: end;
  margin-top: 0.25rem;
}

.srow-total {
  margin-bottom: 0.35rem;
  font-size: var(--t-row);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  white-space: nowrap;
}

.total-max {
  color: var(--muted);
  font-size: var(--t-meta);
  font-weight: 600;
}

/* -------------------------------------------------------------- the end */

/* A list with a visible end is a list you know you finished. */
.end-block {
  display: grid;
  gap: 0.9rem;
  margin-top: 1.1rem;
}

.end-line {
  margin-bottom: 0;
  color: var(--muted);
  font-size: 0.9rem;
}

.end-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
}

.bulk-mounted {
  display: grid;
  gap: 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.85rem;
  background: var(--surface-soft);
}

.bulk-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
}

.bulk-row .label {
  min-width: 9rem;
}

/* ----------------------------------------------------------- save bar */

.save-bar {
  position: sticky;
  z-index: 40;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  margin-top: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.6rem 0.9rem;
  background: rgb(255 255 255 / 0.96);
  box-shadow: 0 -2px 14px rgb(60 30 32 / 0.10);
  backdrop-filter: blur(10px);
}

.save-summary {
  margin-bottom: 0;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
}

.save-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.draft-status {
  align-items: center;
}

/* Rows must never be hidden behind the sticky bar when jumped to. */
.srow {
  scroll-margin-bottom: 5rem;
  scroll-margin-top: 4.5rem;
}

/* ------------------------------------------------------------ responsive */

@media (max-width: 720px) {
  .gitem,
  .att {
    grid-template-columns: 1fr;
    gap: 0.3rem;
  }

  .srow-foot {
    grid-template-columns: 1fr;
    align-items: stretch;
  }

  .roster-head .rh-name {
    display: none;
  }

  .roster-head {
    grid-template-columns: 1fr auto;
    justify-items: end;
  }
}

@media (max-width: 420px) {
  .st,
  .rh-item {
    width: 30px;
  }

  .srow-sum {
    gap: 0.5rem;
    padding: 0.35rem 0.5rem;
  }
}

/* Size by input type, not by viewport, so a large tablet still gets fingers. */
@media (pointer: coarse) {
  .chip {
    min-width: 46px;
    min-height: 46px;
  }

  .chip-att {
    min-width: 6.5rem;
  }

  .st {
    height: 48px;
  }
}

/* The real radio is opacity:0 and selection is carried by background alone,
   which Windows high-contrast discards. */
@media (forced-colors: active) {
  .chip {
    border: 1px solid ButtonBorder;
  }

  .chip:has(input:checked) {
    forced-color-adjust: none;
    border-color: Highlight;
    background: Highlight;
    color: HighlightText;
  }

  .chip input:checked + span {
    color: HighlightText;
  }
}

@media print {
  .sess-change,
  .save-bar,
  .end-actions,
  .skip-link {
    display: none !important;
  }

  .srow-body {
    display: block;
  }

  .srow {
    break-inside: avoid;
  }
}


.session-bar,
.grade-toolbar,
.bulk-panel,
.session-note,
.save-bar {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.session-bar {
  display: flex;
  align-items: end;
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding: 0.9rem;
}

.session-bar .field-inline:first-child {
  min-width: 220px;
  flex: 1 1 320px;
}

.session-bar .field-period {
  flex: 0 0 84px;
}

.score-excellent {
  color: var(--success);
}

.score-good {
  color: var(--accent);
}

.score-fair {
  color: var(--warning);
}

.score-poor {
  color: var(--danger);
}

.score-none {
  color: var(--muted);
  font-size: 0.78rem;
}

.session-note {
  margin-top: 0.85rem;
  padding: 0.85rem;
}

@media (max-width: 900px) {
  /* The topbar wraps its nav onto a second row below 900px, so the desktop
     scroll-padding clips the top of anything a skip link jumps to. */
  html {
    scroll-padding-top: 7rem;
  }

  .topbar-inner {
    grid-template-columns: 1fr auto;
    gap: 0.5rem;
  }

  .nav {
    grid-column: 1 / -1;
    grid-row: 2;
    min-width: 0;
    overflow-x: auto;
    border-top: 1px solid var(--line);
  }

  .nav-link {
    min-height: 42px;
    white-space: nowrap;
  }

  .user-name {
    display: none;
  }

  .page {
    padding-top: 1.5rem;
  }
}

@media (max-width: 720px) {
  body {
    font-size: 15px;
  }

  .topbar-inner,
  .topbar-inner.wide,
  .page,
  .page.wide {
    width: min(100% - 1rem, 100%);
  }

  .topbar-inner {
    min-height: 58px;
  }

  .brand-text {
    max-width: 45vw;
  }

  .topbar-user .btn {
    padding-inline: 0.45rem;
  }

  .page {
    padding-bottom: 6rem;
  }

  .page-head {
    align-items: stretch;
    flex-direction: column;
    margin-bottom: 1rem;
  }

  .page-actions,
  .page-actions .inline-form {
    width: 100%;
  }

  .page-actions input {
    flex: 1;
  }

  .grid-2 {
    grid-template-columns: 1fr;
  }

  .admin-split,
  .admin-form-grid {
    grid-template-columns: 1fr;
  }

  .admin-form-wide {
    grid-column: auto;
  }

  .admin-form-submit {
    width: 100%;
  }

  .session-bar {
    align-items: stretch;
    flex-wrap: wrap;
  }

  .session-bar .field-inline:first-child {
    min-width: 100%;
  }

  .session-bar .field-inline:not(:first-child) {
    flex: 1 1 120px;
  }

  .session-bar > .btn {
    flex: 1 1 auto;
  }

  .flash {
    align-items: stretch;
    flex-direction: column;
  }

  .table-wrap {
    border-radius: var(--radius-sm);
  }
}

@media (max-width: 420px) {
  .brand-mark {
    width: 28px;
    height: 28px;
  }

  .brand-text {
    max-width: 38vw;
    font-size: 0.88rem;
  }

  /* The name is already hidden at this width; hiding the avatar too left a
     zero-size link in the tab order on every page. */
  .avatar {
    display: inline-grid;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}

@media print {
  .topbar,
  .page-actions,
  .session-bar,
  .grade-toolbar,
  .bulk-panel,
  .save-bar,
  .row-actions {
    display: none !important;
  }

  body,
  .page {
    width: 100%;
    background: white;
  }
}

/* ------------------------------------------------------------------ Reports */

/* Averages in a column only read as a column when the digits line up. */
.table td.num,
.table th.num {
  font-variant-numeric: tabular-nums;
}

.report-filters {
  align-items: flex-end;
}

.field-check {
  flex-direction: row;
  align-items: center;
  gap: 0.4rem;
}

.range-shortcuts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: baseline;
  margin: 0 0 1rem;
}

.range-link {
  border-bottom: 1px solid transparent;
  color: var(--primary);
  text-decoration: none;
}

.range-link:hover {
  border-bottom-color: currentColor;
}

.range-link.is-active {
  color: var(--ink);
  font-weight: 600;
}

.report-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin: 0 0 1.25rem;
}

.export-count {
  margin-inline-start: 0.25rem;
}

/* The absence rule travels with every number derived from it.
   .flash is a flex row, which would break this sentence into one line per
   <strong>; it needs to read as prose. */
.methodology {
  display: block;
  margin-bottom: 1.25rem;
  line-height: 1.5;
}

.pill-danger {
  background: var(--danger-soft);
  color: var(--danger);
}

/* Bars take their fill from a view-computed custom property, the same mechanism
   the grading grid uses for its own columns. Never an inline width -- and, since
   the production CSP is `style-src 'self'`, never an inline style attribute
   either: style-src governs those, so the attribute this used to carry was
   dropped and every bar rendered empty. The percentage arrives as data-pct and
   the table at the end of this block turns it into --pct. */
.meter {
  display: block;
  width: 100%;
  max-width: 120px;
  height: 8px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface-soft);
}

.meter > i {
  display: block;
  width: calc(var(--pct, 0) * 1%);
  height: 100%;
  background: var(--mark-line);
}

.meter.is-excellent > i {
  background: var(--success);
}

.meter.is-good > i {
  background: var(--devup-line);
}

.meter.is-fair > i {
  background: var(--dev1-line);
}

.meter.is-poor > i {
  background: var(--dev2-line);
}

.meter.is-none > i {
  background: var(--line-strong);
}

/* One rule per whole percent. Generated, dull, and CSP-clean: the alternative
   was relaxing style-src to allow inline style attributes, which would weaken
   the policy across every page to draw a bar. */
.meter[data-pct="0"] { --pct: 0; }
.meter[data-pct="1"] { --pct: 1; }
.meter[data-pct="2"] { --pct: 2; }
.meter[data-pct="3"] { --pct: 3; }
.meter[data-pct="4"] { --pct: 4; }
.meter[data-pct="5"] { --pct: 5; }
.meter[data-pct="6"] { --pct: 6; }
.meter[data-pct="7"] { --pct: 7; }
.meter[data-pct="8"] { --pct: 8; }
.meter[data-pct="9"] { --pct: 9; }
.meter[data-pct="10"] { --pct: 10; }
.meter[data-pct="11"] { --pct: 11; }
.meter[data-pct="12"] { --pct: 12; }
.meter[data-pct="13"] { --pct: 13; }
.meter[data-pct="14"] { --pct: 14; }
.meter[data-pct="15"] { --pct: 15; }
.meter[data-pct="16"] { --pct: 16; }
.meter[data-pct="17"] { --pct: 17; }
.meter[data-pct="18"] { --pct: 18; }
.meter[data-pct="19"] { --pct: 19; }
.meter[data-pct="20"] { --pct: 20; }
.meter[data-pct="21"] { --pct: 21; }
.meter[data-pct="22"] { --pct: 22; }
.meter[data-pct="23"] { --pct: 23; }
.meter[data-pct="24"] { --pct: 24; }
.meter[data-pct="25"] { --pct: 25; }
.meter[data-pct="26"] { --pct: 26; }
.meter[data-pct="27"] { --pct: 27; }
.meter[data-pct="28"] { --pct: 28; }
.meter[data-pct="29"] { --pct: 29; }
.meter[data-pct="30"] { --pct: 30; }
.meter[data-pct="31"] { --pct: 31; }
.meter[data-pct="32"] { --pct: 32; }
.meter[data-pct="33"] { --pct: 33; }
.meter[data-pct="34"] { --pct: 34; }
.meter[data-pct="35"] { --pct: 35; }
.meter[data-pct="36"] { --pct: 36; }
.meter[data-pct="37"] { --pct: 37; }
.meter[data-pct="38"] { --pct: 38; }
.meter[data-pct="39"] { --pct: 39; }
.meter[data-pct="40"] { --pct: 40; }
.meter[data-pct="41"] { --pct: 41; }
.meter[data-pct="42"] { --pct: 42; }
.meter[data-pct="43"] { --pct: 43; }
.meter[data-pct="44"] { --pct: 44; }
.meter[data-pct="45"] { --pct: 45; }
.meter[data-pct="46"] { --pct: 46; }
.meter[data-pct="47"] { --pct: 47; }
.meter[data-pct="48"] { --pct: 48; }
.meter[data-pct="49"] { --pct: 49; }
.meter[data-pct="50"] { --pct: 50; }
.meter[data-pct="51"] { --pct: 51; }
.meter[data-pct="52"] { --pct: 52; }
.meter[data-pct="53"] { --pct: 53; }
.meter[data-pct="54"] { --pct: 54; }
.meter[data-pct="55"] { --pct: 55; }
.meter[data-pct="56"] { --pct: 56; }
.meter[data-pct="57"] { --pct: 57; }
.meter[data-pct="58"] { --pct: 58; }
.meter[data-pct="59"] { --pct: 59; }
.meter[data-pct="60"] { --pct: 60; }
.meter[data-pct="61"] { --pct: 61; }
.meter[data-pct="62"] { --pct: 62; }
.meter[data-pct="63"] { --pct: 63; }
.meter[data-pct="64"] { --pct: 64; }
.meter[data-pct="65"] { --pct: 65; }
.meter[data-pct="66"] { --pct: 66; }
.meter[data-pct="67"] { --pct: 67; }
.meter[data-pct="68"] { --pct: 68; }
.meter[data-pct="69"] { --pct: 69; }
.meter[data-pct="70"] { --pct: 70; }
.meter[data-pct="71"] { --pct: 71; }
.meter[data-pct="72"] { --pct: 72; }
.meter[data-pct="73"] { --pct: 73; }
.meter[data-pct="74"] { --pct: 74; }
.meter[data-pct="75"] { --pct: 75; }
.meter[data-pct="76"] { --pct: 76; }
.meter[data-pct="77"] { --pct: 77; }
.meter[data-pct="78"] { --pct: 78; }
.meter[data-pct="79"] { --pct: 79; }
.meter[data-pct="80"] { --pct: 80; }
.meter[data-pct="81"] { --pct: 81; }
.meter[data-pct="82"] { --pct: 82; }
.meter[data-pct="83"] { --pct: 83; }
.meter[data-pct="84"] { --pct: 84; }
.meter[data-pct="85"] { --pct: 85; }
.meter[data-pct="86"] { --pct: 86; }
.meter[data-pct="87"] { --pct: 87; }
.meter[data-pct="88"] { --pct: 88; }
.meter[data-pct="89"] { --pct: 89; }
.meter[data-pct="90"] { --pct: 90; }
.meter[data-pct="91"] { --pct: 91; }
.meter[data-pct="92"] { --pct: 92; }
.meter[data-pct="93"] { --pct: 93; }
.meter[data-pct="94"] { --pct: 94; }
.meter[data-pct="95"] { --pct: 95; }
.meter[data-pct="96"] { --pct: 96; }
.meter[data-pct="97"] { --pct: 97; }
.meter[data-pct="98"] { --pct: 98; }
.meter[data-pct="99"] { --pct: 99; }
.meter[data-pct="100"] { --pct: 100; }

/* Uppercase a value without an inline style attribute, for the same reason. */
.input-upper {
  text-transform: uppercase;
}






.note-cell {
  max-width: 22rem;
  white-space: normal;
}

@media (max-width: 720px) {
  .meter {
    max-width: none;
  }
}

@media print {
  .report-filters,
  .range-shortcuts,
  .report-actions {
    display: none !important;
  }

  /* The methodology note is deliberately NOT hidden: a printed average without
     its exclusion rule is the misreading this feature exists to prevent. */
  .meter,
  .trend {
    break-inside: avoid;
  }
}

/* ------------------------------------------------------- brand lockup */

/* School identity and system name together. Without the second line the header
   says where you are but not what the tool does, which is disorienting for a
   teacher opening it for the first time. */
.brand-lines {
  display: grid;
  min-width: 0;
  gap: 0.05rem;
  line-height: 1.2;
}

.brand-text {
  font-size: 1.02rem;
  font-weight: 600;
}

.brand-system {
  overflow: hidden;
  color: var(--muted);
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.auth-system {
  display: inline-block;
  margin-top: 0.15rem;
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  background: var(--primary-soft);
  color: var(--primary-strong);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

@media (max-width: 900px) {
  .brand-system {
    display: none;
  }
}

/* --------------------------------------------------------- utilities */

/* Available to a screen reader, invisible on screen. Used for full legal names,
   fieldset legends and the live region. */
.vh {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The topbar carries six links on every page; without this, reaching the
   content by keyboard means tabbing through all of them every time. */
.skip-link {
  position: absolute;
  left: -9999px;
  z-index: 100;
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.9rem;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  font-weight: 700;
}

.skip-link:focus {
  left: 1rem;
  top: 1rem;
}

/* A table that still has to scroll says so, instead of hiding data off-screen
   with no affordance. */
.table-wrap {
  position: relative;
  scrollbar-width: thin;
}

.table-wrap.is-scrollable {
  background:
    linear-gradient(to right, var(--surface) 30%, rgb(255 255 255 / 0)) left / 24px 100% no-repeat,
    linear-gradient(to left, var(--surface) 30%, rgb(255 255 255 / 0)) right / 24px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgb(60 30 32 / 0.16), transparent) left / 12px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgb(60 30 32 / 0.16), transparent) right / 12px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;
}

.table-wrap:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 2px;
}

.table-scroll-hint {
  margin: 0 0 0.4rem;
  color: var(--muted);
  font-size: 0.8rem;
}

@media (min-width: 721px) {
  .table-scroll-hint {
    display: none;
  }
}

/* The strength meter restates the percentage beside it; on a narrow screen that
   duplication is what pushes the table off the edge. */
@media (max-width: 720px) {
  .table th.col-meter,
  .table td.col-meter {
    display: none;
  }
}

/* Least load-bearing column on a phone; the number it carries is derivable
   from the sessions and absence counts beside it. */
@media (max-width: 720px) {
  .table th.col-secondary,
  .table td.col-secondary {
    display: none;
  }
}

/* ------------------------------------------------------------- Today */

.day-nav {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  margin-bottom: 1rem;
  font-size: 0.92rem;
  font-weight: 600;
}

.day-nav .is-current {
  color: var(--muted);
}

/* One entry per recorded period. A class taught twice in a day used to show
   only the first, and the card then reopened those saved marks. */
.period-list {
  margin: 0 0 0.9rem;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}

.period-list li {
  border-bottom: 1px solid var(--line);
}

.period-list a {
  display: block;
  padding: 0.5rem 0;
  color: var(--ink);
  font-size: 0.88rem;
}

.period-list a:hover {
  color: var(--primary);
  text-decoration: none;
}

.school-strip {
  margin-bottom: 1.25rem;
}

.stale-list > summary {
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  background: var(--surface-soft);
  font-size: 0.9rem;
  font-weight: 650;
  cursor: pointer;
}

.stale-list[open] > summary {
  margin-bottom: 0.75rem;
}

/* A class nobody has ever scored is the highest-value cell on an
   administrator's screen: it is how an unassigned class is found. */
.never-marked {
  color: var(--dev2-ink);
  font-weight: 700;
}

.table th.row-name,
.table td.row-name {
  font-weight: 600;
  text-align: left;
}

/* ------------------------------------------------------- admin setup */

/* Six counters all reading zero told an administrator nothing about the order
   setting a school up actually requires. */
.setup-next {
  margin-bottom: 1.1rem;
  border-left: 3px solid var(--primary);
}

.setup-list {
  display: grid;
  gap: 0.4rem;
  margin: 0 0 1.5rem;
  padding: 0;
  list-style: none;
  counter-reset: none;
}

.setup-step a {
  display: grid;
  grid-template-columns: 1.6rem 1fr auto;
  gap: 0.75rem;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0.65rem 0.85rem;
  background: var(--surface);
  color: var(--ink);
}

.setup-step a:hover {
  border-color: var(--line-strong);
  text-decoration: none;
}

.setup-step.is-done a {
  background: var(--surface-soft);
}

.setup-mark {
  display: grid;
  width: 1.5rem;
  height: 1.5rem;
  place-items: center;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  color: var(--devup-ink);
  font-weight: 700;
}

.setup-step.is-done .setup-mark {
  border-color: var(--devup-line);
  background: var(--devup-fill);
}

.setup-label {
  font-weight: 650;
}

.setup-count {
  color: var(--muted);
  font-size: 0.88rem;
  font-variant-numeric: tabular-nums;
}

/* An empty table should say why it is empty, not look broken. */
.empty-row {
  padding: 1.4rem 1rem !important;
  color: var(--muted);
  text-align: center;
}

@media (max-width: 720px) {
  .admin-nav {
    gap: 0.3rem;
  }

  .admin-nav a {
    font-size: 0.85rem;
  }
}

.dep-list {
  margin: 0.5rem 0 0;
  padding-left: 1.2rem;
}

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

/* ------------------------------------------------------------ exports */

.export-block {
  margin-bottom: 1.25rem;
}

.export-summary {
  margin-bottom: 0.5rem;
  font-size: 0.92rem;
}

.export-warn {
  color: var(--dev2-ink);
  font-weight: 650;
}

/* A control that cannot succeed should not look clickable. Rendered as a span
   rather than a disabled button so it stays readable to a screen reader with
   its reason attached. */
.btn.is-unavailable {
  border-color: var(--line);
  background: var(--surface-soft);
  color: var(--muted);
  box-shadow: none;
  cursor: not-allowed;
}

.student-search {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: flex-end;
  margin-bottom: 1rem;
}

.student-search .hint {
  flex-basis: 100%;
}

.found-list {
  display: grid;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.found-list li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: baseline;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.4rem;
}

.pager {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  justify-content: center;
  margin-top: 1rem;
}

/* ------------------------------------------------------------- trend */

/* Uniform scaling, so the visual slope is a property of the data rather than of
   the reader's screen width. */
.trend {
  margin: 0;
}

.trend-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 240px;
}

.trend-grid {
  stroke: var(--line);
  stroke-width: 1;
}

.trend-tick {
  fill: var(--muted);
  font-size: 11px;
}

.trend-line {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.trend-dot {
  stroke: var(--surface);
  stroke-width: 1.5;
}

/* Categorical, and distinguishable in greyscale by lightness order. */
.trend-series.s1 .trend-line { stroke: var(--accent); }
.trend-series.s1 .trend-dot  { fill: var(--accent); }
.trend-series.s2 .trend-line { stroke: var(--primary); }
.trend-series.s2 .trend-dot  { fill: var(--primary); }
.trend-series.s3 .trend-line { stroke: var(--warning); }
.trend-series.s3 .trend-dot  { fill: var(--warning); }
.trend-series.s4 .trend-line { stroke: #4d4547; }
.trend-series.s4 .trend-dot  { fill: #4d4547; }
.trend-series.s5 .trend-line { stroke: #7a5200; }
.trend-series.s5 .trend-dot  { fill: #7a5200; }

.trend-key {
  margin-right: 0.5rem;
  font-weight: 650;
}

.trend-key::before {
  display: inline-block;
  width: 0.65rem;
  height: 0.65rem;
  margin-right: 0.25rem;
  border-radius: 2px;
  content: "";
  vertical-align: baseline;
}

.trend-key.s1::before { background: var(--accent); }
.trend-key.s2::before { background: var(--primary); }
.trend-key.s3::before { background: var(--warning); }
.trend-key.s4::before { background: #4d4547; }
.trend-key.s5::before { background: #7a5200; }

/* Touch input needs room whatever the screen size; a narrow laptop window does
   not. Sizing by viewport width conflated the two. */
@media (pointer: coarse) {
  .btn {
    min-height: 46px;
  }

  .btn-sm {
    min-height: 40px;
  }

  .table td,
  .table th {
    padding-top: 0.7rem;
    padding-bottom: 0.7rem;
  }

  .setup-step a {
    padding-top: 0.85rem;
    padding-bottom: 0.85rem;
  }
}

/* A pointer that can hover is precise, so the reading surfaces can be denser
   without costing accuracy. */
@media (pointer: fine) {
  .table td,
  .table th {
    padding-top: 0.45rem;
    padding-bottom: 0.45rem;
  }
}
