/* King Faisal Schools house style — green #004E42 + gold #AB9835.
   Tokens match Data Streaming/Workbook PDF Analysis/pdf_build/kfs.py */
:root {
  /* This is a light interface with dark chrome bands, by design, and saying
     so stops a browser deciding otherwise. Chrome's auto-dark-mode repaints
     any page that does not declare a scheme when the reader's OS is dark —
     it was rewriting --bg #ECECEF to rgb(24,26,27) and taking --ground, the
     band colours and every chart fill with it. A band is a judgement about a
     child rendered as a colour; a browser must not be inventing those. */
  color-scheme: light;

  --ink: #22282B;
  --muted: #6E7679;
  --line: #DDE1E1;
  --paper: #FFFFFF;
  --wash: #F4F5F6;
  --bg: #ECECEF;
  --brand: #004E42;
  --brand-deep: #00382F;
  --brand-light: #2E8B7A;
  --gold: #AB9835;
  --gold-light: #C9B45C;
  /* The house gold is a border/fill token. This darker companion is for
     ordinary text on a light surface and meets normal-text contrast. */
  --gold-text: #6F5B00;
  --wae: #004E42;
  --wwe: #AB9835;
  --wte: #B0473E;

  /* Chart-mark steps. The three above are the house tokens and stay as they
     are for type and chrome; as *fills on a light surface* the deep brand
     green is too dark and too near-grey to read as a data mark, and the gold
     falls under 3:1 contrast. These are the nearest steps in the same hue
     families that pass on lightness, chroma, contrast and colour-vision
     separation (worst adjacent pair ΔE 11.6 deuteranopia, 18.1 normal).
     Every chart that uses them also carries a legend and a text label, so no
     figure on the page is ever carried by colour alone. */
  --mark-wae: #00806A;
  --mark-wwe: #A68C22;
  --mark-wte: #B0473E;
  --mark-norm: #6E7679;
  --mark-fit: #AB9835;

  /* Categorical series (grouped_bars, the gap lines): Reading against
     Mathematics, Praise against Concern, SEN against the rest. These have no
     order and no norm, so none of them is a band token — a Praise bar in
     Working Above green or a Concern bar in Working Below red is a judgement
     nobody made. Warm against cool for the most-used pair (cmp-1 against
     cmp-2, ΔE 62 under deuteranopia). cmp-4 is the house gold, which is also
     the hex of --wwe and under 3:1 as a fill, so it is only ever a fourth
     series. A role's bar, line, dot and legend swatch all read its token
     (tests/test_cmp_colours.py). */
  --cmp-1: #A8582F;
  --cmp-2: #2F6D86;
  --cmp-3: var(--muted);
  --cmp-4: var(--gold);

  /* Goal strands are a diverging scale about "Average": red pole → neutral
     grey midpoint → green pole. Never a rainbow, and never a hue in the
     middle. */
  --shade-1: #B0473E;
  --shade-2: #DFAFA9;
  --shade-3: #E8EAEA;
  --shade-4: #A4D0C4;
  --shade-5: #00806A;
  --warn-bg: #FBF3DF;
  --warn-line: #AB9835;
  --soft-on-green: #BFD3CE;

  /* --- The identity ground -------------------------------------------
     The dark green the diamond mark is drawn on. It does NOT replace
     --brand: the house green is the school's, it is what the logo is
     printed in and what pdf_build/kfs.py matches, and it stays on the
     topbar and in every report.

     This is a deeper step in the same hue, and the reason to have it is
     measurable rather than decorative — the house gold reads 4.99:1 on
     --ground and only 3.35:1 on --brand. On the ground the gold is
     finally allowed to be text; on --brand it may only ever be a rule or
     a border. Every dark identity surface (sign-in, the public pages, the
     footer, a cover) uses --ground for that reason. */
  --ground: #043028;
  --ground-2: #02211B;
  --ground-line: #1C4A40;   /* decorative hairline only — 1.44:1 */
  --on-ground: #E8EDEB;     /* 12.2:1 */
  --on-ground-dim: #9FB3AC; /* 6.5:1 */

  /* --- The type scale --------------------------------------------------
     Every font-size on an inspection page and on an appraisal page is one of
     these tokens — no literal size is left — and none is under 14px but
     --fs-d2. That one step is for the named exemptions in
     tests/test_inspection_design.py and nothing else: furniture a reader
     scans and never reads as a sentence (a column label, a file-kind badge,
     a count inside a chip, the year and n under a trend cell). A note, a help
     line, a caveat, a date, a name — anything read as words — sits on 14px or
     above, because the inspection desk's own explanations at 11.5px were the
     words a non-technical keeper most needed and could least read.

     Here, beside the colour tokens, since 2026-09-21 (DR-156). The scale was
     written for the 2026-09-14 inspection revamp and lived in inspection.css,
     so the only way an appraisal page could size its own type was to link a
     second area's entire stylesheet for ten custom properties — and the
     appraisal's own print sheet, which links adt.css and appraisal.css and
     never inspection.css, had no scale at all and printed at the browser's
     defaults. A token two areas read is the platform's, and this is the sheet
     both shells already load. */
  --fs-d2: 0.8125rem;   /* 13px — the named exemptions only */
  --fs-p1: 0.875rem;    /* 14px — the floor */
  --fs-p2: 1rem;        /* 16px */
  --fs-p3: 1.125rem;    /* 18px */
  --fs-h2: 1.3125rem;   /* 21px */
  --fs-h1: 1.5rem;      /* 24px */
  --fs-n1: 1.5rem;      /* 24px — a figure */
  --fs-n2: 2rem;        /* 32px — a figure on a card */
  --fs-n3: 2.5rem;      /* 40px — a lead figure */
  --fs-n4: 3.25rem;     /* 52px — the first ranked card's figure */
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  color: var(--ink);
  background: var(--bg);
}

a { color: var(--brand); }

.topbar {
  display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap;
  padding: 0.65rem 1.5rem 0.65rem 0;
  background: var(--brand);
  color: #fff;
  border-bottom: 3px solid var(--gold);
}
.topbar::before {
  content: "";
  width: 10px;
  align-self: stretch;
  background: var(--gold);
  margin-inline-end: 0.25rem;
}
.brand {
  display: flex; align-items: center; gap: 0.75rem;
  text-decoration: none; color: #fff;
}
.brand img {
  height: 42px; width: auto; display: block;
  background: #fff; border-radius: 4px; padding: 2px;
}
.brand-text { display: flex; flex-direction: column; gap: 0.05rem; }
.brand strong { color: #fff; letter-spacing: 0.02em; font-size: 0.98rem; }
.brand-school { font-size: 0.72rem; color: var(--soft-on-green); }

/* A small label above a group of things: the nav's sections, the tab strip's
   four groups, a tab's own filter bar. It was only ever defined inside those
   scopes, so the class meant nothing on its own; this is the shared look and
   each scope still overrides what it needs to. */
.eyebrow {
  font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--muted);
}

.nav-groups { display: flex; gap: 1.25rem; flex-wrap: wrap; flex: 1; }
.nav-group { display: flex; flex-direction: column; gap: 0.2rem; }
.nav-group .eyebrow {
  font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--gold-light);
}
.nav-group .links { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: baseline; }
.topbar nav a, .nav-group a {
  text-decoration: none; font-size: 0.88rem; color: #fff; opacity: 0.92;
}
.topbar nav a:hover, .nav-group a:hover { opacity: 1; text-decoration: underline; color: var(--gold-light); }
.who { margin-inline-start: auto; font-size: 0.8rem; color: var(--soft-on-green); }

/* ---- the top bar's search box ----------------------------------------------

   A dark-surface field on --brand. Per docs/brand.md the house gold reads
   3.35:1 on --brand, so it may be a *rule* here and never text: the border is
   gold, the typed text is white, and the placeholder is the same soft tone the
   rest of the bar uses. On focus the field inverts to the light surface — where
   --ink on white is the reading contrast a person typing a child's name
   deserves — and takes a gold outline, which is a rule again and not a word.

   The panel is absolutely positioned on a relative form, the same trick
   .nav-more-panel uses, so an open dropdown can never grow the topbar and push
   the page down. It is a light surface because it holds names and second lines,
   which is body text and not chrome.

   The resting width is measured, not chosen. The topbar is a wrapping flex row
   that is already full for an ADT reader with five nav groups, so every pixel
   this box takes comes out of .nav-groups and can wrap it onto another row —
   which is fold on every page in the platform. Measured on the class page at
   900 px tall, bar height with the box against without:

     box     1280    1366    1440    1536    1920
     18rem   +119    +119     +60      0      +48
     11rem    +60     +60       0      0      +48
     7.5rem   +60       0       0      0      +48

   So the box is 11rem where there is room for it and 7.5rem below 1440, which
   is zero cost at 1366, 1440 and 1536. A teacher-only reader has one nav group
   and pays nothing at any width. Two costs are left and are not hidden: 1280,
   where only a 40 px field would be free and a 40 px field is not a field; and
   1920, where the five groups fit on one row with no box at all and on two with
   any box — a knife edge that already falls the other way at 1680.

   The panel is absolutely positioned and keeps its own width, so a narrow field
   costs the results nothing. */
.topsearch {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
  flex: 0 1 11rem;
}
@media (max-width: 1439px) {
  .topsearch { flex-basis: 7.5rem; }
}
.topsearch input {
  font: inherit;
  font-size: 0.85rem;
  width: 100%;
  min-width: 0;
  padding: 0.35rem 0.6rem;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  border: 1px solid var(--gold);
  border-radius: 6px;
}
.topsearch input::placeholder { color: var(--soft-on-green); opacity: 1; }
/* Chrome keeps room for its clear button inside a search field even while the
   field is empty; in the 7.5rem box below 1440 px that room cut the
   placeholder to "Searc". The field clears with Escape either way. */
.topsearch input::-webkit-search-cancel-button,
.topsearch input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
.topsearch input:focus {
  background: #fff;
  color: var(--ink);
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}
.topsearch input:focus::placeholder { color: var(--muted); }
.topsearch button {
  font: inherit; font-size: 0.8rem; color: var(--gold);
  background: none; border: 0; cursor: pointer; padding: 0;
  white-space: nowrap;
}
.topsearch button:hover { text-decoration: underline; }
.topsearch-panel {
  position: absolute;
  top: calc(100% + 0.4rem);
  /* Anchored to the box's *end*, the way .nav-more-panel is, and for the same
     reason: the form sits near the right of the bar, so a panel opening to the
     right ran 130 px past the viewport and gave every page a horizontal
     scrollbar. Opening leftwards it stays inside the window at every width. */
  inset-inline-end: 0;
  inset-inline-start: auto;
  z-index: 45;
  width: min(26rem, 84vw);
  max-height: min(70vh, 32rem);
  overflow-y: auto;
  padding: 0.55rem 0.7rem;
  background: #fff;
  color: var(--ink);
  border: 1px solid var(--line);
  border-top: 3px solid var(--gold);
  border-radius: 0 0 8px 8px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28);
  text-align: start;
}
.search-group + .search-group { margin-top: 0.55rem; padding-top: 0.5rem; border-top: 1px solid var(--line); }
.search-group > .eyebrow { margin: 0 0 0.25rem; color: var(--brand); font-weight: 650; }
.search-row {
  display: flex; flex-direction: column; gap: 0.05rem;
  padding: 0.3rem 0.35rem; border-radius: 4px;
  text-decoration: none; color: var(--ink); font-size: 0.85rem;
}
.search-row-name { font-weight: 650; color: var(--brand-deep); }
.search-row-sub { font-size: 0.76rem; color: var(--muted); }
.search-row:hover, .search-row:focus, .search-row.is-active {
  background: var(--paper); outline: 2px solid var(--brand-light); outline-offset: -2px;
}
.search-row.is-active .search-row-name { text-decoration: underline; }
.search-more, .search-empty, .search-why {
  margin: 0.3rem 0 0; font-size: 0.76rem; color: var(--muted);
}
.search-empty { margin: 0.2rem 0; color: var(--ink); }
.search-results { margin-top: 1rem; }
.search-results .search-row { font-size: 0.95rem; padding: 0.4rem 0.45rem; }
.search-results .search-row-sub { font-size: 0.82rem; }
.search-scope-note { font-size: 0.82rem; color: var(--muted); }

/* Analysis overflow: overlay panel so open state does not grow the topbar */
.nav-more {
  position: relative;
  display: inline-block;
}
.nav-more > summary {
  list-style: none;
  cursor: pointer;
  font-size: 0.88rem;
  color: #fff;
  opacity: 0.92;
  user-select: none;
}
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more > summary::after {
  content: " ▾";
  font-size: 0.75em;
  opacity: 0.85;
}
.nav-more > summary:hover,
.nav-more[open] > summary {
  opacity: 1;
  text-decoration: underline;
  color: var(--gold-light);
}
.nav-more-panel {
  position: absolute;
  inset-inline-end: 0;
  inset-inline-start: auto;
  top: calc(100% + 0.45rem);
  z-index: 40;
  min-width: 10.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.65rem 0.85rem;
  background: var(--brand);
  border: 1px solid var(--gold);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.28);
}
.nav-more-panel a {
  white-space: nowrap;
}

.banner {
  padding: 0.7rem 1.5rem; font-size: 0.9rem;
  border-bottom: 1px solid var(--line);
}
.banner-warn { background: var(--warn-bg); border-bottom-color: var(--warn-line); }

/* One announcement surface across portal, access, import, and Spaces pages.
   The colour supplements the role/live-region semantics; it is never the only
   way an error is conveyed. */
.app-feedback {
  max-width: 1180px;
  margin: 1rem auto 0;
  padding-inline: 1.5rem;
}
.app-feedback__message {
  margin: 0 0 0.65rem;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--line);
  border-inline-start: 4px solid var(--brand);
  border-radius: 6px;
  background: var(--paper);
}
.app-feedback__message--warning {
  background: var(--warn-bg);
  border-color: var(--warn-line);
  border-inline-start-color: #7A5A10;
}
.app-feedback__message--error {
  background: #FCEDEA;
  border-color: #B0473E;
  border-inline-start-color: #8E3029;
}

main { padding: 1.5rem; max-width: 1180px; margin: 0 auto; }

h1 {
  font-size: 1.45rem; margin: 0 0 0.25rem;
  color: var(--brand); font-weight: 650;
}
h2 {
  font-size: 1.05rem; margin: 2rem 0 0.5rem;
  color: var(--brand-deep);
  border-inline-start: 3px solid var(--gold);
  padding-inline-start: 0.55rem;
}
.lede { color: var(--muted); margin: 0 0 1.25rem; max-width: 62ch; }
/* The analysis year line sits under the h1: a standing fact, not an alert. */
.analysis-year { color: var(--muted); margin: 0 0 0.85rem; max-width: 72ch; }
.analysis-year + .lede { margin-top: 0; }

.card {
  background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
  padding: 1rem 1.25rem; margin-bottom: 1.25rem;
  box-shadow: 0 1px 0 rgba(0, 78, 66, 0.04);
}

.controls { display: flex; gap: 1rem; align-items: flex-end; flex-wrap: wrap; margin-bottom: 1.25rem; }
.controls label { display: flex; flex-direction: column; font-size: 0.78rem; color: var(--muted); gap: 0.2rem; }
.controls select, .controls input {
  font: inherit; padding: 0.35rem 0.5rem; border: 1px solid var(--line);
  border-radius: 4px; background: var(--paper);
}
.controls button {
  font: inherit; padding: 0.4rem 0.9rem; border: 1px solid var(--brand);
  background: var(--brand); color: #fff; border-radius: 4px; cursor: pointer;
}
.controls button:hover { background: var(--brand-deep); }

/* A tab's own filters, under the page's. Same markup, marked out so the two
   bars do not read as one duplicated: the window and subject above apply to
   every tab; these cut this one. */
.controls-sub {
  align-items: center;
  padding: 0.65rem 0.9rem;
  border: 1px dashed var(--line);
  border-radius: 10px;
  background: var(--paper);
}
.controls-sub > .eyebrow { align-self: center; color: var(--brand); font-weight: 650; }

.stats { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.stat {
  background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
  border-inline-start: 4px solid var(--gold);
  padding: 0.8rem 1.1rem; min-width: 9rem;
}
.stat .value { font-size: 1.6rem; font-weight: 650; color: var(--brand); }
.stat .label { font-size: 0.75rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.stat .note { font-size: 0.75rem; color: var(--muted); }
.stats a.stat-link {
  text-decoration: none; color: inherit; transition: border-color 0.15s, transform 0.15s;
}
.stats a.stat-link:hover { border-color: var(--brand-light); transform: translateY(-1px); }
.stats a.stat-link.is-active { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }

table { width: 100%; border-collapse: collapse; background: var(--paper); font-size: 0.9rem; }
caption { text-align: start; color: var(--muted); font-size: 0.8rem; padding-bottom: 0.4rem; }
th, td { text-align: start; padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--line); }
th { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
td.num, th.num { text-align: end; font-variant-numeric: tabular-nums; }
tbody tr:hover { background: var(--wash); }
.table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; }

/* Opt-in for a roster too wide to read after a horizontal scroll — the
   inclusion register's fix (docs/notes/inclusion-desk-craft-2026-08-21.md
   §3), reused rather than reinvented: the header stays put and the first
   column (the pupil's name) travels with the scroll, so reaching a column on
   the right never costs knowing which row it belongs to.
   `border-collapse: collapse` and a sticky header disagree — with collapsed
   borders the cell backgrounds paint below the sticky rule, so the pinned
   head goes see-through. Separate borders with zero spacing look identical,
   because the rules are drawn by the cells rather than by the table. */
.table-wrap-pinned { overflow: auto; max-height: min(70vh, 46rem); }
.table-wrap-pinned table { border-collapse: separate; border-spacing: 0; }
.table-wrap-pinned thead th {
  position: sticky; top: 0; z-index: 3;
  background: linear-gradient(180deg, #F8FAF9, var(--wash));
  box-shadow: inset 0 -1px 0 var(--line);
}
.table-wrap-pinned th:first-child,
.table-wrap-pinned td:first-child {
  position: sticky; inset-inline-start: 0; z-index: 1;
  background: var(--paper);
  box-shadow: 5px 0 10px -10px rgba(34, 40, 43, 0.8);
}
.table-wrap-pinned thead th:first-child { z-index: 4; background: var(--wash); }
.table-wrap-pinned tbody tr:hover td:first-child { background: var(--wash); }
@media print {
  .table-wrap-pinned { overflow: visible; max-height: none; }
  .table-wrap-pinned thead th,
  .table-wrap-pinned th:first-child,
  .table-wrap-pinned td:first-child { position: static; box-shadow: none; }
}

.band { font-weight: 600; }
.band-WAE { color: var(--wae); }
.band-WWE { color: var(--gold-text); }
.band-WTE { color: var(--wte); }
.suppressed { color: var(--muted); }

.bar { display: flex; height: 0.85rem; border-radius: 3px; overflow: hidden; min-width: 8rem; }
.bar span { display: block; }
.bar .b-WAE { background: var(--wae); }
.bar .b-WWE { background: var(--wwe); }
.bar .b-WTE { background: var(--wte); }

.spark { display: flex; align-items: flex-end; gap: 3px; height: 3rem; }
.spark i { display: block; width: 14px; background: var(--brand); border-radius: 2px 2px 0 0; }

.pill {
  display: inline-block; padding: 0.05rem 0.45rem; border-radius: 999px;
  font-size: 0.72rem; border: 1px solid var(--line); color: var(--muted);
}
.pill-warn { background: var(--warn-bg); border-color: var(--warn-line); color: #7a5a10; }

.rule { border-inline-start: 3px solid var(--line); padding-inline-start: 1rem; margin-bottom: 1.5rem; }
.rule.open { border-inline-start-color: var(--warn-line); }
.rule h3 { margin: 0 0 0.25rem; font-size: 1rem; color: var(--brand); }
.rule .meta { font-size: 0.78rem; color: var(--muted); }
.rule .open-note { background: var(--warn-bg); padding: 0.6rem 0.8rem; border-radius: 4px; margin-top: 0.5rem; font-size: 0.86rem; }

/* The site footer only: a card's own <footer class="card-actions"> was
   inheriting this ground-green band (My classes, 2026-09-05). */
body > footer {
  display: flex; gap: 1rem; justify-content: space-between; flex-wrap: wrap;
  padding: 1rem 1.5rem; color: var(--muted); font-size: 0.78rem;
  border-top: 3px solid var(--gold);
  background: var(--brand);
  color: var(--soft-on-green);
}
body > footer span { color: var(--soft-on-green); }

/* ---- Sign-in (email → password | OTP) ---- */
body.auth-page {
  min-height: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
  /* The identity ground, and the mark held behind it at 7%. This is the
     first screen anyone sees, so it is the one that carries the most of
     the identity. The gradient and the watermark are written here rather
     than as reusable utilities — see the note in the identity block. */
  background:
    radial-gradient(ellipse at 20% 0%, rgba(171, 152, 53, 0.18), transparent 55%),
    linear-gradient(158deg, var(--ground) 0%, var(--ground-2) 100%);
  padding: 1.5rem;
  position: relative;
  overflow: hidden;
}
body.auth-page::before {
  content: "";
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  width: min(46rem, 130vw);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: url("../img/mark-display.c186f8bad2c8.svg") center / contain no-repeat;
  opacity: 0.06;
  pointer-events: none;
}
body.auth-page > * { position: relative; }
.auth-card {
  width: min(26rem, 100%);
  background: var(--paper);
  border-radius: 12px;
  border: 1px solid rgba(171, 152, 53, 0.35);
  box-shadow: 0 18px 40px rgba(0, 30, 24, 0.35);
  padding: 1.5rem 1.6rem 1.75rem;
}
.auth-brand {
  display: flex; gap: 0.9rem; align-items: center; margin-bottom: 1rem;
}
.auth-brand img { width: 3.2rem; height: auto; }
.auth-brand .kicker {
  margin: 0; font-size: 0.72rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.auth-brand h1 {
  margin: 0.15rem 0 0; font-size: 1.35rem; color: var(--brand-deep);
  border: 0; padding: 0;
}
.auth-brand .ar { margin: 0.15rem 0 0; color: var(--gold-text); font-size: 0.9rem; }
.auth-form { display: flex; flex-direction: column; gap: 0.85rem; }
.auth-form label {
  display: flex; flex-direction: column; gap: 0.3rem;
  font-size: 0.78rem; color: var(--muted);
}
.auth-form input {
  font: inherit; padding: 0.55rem 0.65rem; border: 1px solid var(--line);
  border-radius: 6px; background: #fff;
}
.auth-form button {
  font: inherit; padding: 0.55rem 0.9rem; border: 0;
  background: var(--brand); color: #fff; border-radius: 6px; cursor: pointer;
  font-weight: 600;
}
.auth-form button:hover { background: var(--brand-deep); }
.auth-error { color: #8b1e1e; margin: 0; font-size: 0.88rem; }
.auth-msg { color: var(--brand); margin: 0 0 0.75rem; font-size: 0.9rem; }
.auth-alt { margin: 1rem 0 0; font-size: 0.85rem; color: var(--muted); }
.auth-alt form { display: inline; }
.auth-alt button {
  font: inherit; color: var(--gold-text); background: none; border: 0;
  cursor: pointer; padding: 0;
}
.auth-alt button:hover { text-decoration: underline; }
.auth-alt a, .who a { color: var(--gold-text); }
.topbar .who a { color: var(--gold); text-decoration: none; }
.topbar .who a:hover { text-decoration: underline; }
.logout-form { display: inline; margin: 0; }
.logout-form button {
  font: inherit; color: var(--gold); background: none; border: 0;
  cursor: pointer; padding: 0;
}
.topbar .logout-form button:hover { text-decoration: underline; }
.skip-link {
  position: absolute; left: -999px; top: 0; z-index: 100;
  background: var(--gold); color: var(--brand-deep); padding: 0.4rem 0.8rem;
}
.skip-link:focus { left: 1rem; top: 1rem; }

/* ---- ManageBac roster changes timeline ---- */
.mb-changes-hero {
  margin-bottom: 1.25rem;
  padding: 1.25rem 1.35rem;
  border-radius: 12px;
  background:
    linear-gradient(135deg, rgba(0, 78, 66, 0.08), transparent 55%),
    linear-gradient(180deg, #fff, var(--wash));
  border: 1px solid var(--line);
  border-inline-start: 4px solid var(--gold);
}
.mb-changes-kicker {
  margin: 0 0 0.35rem;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold-text);
}
.usage h2 { margin-top: 1.75rem; }
.mb-changes-hero .lede { margin-bottom: 0; }
.mb-changes-tabs {
  display: flex; gap: 0.35rem; margin: 0.9rem 0 1.1rem;
}
.mb-changes-tabs a {
  display: inline-block; padding: 0.35rem 0.85rem; border-radius: 999px;
  font-size: 0.86rem; font-weight: 600; text-decoration: none;
  color: var(--muted); border: 1px solid var(--line); background: var(--paper);
}
.mb-changes-tabs a:hover { border-color: var(--brand-light); color: var(--brand); }
.mb-changes-tabs a.is-active {
  color: var(--brand-deep); border-color: var(--brand);
  background: rgba(0, 78, 66, 0.08);
}
.mb-changes-stats .stat-link {
  text-decoration: none; color: inherit; transition: border-color 0.15s, transform 0.15s;
}
.mb-changes-stats .stat-link:hover { border-color: var(--brand-light); transform: translateY(-1px); }
.mb-changes-stats .stat-link.is-active { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.mb-changes-stats .tone-in { border-inline-start-color: var(--brand); }
.mb-changes-stats .tone-out { border-inline-start-color: var(--wte); }
.mb-changes-stats .tone-move { border-inline-start-color: var(--gold); }
.mb-changes-filter { align-items: center; }
.mb-changes-clear { font-size: 0.85rem; align-self: center; }

.mb-changes-timeline {
  display: flex; flex-direction: column; gap: 0.75rem;
  position: relative;
}
.mb-change {
  display: grid;
  grid-template-columns: 1.1rem 1fr;
  gap: 0.85rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.9rem 1.1rem;
  animation: mb-change-in 0.35s ease both;
}
.mb-change:nth-child(1) { animation-delay: 0.02s; }
.mb-change:nth-child(2) { animation-delay: 0.06s; }
.mb-change:nth-child(3) { animation-delay: 0.1s; }
@keyframes mb-change-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
.mb-change-rail {
  width: 0.55rem; border-radius: 999px; margin-top: 0.2rem;
  background: var(--line);
}
.mb-change.tone-in .mb-change-rail { background: var(--brand); }
.mb-change.tone-out .mb-change-rail { background: var(--wte); }
.mb-change.tone-move .mb-change-rail { background: var(--gold); }
.mb-change-top {
  display: flex; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap;
  margin-bottom: 0.25rem;
}
.mb-change-top time { font-size: 0.78rem; color: var(--muted); }
.mb-change-pill {
  display: inline-block; padding: 0.12rem 0.55rem; border-radius: 999px;
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.02em;
  border: 1px solid var(--line); color: var(--muted); background: var(--wash);
}
.mb-change.tone-in .mb-change-pill {
  color: var(--brand); border-color: rgba(0, 78, 66, 0.25); background: rgba(0, 78, 66, 0.08);
}
.mb-change.tone-out .mb-change-pill {
  color: var(--wte); border-color: rgba(176, 71, 62, 0.3); background: rgba(176, 71, 62, 0.08);
}
.mb-change.tone-move .mb-change-pill {
  color: #7a5a10; border-color: rgba(171, 152, 53, 0.45); background: var(--warn-bg);
}
.mb-change h2 {
  margin: 0 0 0.35rem; font-size: 1rem; border: 0; padding: 0; color: var(--ink);
}
.mb-change-meta {
  display: flex; flex-wrap: wrap; gap: 0.45rem 0.85rem; align-items: center;
  margin: 0; font-size: 0.86rem; color: var(--muted);
}
.mb-change-class {
  color: var(--brand-deep); font-weight: 560;
}
.mb-change-arrow { color: var(--gold); font-weight: 700; }
.mb-change-delta {
  display: inline-flex; flex-wrap: wrap; gap: 0.35rem; align-items: baseline;
  font-weight: 500; margin-inline-start: 0.35rem;
}
.mb-change-before { color: var(--muted); text-decoration: line-through; font-weight: 400; }
.mb-change-after { color: var(--brand-deep); font-weight: 650; }
.mb-changes-pager {
  display: flex; gap: 1rem; align-items: center; justify-content: center;
  margin-top: 1.25rem; color: var(--muted); font-size: 0.88rem;
}
.mb-changes-pager a { font-weight: 600; }

.note { font-size: 0.8rem; color: var(--muted); }
/* ===================================================================== */
/* My classes — the teacher's own screen.                                */
/*                                                                       */
/* Everything below serves two pages: the class list and one class in    */
/* depth. The charts are inline SVG positioned entirely by               */
/* analytics/charts.py, so this file styles marks and never places them. */
/* ===================================================================== */

.sr {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.page-head {
  display: flex; gap: 1.5rem; align-items: flex-start;
  justify-content: space-between; flex-wrap: wrap; margin-bottom: 1rem;
}
.head-stats { display: flex; gap: 0.5rem; }
.head-stat {
  background: var(--paper); border: 1px solid var(--line); border-radius: 10px;
  padding: 0.5rem 0.9rem; min-width: 5.5rem; text-align: center;
}
.head-stat b { display: block; font-size: 1.45rem; color: var(--brand); font-weight: 650; }
.head-stat span { font-size: 0.7rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.head-stat.is-alert { border-color: var(--wte); }
.head-stat.is-alert b { color: var(--wte); }

.crumbs { font-size: 0.82rem; color: var(--muted); margin-bottom: 0.6rem; }
.crumbs span { margin: 0 0.35rem; }

.inline-banner { border: 1px solid var(--warn-line); border-radius: 10px; margin-bottom: 1.25rem; }
.footnote { margin-top: 2rem; padding-top: 0.8rem; border-top: 1px solid var(--line); }
.gate-note { margin: -0.5rem 0 1.25rem; }

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

.class-grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
}
.class-card {
  background: var(--paper); border: 1px solid var(--line); border-radius: 12px;
  padding: 1rem 1.1rem 0.9rem; display: flex; flex-direction: column; gap: 0.7rem;
  box-shadow: 0 1px 0 rgba(0, 78, 66, 0.04);
}
.class-card h2 {
  margin: 0; border: 0; padding: 0; font-size: 1rem; line-height: 1.3;
}
/* A class nothing measures and nobody has marked. It still gets a card — it is
   still their class — but it does not compete with the ones carrying figures. */
.class-card.is-quiet { background: var(--wash); box-shadow: none; }
.class-card .subject-list { flex-direction: column; gap: 0.15rem; }
.class-card .subject-list li { font-size: 0.82rem; color: var(--ink); }
.class-card h2 a { color: var(--brand-deep); text-decoration: none; }
.class-card h2 a:hover { text-decoration: underline; }
.class-card .meta { margin: 0.2rem 0 0; font-size: 0.78rem; color: var(--muted); }
.class-card .card-open {
  margin-top: 0; font-size: 0.82rem; text-decoration: none;
  padding-top: 0;
  border-top: 0;
}
.class-card .card-open:hover { text-decoration: underline; }
.class-card .card-actions {
  margin-top: auto; display: flex; flex-wrap: wrap; gap: 0.55rem 1rem;
  align-items: center; padding-top: 0.6rem; border-top: 1px solid var(--line);
}
.class-card .card-space {
  font-size: 0.82rem; text-decoration: none; color: var(--brand);
  font-weight: 560;
}
.class-card .card-space.is-paused,
.class-card .card-space.is-closed { color: var(--muted); }
.class-card .card-space:hover { text-decoration: underline; }
.class-card .card-space-open { margin: 0; }
.class-card .card-space-open button {
  font: inherit; font-size: 0.82rem; font-weight: 560; cursor: pointer;
  border: 1px solid var(--brand); background: var(--brand); color: #fff;
  border-radius: 4px; padding: 0.2rem 0.65rem;
}
.class-card .card-space-open button:hover { background: var(--brand-deep); }

.band-bar {
  display: flex; height: 0.7rem; border-radius: 4px; overflow: hidden;
  background: var(--wash); gap: 2px;
}
.band-bar span { display: block; }
.band-bar .b-WAE { background: var(--mark-wae); }
.band-bar .b-WWE { background: var(--mark-wwe); }
.band-bar .b-WTE { background: var(--mark-wte); }

.band-key { display: flex; gap: 0.9rem; flex-wrap: wrap; list-style: none; margin: 0; padding: 0; font-size: 0.76rem; }
.band-key li { color: var(--muted); }
.band-key b { font-size: 0.95rem; font-weight: 650; margin-inline-end: 0.15rem; }
.band-key .band-WAE b { color: var(--wae); }
.band-key .band-WWE b { color: #7a6a15; }
.band-key .band-WTE b { color: var(--wte); }
.band-key .band-top10 b { color: var(--brand); }

.card-facts { display: flex; gap: 1.1rem; margin: 0; flex-wrap: wrap; }
.card-facts dt { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.card-facts dd { margin: 0.15rem 0 0; font-size: 1rem; font-weight: 600; color: var(--brand-deep); font-variant-numeric: tabular-nums; }
.card-facts dd.is-alert { color: var(--wte); }
.card-facts .of { font-weight: 400; color: var(--muted); font-size: 0.82rem; }

.empty-state { grid-column: 1 / -1; }
.empty-state h2 { margin-top: 0; border: 0; padding: 0; font-size: 1rem; }

/* --------------------------------------------- by class / by pupil switch */

/* Two readings of one timetable. Underline weight rather than a filled tab,
   because neither reading is the "real" one that the other is a mode of. */
.view-switch { display: flex; gap: 1.25rem; margin: 0 0 0.9rem; }
.view-switch a {
  font-size: 0.86rem; font-weight: 560; text-decoration: none; color: var(--muted);
  padding-bottom: 0.3rem; border-bottom: 2px solid transparent;
}
.view-switch a:hover { color: var(--brand); }
.view-switch a.is-current { color: var(--brand-deep); border-bottom-color: var(--gold); }

/* ------------------------------------------------ tests these students sat */

/* The MAP tests a class or homeroom is read on, as banners
   (analytics/_test_banner.html). The open test sits on --ground, where the
   gold may be text, with the gold as its bottom rule and the KFS mark behind
   it; the others are paper cards whose border turns gold on hover. One test
   at a time, each against its own norm — the banners switch, never merge. */
.test-banners { margin: 0 0 1.1rem; }
.test-banners-kicker { margin: 0 0 0.45rem; font-size: var(--fs-p1); font-weight: 600; color: var(--gold-text); }
.test-banners-row {
  display: grid; gap: 0.75rem;
  grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr));
}
.test-banner {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: 0.8rem;
  padding: 0.85rem 0.95rem 1rem;
  background: var(--paper); border: 1px solid var(--line); border-radius: 12px;
  text-decoration: none; color: var(--ink);
  transition: border-color .15s ease, transform .15s ease;
}
.test-banner:hover { border-color: var(--gold); transform: translateY(-1px); }
.test-banner-icon {
  flex: none; display: grid; place-items: center;
  width: 2.65rem; height: 2.65rem; border-radius: 10px;
  background: var(--wash); border: 1px solid var(--line); color: var(--brand);
}
.test-banner-icon svg { width: 1.4rem; height: 1.4rem; }
.test-banner-text { position: relative; display: flex; flex-direction: column; min-width: 0; }
.test-banner-title { font-size: var(--fs-p3); font-weight: 600; line-height: 1.2; color: var(--brand); }
.test-banner-sub { margin-top: 0.15rem; font-size: var(--fs-p1); line-height: 1.3; color: var(--muted); }
.test-banner-with { margin-top: 0; }
.test-banner.is-current { background: var(--ground); border-color: var(--ground); cursor: default; }
.test-banner.is-current:hover { transform: none; }
.test-banner.is-current::after {
  content: ""; position: absolute; inset: auto 0 0 0; height: 4px; background: var(--gold);
}
.test-banner.is-current .test-banner-icon { background: transparent; border: 1.5px solid var(--gold); color: var(--gold-light); }
.test-banner.is-current .test-banner-text { padding-inline-end: 4.6rem; }
.test-banner.is-current .test-banner-title { color: var(--on-ground); }
.test-banner.is-current .test-banner-sub { color: var(--on-ground-dim); }
.test-banner-mark {
  position: absolute; right: -1.6rem; top: 50%; transform: translateY(-50%);
  width: 6.9rem; height: 6.9rem; opacity: 0.3; pointer-events: none;
}
.test-banner-tag {
  position: absolute; top: 0.55rem; right: 0.65rem;
  font-size: var(--fs-p1); line-height: 1.35; color: var(--gold-light);
  border: 1px solid var(--ground-line); border-radius: 999px; padding: 0 0.5rem;
}
@media (max-width: 600px) {
  .test-banners-row { grid-template-columns: 1fr; gap: 0.5rem; }
  .test-banner { padding: 0.65rem 0.8rem 0.8rem; }
  .test-banner-icon { width: 2.15rem; height: 2.15rem; }
  .test-banner-icon svg { width: 1.15rem; height: 1.15rem; }
  .test-banner-title { font-size: var(--fs-p2); }
  .test-banner-mark { width: 5rem; height: 5rem; right: -1.1rem; }
}
@media print { .test-banners { display: none; } }
@media (prefers-reduced-motion: reduce) { .test-banner { transition: none; } }

.year-note { margin: -0.4rem 0 1.1rem; }

/* ------------------------------------------------------- the pupil list */

.pupil-table th[scope="row"] { font-weight: 600; }
.pupil-table th[scope="row"] .meta {
  display: block; font-size: 0.72rem; font-weight: 400; color: var(--muted);
}
.pupil-table tr.is-alert th[scope="row"] { box-shadow: inset 3px 0 0 var(--wte); }
.pupil-table .with-you { font-size: 0.8rem; }
.pupil-table .with-you a { color: var(--brand); text-decoration: none; }
.pupil-table .with-you a:hover { text-decoration: underline; }
.pupil-table .with-you .sep { color: var(--muted); margin: 0 0.25rem; }
.pupil-table .measures { min-width: 30rem; }
/* One line per subject, and every fact about that subject on it — laid out on
   a grid so the columns still line up down the page. Split into real table
   columns instead, three subjects lined up against two distances and the
   reader had to work out which was which: the arithmetic this page exists to
   stop people doing. */
.pupil-table .measure {
  display: grid; align-items: baseline; column-gap: 0.55rem;
  grid-template-columns: 8.5rem 2.6rem 4.6rem 5.6rem 8.6rem auto;
  font-size: 0.82rem; padding: 0.12rem 0;
}
.pupil-table .measure .no-band { grid-column: 2 / -1; }
.pupil-table .measure .flags:empty { display: none; }
/* The companion half of an English test is shown, never merged: it is set
   back so a reader can see at a glance which figure is which subject. */
.pupil-table .measure.is-companion { padding-inline-start: 0.85rem; }
.pupil-table .measure.is-companion { grid-template-columns: 7.65rem 2.6rem 4.6rem 5.6rem 8.6rem auto; }
/* A mark, for the subjects MAP does not test. Set apart from the MAP measures
   above it on purpose: they wear the same three band colours and are not the
   same measurement, and a row that ran them together would invite the average
   nobody should be taking. */
.pupil-table .measure.is-mark { grid-template-columns: 8.5rem 2.6rem 6rem auto; }
.pupil-table .measure.is-mark .measure-subject { color: var(--muted); }
.pupil-table .measure-subject { color: var(--muted); }
.pupil-table .measure-rit { font-variant-numeric: tabular-nums; }
.pupil-table .to-next { font-variant-numeric: tabular-nums; color: var(--ink); }
.pupil-table .growth { color: var(--muted); }
.pupil-table .growth.is-met { color: var(--mark-wae); }
.pupil-table .growth.is-missed { color: var(--wte); }
.pupil-table .band { min-width: 2.6rem; }
.pupil-table .pill-top10 { color: var(--brand); border-color: var(--brand-light); }
.pupil-table .of { color: var(--muted); font-weight: 400; }
.pupil-table td .meta { display: block; font-size: 0.72rem; color: var(--muted); }
.pupil-table .empty-state { padding: 0.5rem 0; }
/* What a column's share is of, under its heading — the roster's `.th-note`. */
.pupil-table .th-note {
  display: block; font-weight: 400; font-size: 0.62rem; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted);
}

/* ------------------------------------------------------------ stat strip */

.strip { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.strip .stat { flex: 1 1 8rem; margin: 0; }
.strip .stat .value { line-height: 1.15; }
.strip .stat .of { font-size: 0.9rem; font-weight: 400; color: var(--muted); }
.stat-wae { border-inline-start-color: var(--mark-wae); }
.stat-wwe { border-inline-start-color: var(--mark-wwe); }
.stat-wte { border-inline-start-color: var(--mark-wte); }
.stat-wte .value { color: var(--wte); }

/* ---------------------------------------------------------------- charts */

.chart {
  margin: 0 0 1.75rem; background: var(--paper); border: 1px solid var(--line);
  border-radius: 12px; padding: 0.9rem 1rem 0.8rem;
}
.chart-title { margin: 0 0 0.5rem; font-size: 0.92rem; color: var(--brand-deep); }
.chart .plot { width: 100%; height: auto; display: block; overflow: visible; }
.chart-empty { color: var(--muted); }
.chart-empty p { margin: 0; }

.plot .grid { stroke: var(--line); stroke-width: 1; }
.plot .axis { stroke: #C6CBCB; stroke-width: 1; }
.plot .tick { font-size: 10px; fill: var(--muted); }
.plot .axis-label { font-size: 10px; fill: var(--muted); letter-spacing: 0.04em; text-transform: uppercase; }

.plot .mark-bar { fill: var(--mark-wae); fill-opacity: 0.5; rx: 3; }
.plot .mark-bar:hover { fill-opacity: 0.8; }

.plot .line { stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.plot .line-norm { stroke: var(--mark-norm); stroke-width: 2; stroke-dasharray: 1 4; stroke-linecap: round; }
.plot .line-series { stroke: var(--brand); }
.plot .line-fit { stroke: var(--mark-fit); stroke-width: 2; stroke-dasharray: 7 4; }

.plot .cut { stroke-width: 1.5; stroke-dasharray: 4 3; }
.plot .cut-below { stroke: var(--mark-wte); }
.plot .cut-above { stroke: var(--mark-wae); }
.plot .cut-top10 { stroke: var(--brand-light); }
.plot .cut-divider { stroke: #9BA3A3; stroke-dasharray: 3 3; }
/* .plot .cut sets width and dash but no colour, and an SVG line with no stroke
   is an invisible line: the comparison the chart is built around would simply
   not be drawn. */
.plot .cut-norm { stroke: var(--mark-norm); }
/* A cut-off label sits wherever its line does, which on a bar chart is often
   on top of a bar — "School above 31.7%" landed across Grade 10's column and
   read as a figure for that grade. `paint-order: stroke` draws a paper-coloured
   halo behind the glyphs first, so the label stays readable over a mark of
   any colour without having to know what is underneath it. */
.plot .cut-label {
  font-size: 9px; fill: var(--muted); font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--paper); stroke-width: 3px;
  stroke-linejoin: round;
}

.plot .dot { stroke: var(--paper); stroke-width: 2; }
.plot .dot-WAE { fill: var(--mark-wae); }
.plot .dot-WWE { fill: var(--mark-wwe); }
.plot .dot-WTE { fill: var(--mark-wte); }
.plot .dot-none { fill: #B9BFBF; }
.plot .dot-series { fill: var(--brand); }
.plot .dot-rug { fill: var(--brand-deep); fill-opacity: 0.55; stroke: none; }
.plot .dot.is-top10 { stroke: var(--gold); stroke-width: 2.5; }
.plot .dot.is-low-confidence { fill-opacity: 0.45; stroke-dasharray: 2 2; }
.plot a:hover .dot { stroke: var(--brand-deep); }
.plot a { cursor: pointer; }

.plot .quad rect { fill: var(--wash); stroke: var(--paper); stroke-width: 2; }
.plot .quad text {
  font-size: 10px; fill: var(--muted);
  /* The top gridline can fall on the caption row: halo the words so a rule
     never cuts through them (same idiom as the grade labels above). */
  paint-order: stroke; stroke: var(--paper); stroke-width: 3px; stroke-linejoin: round;
}
.plot .quad-priority rect { fill: rgba(176, 71, 62, 0.09); }
.plot .quad-catching-up rect { fill: rgba(171, 152, 53, 0.10); }
.plot .quad-coasting rect { fill: rgba(110, 118, 121, 0.09); }
.plot .quad-thriving rect { fill: rgba(0, 128, 106, 0.10); }

.chart figcaption { font-size: 0.78rem; color: var(--muted); margin-top: 0.5rem; line-height: 1.45; }
.chart .limitation { display: block; margin-top: 0.3rem; padding-inline-start: 0.6rem; border-inline-start: 2px solid var(--warn-line); }
.chart-data { margin-top: 0.7rem; font-size: 0.82rem; }
.chart-data > summary { cursor: pointer; color: var(--brand); font-weight: 600; }
.chart-data ul { margin: 0.55rem 0 0; padding-inline-start: 1.25rem; }
.chart-data li + li { margin-top: 0.25rem; }

.legend { display: flex; gap: 0.35rem 1rem; flex-wrap: wrap; list-style: none; margin: 0.5rem 0 0; padding: 0; font-size: 0.76rem; color: var(--muted); }
.legend li { display: flex; align-items: center; gap: 0.35rem; }
.legend i { display: inline-block; width: 14px; height: 3px; border-radius: 2px; background: var(--muted); }
.legend .legend-class i { background: var(--mark-wae); height: 10px; border-radius: 3px; }
.legend .legend-norm i { background: repeating-linear-gradient(90deg, var(--mark-norm) 0 2px, transparent 2px 5px); }
.legend .legend-series i { background: var(--brand); }
.legend .legend-fit i { background: repeating-linear-gradient(90deg, var(--mark-fit) 0 6px, transparent 6px 10px); }

/* The key beside the strand grid (analytics/_strand_grid.html). Redesigned
   twice on 2026-09-05; the second time because a key of nine rows and two
   paragraphs was more to read than the grid. Now: the three band pills on one
   line, the five strand words as one scale with the percentile cuts ticked
   under it, and two sentences. The intervals come out of glossary.ADJECTIVES
   and the cuts out of glossary.QUINTILE_CUTS — never typed here. */
.strand-panel { display: flex; gap: 1.25rem; align-items: flex-start; flex-wrap: wrap; }
.strand-key {
  flex: 1 1 16rem; max-width: 26rem; min-width: 0;
  padding: 0.9rem 1rem 0.8rem; border: 1px solid var(--line); border-radius: 10px;
  background: var(--wash); font-size: 0.78rem; line-height: 1.45; color: var(--muted);
}
.strand-key p { margin: 0; }
.strand-key-eyebrow {
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--brand); font-weight: 650; margin: 0 0 0.45rem !important;
}
.strand-key-eyebrow + .strand-key-eyebrow,
.strand-key-bands + .strand-key-eyebrow { margin-top: 1rem !important; }
.strand-key-bands {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 0.3rem; color: var(--ink);
}
.strand-key-bands li { display: flex; align-items: center; gap: 0.5rem; white-space: nowrap; }
.strand-key-bands .chip { min-width: 2.6rem; text-align: center; }
/* One scale, five equal segments: the words are quintiles by definition, so
   equal widths are the truth and not a simplification. */
.strand-scale {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 2px;
  border-radius: 4px; overflow: hidden;
}
.strand-scale li {
  display: flex; align-items: center; justify-content: center;
  min-height: 1.7rem; padding: 0.2rem 0.15rem;
  font-size: 0.68rem; font-weight: 600; letter-spacing: 0.01em; color: var(--ink);
  text-align: center; line-height: 1.15; cursor: help;
}
.strand-scale .shade-s1 { background: var(--shade-1); color: #fff; }
.strand-scale .shade-s2 { background: var(--shade-2); }
.strand-scale .shade-s3 { background: var(--shade-3); }
.strand-scale .shade-s4 { background: var(--shade-4); }
.strand-scale .shade-s5 { background: var(--shade-5); color: #fff; }
/* Each cut is placed at its own percentile of the scale's width, so the tick
   sits under the boundary it names; the ends hug the edges. */
.strand-scale-ticks {
  position: relative; height: 1rem; margin-top: 0.15rem;
  font-size: 0.64rem; font-variant-numeric: tabular-nums; color: var(--muted);
}
.strand-scale-ticks span { position: absolute; top: 0; transform: translateX(-50%); }
.strand-scale-ticks .is-start { left: 0; transform: none; }
.strand-scale-ticks .is-end { left: auto; right: 0; transform: none; }
.strand-key-none { margin-top: 0.5rem !important; display: flex; align-items: center; gap: 0.45rem; }
.strand-key-none i {
  display: inline-flex; width: 1.4rem; height: 1.1rem; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 3px; background: var(--paper); font-style: normal;
}
.strand-key-note { margin-top: 0.8rem !important; padding-top: 0.7rem; border-top: 1px solid var(--line); }
.strand-key-more { margin-top: 0.55rem; }
.strand-key-more > summary { cursor: pointer; color: var(--brand); font-weight: 600; font-size: 0.74rem; }
.strand-key-more p { margin-top: 0.35rem; }

/* ---------------------------------------------------------- sparklines */

.spark-line { display: block; overflow: visible; }
.spark-line path { stroke: var(--brand-light); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.spark-line circle { fill: var(--brand); }
.spark-up path { stroke: var(--mark-wae); }
.spark-up circle { fill: var(--mark-wae); }
.spark-down path { stroke: var(--mark-wte); }
.spark-down circle { fill: var(--mark-wte); }
.spark-none { font-size: 0.85rem; }

/* --------------------------------------------------- the Lexile spread */
/* Deliberately one neutral colour throughout — no band tint (rule 12b): a
   Lexile is a reading measure and never a standing. */

.lexile-spread { display: block; margin: 0.4rem 0 0.6rem; overflow: visible; }
.lex-track { stroke: var(--line); stroke-width: 2; stroke-linecap: round; }
.lex-tick { stroke: var(--muted); stroke-width: 1.6; }
.lex-tick-median { stroke: var(--ink); stroke-width: 2.2; }
.lex-label { font-size: 0.65rem; fill: var(--muted); }
.lex-label-median { fill: var(--ink); font-weight: 600; }

/* One pupil's line under the standing cards — same neutral family, drawn
   through the shared chart partial. Grey shelf, grey dots, brand line: no
   band colour anywhere (rule 12b). */
.lexile-journey { margin: -0.5rem 0 1.5rem; padding: 0.9rem 1.1rem 0.4rem; }
.lexile-journey h3 { margin: 0 0 0.25rem; font-size: 0.95rem; }
.lexile-journey .chart { margin: 0; }
.lexile-journey .lexile-now { margin-bottom: 0.9rem; }
.lexile-journey .lexile-now .value { font-variant-numeric: tabular-nums; }
.plot .zone-shelf { fill: var(--line); fill-opacity: 0.45; }
.plot .dot-lexile { fill: var(--muted); }
.plot .dot-lexile-now { fill: var(--brand-deep); }
.legend .legend-zone-shelf i { background: var(--line); height: 10px; border-radius: 3px; }

/* -------------------------------------------------------- the heatmap */

/* Sized to its content and not to the page: the box shrinks round a
   three-strand grid and the key takes the rest of the row; a five-strand grid
   (MAP tests carry three to five goals) is ~50rem and fits beside the key;
   wider than the column, the wrap scrolls. */
.heatmap-wrap { overflow-x: auto; width: fit-content; max-width: 100%; flex: 0 1 auto; }
.heatmap { font-size: 0.8rem; width: auto; }
/* Headings read across since 2026-09-05. Rotated 90° they took a 7.5rem
   column of small caps to say three words; at 7rem a column a strand name
   wraps to two or three lines and the cell under it is wide enough for a word. */
.heatmap th.strand {
  vertical-align: bottom; text-align: center; padding: 0.6rem 0.35rem 0.5rem;
  width: 7rem; min-width: 6rem;
}
.heatmap th.strand .strand-head {
  display: inline-block; max-width: 7rem; white-space: normal; line-height: 1.25;
  font-size: 0.68rem; cursor: help;
}
.heatmap th.pupil-head { min-width: 14rem; vertical-align: bottom; padding-bottom: 0.5rem; }
.heatmap tbody th {
  white-space: nowrap; font-size: 0.82rem; text-transform: none; letter-spacing: 0;
  color: var(--ink); font-weight: 500; padding-inline-end: 0.9rem;
}
.heatmap tbody th a { text-decoration: none; }
.heatmap tbody th a:hover { text-decoration: underline; }
/* Name on the left, pill on the right, so the pills line up in a column
   instead of trailing names of different lengths. */
.heatmap .pupil-line { display: flex; justify-content: space-between; align-items: center; gap: 0.75rem; }
.heatmap .cell {
  width: 7rem; min-width: 6rem; height: 2.15rem;
  text-align: center; font-size: 0.72rem; font-weight: 500; letter-spacing: 0.01em;
  border: 2px solid var(--paper); border-radius: 3px; color: var(--ink);
  padding: 0.35rem 0.35rem; white-space: nowrap;
}
.heatmap .cell-s1 { background: var(--shade-1); color: #fff; font-weight: 600; }
.heatmap .cell-s2 { background: var(--shade-2); }
.heatmap .cell-s3 { background: var(--shade-3); }
.heatmap .cell-s4 { background: var(--shade-4); }
.heatmap .cell-s5 { background: var(--shade-5); color: #fff; font-weight: 600; }
.heatmap .cell-none { background: var(--paper); color: var(--muted); }
.heatmap tbody tr:hover { background: transparent; }
.heatmap tbody tr:hover th { background: var(--wash); }
/* The roster is sorted band-then-RIT, so a rule where the band changes turns
   the sort into three visible groups. A faint tint on the header of a WTE row
   is the roster's own convention (.roster .row-WTE). */
.heatmap tbody tr.row-WTE th { background: rgba(176, 71, 62, 0.04); }
.heatmap tbody tr.is-group-start > th,
.heatmap tbody tr.is-group-start > td { border-top: 2px solid var(--line); }
.heatmap .cell, .heatmap th.strand { position: relative; }
.heatmap .cell { cursor: help; }
.heatmap .cell:hover { outline: 2px solid var(--brand-deep); outline-offset: -2px; }

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

.chip {
  display: inline-block; padding: 0.05rem 0.4rem; border-radius: 4px;
  font-size: 0.68rem; font-weight: 600; letter-spacing: 0.03em;
  border: 1px solid transparent; white-space: nowrap;
}
.chip-WAE { background: rgba(0, 128, 106, 0.12); color: var(--wae); border-color: rgba(0, 128, 106, 0.3); }
.chip-WWE { background: rgba(171, 152, 53, 0.15); color: #7a6a15; border-color: rgba(171, 152, 53, 0.4); }
.chip-WTE { background: rgba(176, 71, 62, 0.12); color: var(--wte); border-color: rgba(176, 71, 62, 0.3); }
.chip-none { background: var(--wash); color: var(--muted); border-color: var(--line); }

/* ABT's own three judgement words — Above / Inline / Below. Coloured the
   same as the band chips above because both read "did they clear a line",
   but kept as separate classes because they are not the same line: an ABT
   skill judgement is never WAE/WWE/WTE (CLAUDE.md rule 8). */
.chip-abt-above { background: rgba(0, 128, 106, 0.12); color: var(--wae); border-color: rgba(0, 128, 106, 0.3); }
.chip-abt-inline { background: rgba(171, 152, 53, 0.15); color: #7a6a15; border-color: rgba(171, 152, 53, 0.4); }
.chip-abt-below { background: rgba(176, 71, 62, 0.12); color: var(--wte); border-color: rgba(176, 71, 62, 0.3); }
.chip-row { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }

.roster tbody th { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink); white-space: nowrap; }
.roster tbody th a { text-decoration: none; }
.roster tbody th a:hover { text-decoration: underline; }
.roster .row-WTE { background: rgba(176, 71, 62, 0.04); }
.roster .row-WTE:hover { background: rgba(176, 71, 62, 0.08); }
/* One pupil, one line. A wrapped "Below typical growth" turns every row into
   three and the register stops being scannable, which is the only thing it is
   for. Wider than the column? .table-wrap scrolls. */
.roster td, .roster th { white-space: nowrap; }
.roster .flags { white-space: nowrap; }
.roster th { line-height: 1.25; }
.roster .is-gap { color: #7a5a10; }
.roster td.num { font-variant-numeric: tabular-nums; }
.roster .growth-below-typical-growth { color: var(--wte); }
.roster .growth-above-typical-growth { color: var(--wae); }
.roster .growth-not-measured { color: var(--muted); }

.pill-alert { background: rgba(176, 71, 62, 0.1); border-color: var(--wte); color: var(--wte); }

/* Reading Fluency */
/* The shared, neutral parts of MAP Reading Fluency: the sitting head, the
   figure tiles the Growth and Coverage screens draw, NWEA's level word and the
   word tables. Since DR-230 a Reading Fluency band wears the platform's own
   band tokens and chip classes — in the pupil and room blocks below — but
   nothing in this block reads --wae / --wwe / --wte, --mark-*, --shade-* or a
   chip tone: NWEA's word is text under NWEA's own heading and is read as text,
   and a figure that decides no band is ink. Shared by _fluency_result.html,
   _fluency_levels.html and _fluency_roster.html, so the pupil, class, teacher,
   growth and coverage screens draw the same thing. */
.fluency-result { padding: 1rem 1.15rem 0.75rem; margin-bottom: 1rem; }
.fluency-result .fl-head { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; align-items: baseline; margin-bottom: 0.6rem; }
.fluency-result .fl-title { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: baseline; }
.fluency-result .fl-sitting { color: var(--ink); }
.fluency-result .fl-form { color: var(--muted); font-size: 0.85rem; }
.fluency-result .table-wrap { margin: 0.75rem 0; }
.fluency-result caption { caption-side: top; text-align: start; padding: 0.4rem 0.6rem; font-size: 0.78rem; color: var(--muted); }
.fl-figures { display: flex; gap: 1rem; flex-wrap: wrap; margin: 0.25rem 0 0.5rem; }
.fl-figure { flex: 1 1 12rem; padding: 0.7rem 0.9rem; border: 1px solid var(--line); border-radius: 10px; background: var(--paper); }
.fl-figure .label { font-size: 0.75rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
/* --ink and not --brand: a brand-coloured figure is how every banded MAP
   figure is drawn, and this one is deliberately not one of them. */
.fl-figure .value { font-size: 1.6rem; font-weight: 650; color: var(--ink); font-variant-numeric: tabular-nums; }
.fl-figure .note { font-size: 0.75rem; color: var(--muted); }
.fl-table th, .fl-table td { vertical-align: top; }
.fl-table th.num, .fl-table td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.fl-table tbody th, .fl-levels tbody th { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink); font-size: 0.85rem; }
.fl-measure { cursor: help; border-bottom: 1px dotted var(--line); }
/* The word is the whole answer. Weight, not colour, marks it out from the
   note beside it; an unrecognised word is the same weight — it is NWEA's
   text as it arrived, not a verdict on it. */
.fl-level { font-weight: 600; color: var(--ink); white-space: nowrap; }
.fl-level-unknown { font-style: italic; }
.fl-level-unknown::after { content: " (as arrived)"; font-style: normal; font-weight: 400; color: var(--muted); font-size: 0.75rem; }
.fl-zpd { white-space: nowrap; }
.fl-zpd-line { display: block; white-space: nowrap; }
.fl-none { color: var(--muted); }
.fl-comp { margin: 0.25rem 0 0.5rem; }
.fl-about { margin-top: 0.35rem; }
/* The level-count table: NWEA's four words are the column headings and may
   wrap to two lines; a count sits under its word. */
.fl-levels th.fl-word { width: 7.5rem; white-space: normal; line-height: 1.25; vertical-align: bottom; }
.fl-levels caption { caption-side: top; text-align: start; padding: 0.4rem 0.6rem; font-size: 0.78rem; color: var(--muted); }
.fl-levels td .note { white-space: normal; }
/* Reading Fluency — pupil (DR-230) */
/* The pupil page's charts: the percentile profile and the Early Literacy RIT
   history (_fluency_pupil_profile.html, _fluency_pupil_history.html). Their
   band colours are the platform's own classes used as they are used for MAP
   Growth — .zone-* and .dot-* on the profile chart — so nothing below restates
   a band token. The RIT history is neutral: a change in RIT has no norm
   (DR-165). The page around them is the next block. */
/* The profile chart: row names in ink, the overall one heavier; the band
   words over the shaded regions, in each band's own colour (the room block's
   .mark-label-fl-zone and .fl-zone-* rules, shared with the room's strips); a
   figure beside each dot, haloed so it reads over the school-median line; the
   median line itself in ink. */
.chart-fluency-profile .plot .mark-label-fl-row { font-size: 11px; fill: var(--ink); }
.chart-fluency-profile .plot .mark-label-fl-row-overall { font-size: 11px; font-weight: 700; fill: var(--ink); }
.plot .mark-label-fl-value,
.plot .mark-label-fl-change {
  font-size: 10.5px; fill: var(--ink); font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--paper); stroke-width: 3px; stroke-linejoin: round;
}
.plot .mark-label-fl-change { font-weight: 700; }
/* Scoped to the profile: the room's strips draw their median and name their
   Top 10% flag differently (the room block below), and an unscoped rule there
   repainted this one. Each swatch is the mark this chart draws — the median a
   solid ink tick, the flag the dashed line the caption names. */
.chart-fluency-profile .plot .cut-fl-median { stroke: var(--ink); stroke-width: 2.5; stroke-dasharray: none; stroke-linecap: round; }
.chart-fluency-profile .legend .legend-fl-median i { width: 3px; height: 12px; border-radius: 1px; background: var(--ink); }
.chart-fluency-profile .legend .legend-fl-top10 i { width: 2px; height: 12px; border-radius: 0; background: repeating-linear-gradient(180deg, var(--brand-light) 0 4px, transparent 4px 7px); }
/* The RIT history: one neutral line, the latest sitting's dot in ink. */
.plot .line-fl-rit { stroke: var(--muted); }
.plot .dot-fl-rit { fill: var(--muted); }
.plot .dot-fl-rit-now { fill: var(--ink); }
/* Reading Fluency — pupil page, Grade 3 layout (2026-09-24) */
/* The pupil page's Reading Fluency in the Grade 3 page's own classes (DR-273):
   the card is a .stand-card in the .standing grid, the chart sits in a card
   where .lexile-journey sits, the history is a .history-card with a .fold
   timeline under it, and Measure by measure is a plain table the way Weakest
   goal strands is. This block adds only what those classes do not say — the
   card's next steps and scale line, the part named under a measure, and the
   percentile's two weights — and restates no band token: a band is a
   .chip-W* or .stand-W* of the platform's own, and NWEA's level, a percentile
   shown for information and the school median are ink or muted ink, never a
   band colour (DR-232). */
.flp-no-band { color: var(--muted); font-weight: 500; font-size: 0.86rem; }
/* The card holds a subject card's facts and NWEA's next step in two domains,
   so where the grid has room for two columns it takes two: at one it stood
   twice the height of a MAP Growth card beside it. A phone's one column is
   never asked for two. */
@media (min-width: 700px) {
  .standing > .flp-card { grid-column: span 2; }
}
.flp-card-steps > b { color: var(--brand-deep); }
.flp-card-steps ul { list-style: none; margin: 0.2rem 0 0; padding: 0; }
.flp-card-steps li { margin: 0.1rem 0 0; }
.flp-card-steps li b { color: var(--ink); font-weight: 600; }
.flp-card-other .chip { font-size: 0.68rem; }
.flp-card-scale { margin-top: 0.55rem; }
.flp-card-scale + .stand-foot { margin-top: 0.15rem; }
/* Where a Grade 3 page draws its Lexile: the same spacing as .lexile-journey. */
.flp-profile-card { margin: -0.5rem 0 1.5rem; padding: 0.9rem 1.1rem 0.4rem; }
.flp-profile-card h3 { margin: 0 0 0.25rem; font-size: 0.95rem; }
.flp-profile-card .chart { margin: 0; }
.flp-history-card { margin-bottom: 0.75rem; }
.flp-history-card .chart { margin: 0; }
.flp-history-note { margin: 0.5rem 0 0; }
.flp-timeline { margin-bottom: 1.5rem; }
/* A measure's name, and under it which part of the test it is — the column a
   Grade 3 strands table gives the subject. */
.flp-part { display: block; margin-top: 0.1rem; font-size: 0.72rem; font-weight: 400; color: var(--muted); }
.flp-band-cell .chip { vertical-align: 0.05em; }
.flp-basis-short, .flp-why { font-size: 0.74rem; color: var(--muted); }
/* The percentile a band is read from is bold ink (<b>); one shown for
   information only is muted, and so is the school median under it. */
.flp-pct-plain { color: var(--muted); }
.flp-median { display: block; font-size: 0.72rem; color: var(--muted); white-space: nowrap; }
.flp-measures { margin-bottom: 1.5rem; }
.flp-measures > .note { max-width: 84ch; }
.flp-about { margin-top: 0.3rem; }
.flp-about-list { margin: 0.55rem 0 0; display: grid; gap: 0.65rem; }
.flp-about-list dt { font-weight: 650; color: var(--ink); font-size: 0.82rem; }
.flp-about-list dd { margin: 0.1rem 0 0; font-size: 0.8rem; color: var(--muted); line-height: 1.45; max-width: 84ch; }
.flp-about-list ul { margin: 0.3rem 0 0; padding-inline-start: 1.1rem; }
@media print {
  .flp-card, .flp-profile-card, .flp-history-card { break-inside: avoid; }
}
.fluency-roster .fl-lead { margin: 0 0 0.25rem; }
.fluency-roster .fl-tested { font-variant-numeric: tabular-nums; }
.fluency-roster .fl-mismatch { display: block; }
/* A pupil with no result keeps their line and says so in words — never a
   blank cell that reads as a zero (rule 15a). */
.fl-roster tr.fl-untested th { color: var(--muted); font-style: italic; }
.fl-roster tr.fl-untested td { color: var(--muted); font-style: italic; }
.fl-roster td .fl-zpd-line { font-size: 0.72rem; }
@media print {
  .fl-about { display: none; }
  .fl-measure { border-bottom: 0; }
}

/* Reading Fluency — My classes and teacher page */
/* The one line on a My-classes card and the panel on the teacher page read
   the same tokens as the block above — ink for the figure, muted for the rest
   — and since DR-230 their band counts wear the card's own band-key colours
   (.fl-band-counts, in the room block below). The card line sits under the
   card's own "Measured on" line at that line's size. */
.class-card .fl-card-line { margin: 0.3rem 0 0; font-size: 0.76rem; color: var(--muted); }
.class-card .fl-card-line b { color: var(--ink); font-weight: 600; }

/* Reading Fluency — room: bands, charts, teaching groups (DR-230) */
/* A Reading Fluency band is drawn in the platform's own band tokens and chip
   classes — the ones the block above refused while a Fluency band did not
   exist. It has two sources: the overall score and Language comprehension are
   banded on the NWEA percentile at MAP Growth's own cuts, every other measure
   on NWEA's performance level, and every heading says which (.fl-basis). A
   percentile beside a level-band decides nothing, so it is never tinted. A
   pupil with no band is a dashed outline or a plain cell, never a fourth band
   and never a zero (rule 15a). The class page's part is laid out as a Grade 3
   room's is since 2026-09-24 — its own block at the end of this one. */

.fluency-room .fl-lead { margin: 0 0 0.2rem; }
.fluency-room .fl-mismatch { display: block; }
.fluency-room .fl-lede { margin: 0 0 0.6rem; max-width: 82ch; }
.fluency-room .fl-dates { color: var(--muted); font-size: 0.85rem; }
/* The two medians, one quiet line under the lede: a median is not a band
   count and gets no tile, no colour and no size of its own. */
.fluency-room .fl-medians { margin: 0 0 0.5rem; font-variant-numeric: tabular-nums; }

/* Across the years (DR-239…DR-241). The sitting switch is the page's own
   .view-switch — the MAP subject line's underline — wrapping where three
   sittings do not fit a phone. An earlier year's sitting says so on the lead
   in ink, and once more in Start here; the table of the room's sittings marks
   the row shown with the gold rule Start here wears, and its counts wear the
   band-key colours a card's line does. A withheld figure is muted words. */
.fluency-room .fl-switch { flex-wrap: wrap; row-gap: 0.4rem; }
.fluency-room .fl-earlier { color: var(--ink); }
.fluency-room .fl-start-when { margin: 0.35rem 0 0; font-weight: 600; color: var(--ink); }
.fl-over-time-block { margin: 0; }
.fl-over-time-wrap { margin: 0.4rem 0 0.35rem; }
.fl-over-time { font-variant-numeric: tabular-nums; }
.fl-over-time caption { caption-side: top; text-align: start; padding: 0.4rem 0.6rem; }
/* Headings wrap and figures may break before their count, so the table fits a
   1024 px screen; a sitting's name never breaks. */
.fl-table.fl-over-time thead th { vertical-align: bottom; line-height: 1.25; white-space: normal; }
.fl-over-time tbody th[scope="row"] { white-space: nowrap; }
.fl-table.fl-over-time td.num { white-space: normal; }
.fl-table.fl-over-time td.fl-sat { white-space: nowrap; }
.fl-over-time td.fl-test { min-width: 8.5rem; }
.fl-over-time th.fl-band-group { text-align: center; }
.fl-over-time tbody tr.is-shown th, .fl-over-time tbody tr.is-shown td { background: var(--wash); }
.fl-over-time tbody tr.is-shown th[scope="row"] { box-shadow: inset 3px 0 0 var(--gold); }
.fl-over-time .fl-shown {
  margin-inline-start: 0.35rem; font-size: 0.66rem; font-weight: 650; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--brand-deep);
}
.fl-over-time .fl-n { color: var(--muted); font-size: 0.78rem; white-space: nowrap; }
.fl-history-note { margin: 0 0 0.2rem; max-width: 82ch; }
/* The chart's count under each dot and the words in a slot with none: one
   class deeper than `.plot .mark-label`, which is declared further down. */
.chart .plot .mark-label-fl-n { font-size: 10px; fill: var(--muted); font-variant-numeric: tabular-nums; }
.chart .plot .mark-label-fl-absent { font-size: 10px; font-style: italic; fill: var(--muted); }
@media print {
  .fluency-room .fl-switch { display: none; }
  .fl-over-time tbody tr.is-shown th, .fl-over-time tbody tr.is-shown td { background: none; }
}

/* The drawings' marks: the band mix the teacher page draws, and the band
   words the pupil page's profile prints over its regions. */
.plot .bar-fl-none { fill: var(--paper); fill-opacity: 1; stroke: #8E9799; stroke-width: 1; stroke-dasharray: 3 2; }
/* A hairline between segments so two counts never read as one bar; the
   outline keeps its own dashed stroke. */
.chart-fl-bands .plot .mark-bar { stroke: var(--paper); stroke-width: 1; }
.chart-fl-bands .plot .bar-fl-none { stroke: #8E9799; stroke-dasharray: 3 2; }
/* One class deeper than `.plot .mark-label`, which is declared further down
   this file and would otherwise paint every count and band word in ink. */
.chart .plot .mark-label-fl-on-dark { font-size: 11px; font-weight: 650; fill: var(--paper); font-variant-numeric: tabular-nums; }
.chart .plot .mark-label-fl-on-light { font-size: 11px; font-weight: 650; fill: var(--ink); font-variant-numeric: tabular-nums; }
.chart .plot .mark-label-fl-zone { font-size: 9px; font-weight: 650; letter-spacing: 0.05em; text-transform: uppercase; }
.chart .plot .fl-zone-WAE { fill: var(--wae); }
.chart .plot .fl-zone-WWE { fill: #7a6a15; }
.chart .plot .fl-zone-WTE { fill: var(--wte); }
.legend .legend-fl-none i { background: var(--paper); border: 1px dashed #8E9799; height: 10px; border-radius: 3px; }
/* A segment's card lists its pupils as one flowing line, and the ring that
   marks the segment pointed at follows the bar's corners rather than drawing
   an ellipse across it. */
.chart-fl-bands .tipcard .tip-intro { display: block; margin-top: 0.4rem; line-height: 1.5; }
.chart-fl-bands .hotspot.is-box:hover::after { inset: 0; border-radius: 3px; }

/* A head tile carrying "n of N": the N is the quieter half. */
.head-stat b small { font-size: 0.78rem; font-weight: 500; color: var(--muted); }

/* One line of band counts — a card, a teacher's room — in the card's own
   band-key words, inline rather than as the key's own row. */
.fl-band-counts.band-key { display: inline-flex; gap: 0.55rem; font-size: inherit; vertical-align: baseline; flex-wrap: wrap; }
/* The key's own reading: the number in its band's colour, the word muted. */
.fl-band-counts.band-key > span { color: var(--muted); }
.fl-band-counts.band-key b { font-size: 0.86rem; }
.fl-no-band { color: var(--muted); }

/* About these results — the glossary's sentences, folded. */
.fl-about-results { margin-top: 1rem; }
.fl-about-results p { margin: 0.4rem 0; max-width: 82ch; }
.fl-words { display: grid; grid-template-columns: max-content 1fr; gap: 0.2rem 1rem; margin: 0.55rem 0 0.2rem; font-size: 0.8rem; }
.fl-words div { display: contents; }
.fl-words dt { font-weight: 600; color: var(--ink); }
.fl-words dd { margin: 0; color: var(--muted); }

@media (max-width: 720px) {
  .fl-words { grid-template-columns: minmax(0, 1fr); }
  .fl-words dd { margin-bottom: 0.35rem; }
}

/* ---- Reading Fluency — a class in Grade 3's layout (2026-09-24, DR-263…DR-272)
   A Kindergarten room's part is drawn in the Grade 3 page's own classes
   wherever one fits — the .strip of .stat tiles, the .card.start-here, the
   .roster, the .chart and its .compare table, the .heatmap grid and its
   .strand-key — so the two class pages read as one design. What is Fluency's
   own is small: the MAP page's h3 headings under its one h2, the roster's two
   step columns, the grid's band tints (the chips' colours, one per band — never
   a second shade for Approaching, which would be a fourth band) and a level
   NWEA does not use set in italics, untinted. */

/* On the MAP page the part's sections sit under its own h2: an h3 in the h2's
   voice, a size down, so the rail keeps one chip for the part. */
.fluency-room .fl-sub-h {
  font-size: 0.98rem; margin: 1.6rem 0 0.5rem; color: var(--brand-deep);
  border-inline-start: 3px solid var(--gold-light); padding-inline-start: 0.55rem;
  scroll-margin-top: 3.25rem;
}
.fluency-room h2[id^="fl-"] { scroll-margin-top: 3.25rem; }
/* A Kindergarten marks page's head carries four tiles — pupils shown, sat it,
   with an overall band, and the page's own marks tile — one more than a
   Grade 3 head. Its title column gives way so the four sit in the head row
   beside the title at desktop width, as a Grade 3 head's three do: the lede
   wraps a line sooner rather than the tiles dropping under it (verify cycle
   1, L1). On a small phone four no longer fit one row, and there they sit two
   by two rather than run off the screen's edge. Both rules hang off
   .fl-head-stats, which only that head carries: the MAP page's head, where a
   Fluency tile joins MAP Growth's three, and every Grade 3 head are as they
   were (the phone rule once read .fl-head-stat, which the MAP page's tile
   carries too). */
.page-head:has(> .fl-head-stats) > div:first-child { flex: 1 1 24rem; }
@media (max-width: 480px) {
  .head-stats.fl-head-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; }
}
.fluency-room .fl-strip { margin-top: 0.9rem; }
.start-here h3.fl-sub-h { margin-top: 0; border: 0; padding: 0; font-size: 1.05rem; }
.fluency-room .fl-start .fl-start-when { margin: 0.2rem 0 0.1rem; }

/* The roster: the Grade 3 roster's look, with its faint Working Below rows
   (.roster .row-WTE's tint, under a class of Fluency's own) and two step
   columns whose names wrap rather than widen the table. */
.fl-roster-wrap { max-height: min(72vh, 48rem); }
.fl-roster tbody tr.fl-band-WTE { background: rgba(176, 71, 62, 0.04); }
.fl-roster tbody tr.fl-band-WTE:hover { background: rgba(176, 71, 62, 0.08); }
.fl-roster thead th { vertical-align: bottom; }
.fl-roster .fl-basis { display: block; margin-top: 0.12rem; font-size: 0.68rem; font-weight: 500; color: var(--muted); letter-spacing: 0; text-transform: none; }
.fl-roster td.fl-step-cell { white-space: normal; min-width: 8.5rem; font-size: 0.84rem; line-height: 1.35; }
.fl-roster td.fl-change { white-space: nowrap; }
.fl-roster .fl-since { display: block; font-size: 0.72rem; color: var(--muted); white-space: nowrap; }
.fl-roster .th-sort a { color: var(--brand-deep); text-decoration: none; }
.fl-roster .th-sort.is-on a { color: var(--ink); }
.fl-roster .has-tip .tip-anchor .fl-basis { cursor: help; }

/* Against the norm: the Grade 3 chart's marks, and the room's median one
   solid ink line — scoped to this chart, as the profile's are to the
   profile. */
.fl-norm-card .chart { margin-bottom: 1rem; }
/* The table scrolls inside its own row at phone width, where six columns are
   wider than the screen, and the note under it wraps. */
.fl-norm-card .compare-row { overflow-x: auto; margin-bottom: 0.6rem; }
.fl-norm-card .fl-compare-key { margin: 0; max-width: 82ch; }
.chart-fl-norm .plot .cut-fl-median { stroke: var(--ink); stroke-opacity: 0.7; stroke-width: 2; stroke-dasharray: none; }
.chart-fl-norm .legend .legend-fl-median i { width: 3px; height: 12px; border-radius: 1px; background: var(--ink); opacity: 0.7; }
.chart-fl-norm .tipcard .tip-intro { display: block; margin-top: 0.35rem; line-height: 1.5; }
.chart-fl-norm .hotspot.is-box:hover::after { inset: 0; border-radius: 3px; }

/* Measure by measure: the Grade 3 strand table, then the grid. */
.fl-measures { margin-bottom: 1rem; }
.fl-measures .fl-basis, .fl-grid .fl-basis {
  display: block; margin-top: 0.12rem; font-size: 0.66rem; font-weight: 500;
  color: var(--muted); letter-spacing: 0; text-transform: none;
}
.fl-measures tbody th { text-transform: none; letter-spacing: 0; color: var(--ink); font-size: 0.85rem; font-weight: 500; }
.fl-measures .fl-n { display: block; margin-top: 0.1rem; font-size: 0.72rem; color: var(--muted); white-space: nowrap; }
/* One tint per band, the chips' own colours, so a cell reads as the chip
   beside its name does; nothing reported is paper, never a colour. */
.heatmap .fl-cell-WAE { background: rgba(0, 128, 106, 0.18); color: var(--wae); font-weight: 600; }
.heatmap .fl-cell-WWE { background: rgba(171, 152, 53, 0.22); color: #6a5a12; font-weight: 600; }
.heatmap .fl-cell-WTE { background: rgba(176, 71, 62, 0.16); color: var(--wte); font-weight: 600; }
.heatmap .fl-cell-none { background: var(--paper); color: var(--muted); }
.fl-grid .fl-cell-unknown { font-style: italic; font-weight: 500; color: var(--ink); }
.fl-grid .cell-mark { display: block; }
/* A size narrower than the strand grid's, so six measures and the key sit
   side by side at 1366 px as three strands and their key do; a longer word
   than a column holds (an oral measure's) wraps in its cell. */
.fl-grid th.pupil-head { min-width: 11.5rem; }
.fl-grid th.strand, .fl-grid .cell { width: 6.2rem; min-width: 5.6rem; }
.fl-grid th.strand .strand-head { max-width: 6.2rem; }
.fl-grid-key .fl-key-line + .strand-key-eyebrow { margin-top: 1rem !important; }
.fl-grid tbody tr.fl-band-WTE th { background: rgba(176, 71, 62, 0.04); }
.fl-grid th.fl-grid-head { cursor: help; }
.fl-grid-scale {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 2px;
  border-radius: 4px; overflow: hidden;
}
.fl-grid-scale li {
  display: flex; align-items: center; justify-content: center;
  min-height: 1.7rem; padding: 0.2rem 0.15rem; font-size: 0.68rem; font-weight: 600;
  text-align: center; line-height: 1.15;
}
.fl-grid-scale .fl-cell-WAE { background: rgba(0, 128, 106, 0.18); color: var(--wae); }
.fl-grid-scale .fl-cell-WWE { background: rgba(171, 152, 53, 0.22); color: #6a5a12; }
.fl-grid-scale .fl-cell-WTE { background: rgba(176, 71, 62, 0.16); color: var(--wte); }
.fl-grid-key .fl-key-line { margin: 0.45rem 0 0 !important; }
.fl-grid-key .fl-key-unknown { font-style: italic; }
@media print {
  .fl-roster-wrap { max-height: none; }
}
.class-card .fl-card-line .fl-tested { font-variant-numeric: tabular-nums; }
.fl-teacher { padding: 1rem 1.15rem 0.75rem; }
.fl-teacher .fl-lead { margin: 0 0 0.25rem; }
.fl-teacher .fl-tested { font-variant-numeric: tabular-nums; }
.fl-teacher .fl-rooms { list-style: none; margin: 0.25rem 0 0.5rem; padding: 0; display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; font-size: 0.85rem; }
.fl-teacher .fl-rooms li { font-variant-numeric: tabular-nums; }
.fl-teacher .table-wrap { margin: 0.5rem 0; }

/* Reading Fluency — Growth and Coverage */
/* The Growth page's own section, the Coverage tab's, and the glossary's
   reference: the same ink-only figure tiles and tables as the partials above.
   Nothing here borrows the MAP stat tile beside them — .stat .value is brand
   green, and a Fluency figure is deliberately not drawn the way a banded MAP
   figure is. */
.fl-section { margin-top: 1.5rem; }
.fl-section .fl-lead { margin: 0 0 0.5rem; }
.fl-section .fl-figures,
.fl-figures + .table-wrap { margin-top: 0.25rem; }
.fl-glossary th.fl-word { white-space: nowrap; }
.fl-glossary + .fl-glossary { margin-top: 0.75rem; }

/* ------------------------------------------------------------ marks grid */

.marks-grid th .yr { display: block; font-weight: 400; font-size: 0.62rem; color: var(--muted); }
.marks-grid tbody th { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink); white-space: nowrap; }

/* Same cells Inclusion uses: coloured by the mark band, never by MAP.
   Incomplete / unbanded sit in band-none and carry no chip. */
.inc-mark-grid td.mark-cell {
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  white-space: nowrap;
}
.inc-mark-grid td.mark-cell .chip,
.inc-mark-grid td.mark-cell .pill,
.inc-mark-grid td.mark-cell .orbit-pill { margin-inline-start: 0.2rem; }
.inc-mark-grid td.mark-cell.band-WAE { background: rgba(0, 128, 106, 0.14); color: var(--mark-wae); }
.inc-mark-grid td.mark-cell.band-WWE { background: rgba(166, 140, 34, 0.16); color: var(--mark-wwe); }
.inc-mark-grid td.mark-cell.band-WTE { background: rgba(176, 71, 62, 0.14); color: var(--mark-wte); }
.inc-mark-grid td.mark-cell.band-none { color: var(--muted); font-weight: 400; }
/* Same reason as the matrix: the word is longer than the marks around it. */
.inc-mark-grid td.mark-cell.is-incomplete .tip-anchor { font-size: 0.68rem; font-weight: 500; }

/* Flag pills on class pages — same tokens Inclusion uses on the pupil arc. */
.orbit-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 600;
  background: var(--wash);
  color: var(--muted);
}
.orbit-pill.wait {
  background: rgba(176, 71, 62, 0.1);
  color: var(--wte);
}

/* The class page's task × pupil grid (`analytics/_task_grid.html`). A heading
   is a piece of work's own name, so it is not set in capitals; its due date,
   category and weight sit under it once, never repeated in the cells. */
.task-grid thead th.task-head {
  text-transform: none; letter-spacing: 0; color: var(--ink);
  vertical-align: bottom; min-width: 7rem; max-width: 13rem; white-space: normal;
}
.task-grid .task-meta { display: block; font-weight: 400; font-size: 0.68rem; color: var(--muted); margin-top: 0.15rem; }
.task-grid .task-head .orbit-pill.wait { margin-top: 0.25rem; }
.task-grid td.task-cell { text-align: center; font-variant-numeric: tabular-nums; white-space: nowrap; }
.task-grid td.task-cell.is-unmarked { color: var(--muted); }
/* Entered in the other form (levels on points work, points on criteria work):
   shown as it arrived, but it is not that work's mark, so it must not read as one. */
.task-grid td.task-cell.is-other-form { color: var(--muted); font-style: italic; }
.task-grid td.task-cell.is-not-applicable { color: var(--muted); }
.task-swatch {
  display: inline-block; width: 0.55rem; height: 0.55rem; border-radius: 2px;
  margin-inline-end: 0.3rem;
}
.task-categories .orbit-pill { margin-inline-end: 0.25rem; }
.term-grade-notice strong { color: var(--ink); font-weight: 600; }

/* ------------------------------- what the classroom says (shared panel) */
/* `analytics/_marks_classroom.html` is rendered inside the Spaces shell on
   the Inclusion dossier and inside this stylesheet's chrome on the pupil and
   class pages. spaces-orbit.css loads second and restyles every selector
   below, so the desk keeps its own look; these are the rules the other two
   pages get, written in this file's own tokens. One template, two skins —
   which is cheaper than two templates that quietly disagree about a mark. */

.orbit-note { margin: 0.35rem 0; font-size: 0.82rem; color: var(--muted); }
.orbit-scroll { overflow-x: auto; border: 1px solid var(--line); border-radius: 10px; }

/* A teacher's own words, in a row of their own under the piece of work they
   are about. Set in the body face rather than as a note: this is the teacher's
   sentence about one child, not the platform's gloss on it. */
.orbit-subrow td {
  padding-top: 0; border-top: 0;
  font-size: 0.88rem; line-height: 1.5; color: var(--ink);
}
.orbit-subrow .orbit-note { display: block; margin: 0 0 0.15rem; }

/* ManageBac comments arrive as Word HTML. The table cell paints a sanitized
   subset; hover cards keep plain text because a nested table is invalid there. */
.mark-comment-rich {
  max-width: 36rem;
  text-align: start;
  font-size: 0.88rem;
  line-height: 1.45;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
.mark-comment-rich p { margin: 0 0 0.5em; }
.mark-comment-rich p:last-child { margin-bottom: 0; }
.mark-comment-rich table {
  width: auto;
  max-width: 100%;
  border-collapse: collapse;
  margin: 0.25em 0 0.75em;
  font-size: inherit;
}
.mark-comment-rich th,
.mark-comment-rich td,
.orbit-table .mark-comment-rich th,
.orbit-table .mark-comment-rich td {
  padding: 0.2em 0.5em;
  border: 1px solid var(--line);
  vertical-align: top;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  color: inherit;
  font-size: inherit;
  background: transparent;
}
.mark-comment { white-space: pre-wrap; }

.inc-headline {
  margin: 0 0 0.9rem;
  font-size: 1.05rem; line-height: 1.5; color: var(--ink);
}
.inc-headline strong { color: var(--brand-deep); }

.inc-mark-tiles {
  display: grid; gap: 1rem; margin: 1.1rem 0;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.inc-mark-tile {
  display: flex; flex-direction: column; gap: 0.3rem;
  padding: 0.85rem 0.95rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-top: 3px solid var(--line);
  border-radius: 10px;
}
.inc-mark-tile.band-WAE { border-top-color: var(--mark-wae); }
.inc-mark-tile.band-WWE { border-top-color: var(--mark-wwe); }
.inc-mark-tile.band-WTE { border-top-color: var(--mark-wte); }
.inc-mark-tile > header {
  display: flex; gap: 0.5rem; align-items: baseline; justify-content: space-between;
}
.inc-mark-tile .subject { font-weight: 650; }
.inc-mark-value {
  margin: 0; font-size: 2rem; line-height: 1; font-weight: 650;
  font-variant-numeric: tabular-nums;
}
.inc-mark-tile.band-WAE .inc-mark-value { color: var(--mark-wae); }
.inc-mark-tile.band-WWE .inc-mark-value { color: var(--mark-wwe); }
.inc-mark-tile.band-WTE .inc-mark-value { color: var(--mark-wte); }

/* Every term marked, oldest first, one row per year. Squares and not a
   line: a mark is an ordinal judgement on scales that do not share a
   number line. The year sits to the left so a 2020-21 D cannot inherit
   this year's subtitle by sitting next to it. */
.inc-ribbon {
  display: flex; flex-direction: column; gap: 0.28rem;
  list-style: none; margin: 0.4rem 0 0.15rem; padding: 0;
}
.inc-ribbon-year {
  display: grid; grid-template-columns: 2.85rem minmax(0, 1fr);
  column-gap: 0.4rem; align-items: start;
}
.inc-ribbon-year-label {
  margin: 0; padding: 0.18rem 0.4rem 0 0;
  border-inline-end: 2px solid var(--line);
  color: var(--muted); font-size: 0.62rem; font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
  line-height: 1.2; text-align: end; white-space: nowrap;
}
.inc-ribbon-marks {
  display: flex; flex-wrap: wrap; gap: 0.15rem;
  list-style: none; margin: 0; padding: 0;
}
.inc-ribbon-marks li {
  min-width: 1.9rem; padding: 0.1rem 0.3rem; border-radius: 5px;
  background: var(--wash); color: var(--muted);
  font-size: 0.7rem; font-variant-numeric: tabular-nums; text-align: center;
}
.inc-ribbon-marks li.band-WAE { background: rgba(0, 128, 106, 0.16); color: var(--mark-wae); }
.inc-ribbon-marks li.band-WWE { background: rgba(166, 140, 34, 0.18); color: var(--mark-wwe); }
.inc-ribbon-marks li.band-WTE { background: rgba(176, 71, 62, 0.14); color: var(--mark-wte); }

.inc-trend { margin: 0; font-size: 0.76rem; color: var(--muted); }
.inc-trend.trend-up { color: var(--mark-wae); }
.inc-trend.trend-down { color: var(--wte); }
.inc-beside { margin: 0.35rem 0 0; font-size: 0.82rem; }
.inc-beside .agree-mark-above-map { color: var(--wte); font-weight: 600; }
.inc-beside .agree-mark-below-map { color: #7a5a10; font-weight: 600; }
.inc-beside .agree-aligned { color: var(--mark-wae); font-weight: 600; }
.inc-mark-flags { margin: 0.35rem 0 0; display: flex; gap: 0.25rem; flex-wrap: wrap; }

/* The two disagreements, named in each direction. They are the useful part
   of putting a mark next to a RIT, so they get a card each rather than a
   column somebody has to sort. */
.inc-conflicts {
  display: grid; gap: 1rem; margin: 1.25rem 0;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
}
.inc-conflict {
  padding: 0.9rem 1rem;
  border: 1px solid var(--line);
  border-inline-start: 3px solid var(--line);
  border-radius: 10px;
  background: var(--wash);
}
.inc-conflict.tone-alarm { border-inline-start-color: var(--wte); }
.inc-conflict.tone-warn { border-inline-start-color: var(--gold); }
.inc-conflict h3 { margin: 0 0 0.35rem; font-size: 0.95rem; }
.inc-chips { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.inc-conflict .inc-chips {
  display: block; list-style: none; margin: 0.5rem 0 0; padding: 0;
}
.inc-conflict .inc-chips li { padding: 0.3rem 0; border-top: 1px solid var(--line); }

.inc-mark-all { margin: 1.25rem 0 0; }
.inc-mark-all summary { cursor: pointer; }
.inc-mark-all summary h3 { display: inline; font-size: 0.95rem; }

/* ------------------------------------------------------------- compare */

.compare-row { margin-bottom: 1.75rem; }
.compare { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.compare .is-this { background: var(--wash); font-weight: 600; }
.compare .is-this th { color: var(--brand-deep); }

.agreement.is-flagged { border-inline-start: 4px solid var(--warn-line); }
.agreement .strip { margin-bottom: 0.5rem; }
.agreement .stat { border-inline-start-color: var(--line); }

/* ===================================================================== */
/* Spaces and databases — ADT and teachers, in the same room.            */
/*                                                                       */
/* The one thing this styling has to carry is the difference between a   */
/* column somebody typed and a column the rules engine supplied. It is   */
/* the whole reason the feature exists, so it is stated in the header,   */
/* in the cell, and in the colour of both.                               */
/* ===================================================================== */

.eyebrow-kind {
  margin: 0 0 0.2rem; font-size: 0.66rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--gold);
}
.space-card { border-inline-start: 4px solid var(--gold); }
.space-card.space-class { border-inline-start-color: var(--brand-light); }
.space-card.space-department { border-inline-start-color: var(--gold); }
.space-card.space-intervention { border-inline-start-color: var(--wte); }
.card-facts dd.small { font-size: 0.82rem; font-weight: 500; }

.two-kinds { margin: 0.75rem 0; padding-inline-start: 1.1rem; }
.two-kinds li { margin-bottom: 0.5rem; }

.space-anchor .anchor-facts { display: flex; gap: 1.75rem; flex-wrap: wrap; margin: 0; }
.anchor-facts dt { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.anchor-facts dd { margin: 0.2rem 0 0; font-weight: 600; color: var(--brand-deep); }

.db-card h3 { margin: 0 0 0.25rem; font-size: 1rem; color: var(--brand); }
.db-card h3 a { text-decoration: none; }
.db-meta { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0.5rem 0 0; }
.pill-derived { background: rgba(0, 128, 106, 0.10); border-color: var(--mark-wae); color: var(--wae); }
.pill-entered { background: var(--wash); }

.derived-note {
  background: rgba(0, 128, 106, 0.06); border-inline-start: 3px solid var(--mark-wae);
  padding: 0.6rem 0.8rem; border-radius: 0 6px 6px 0; margin-bottom: 1.25rem;
}

/* The table. A derived column is tinted and has no input in it; an entered
   column is on white and does. You can see which is which without reading. */
.db-table th.col-derived, .db-table td.col-derived { background: rgba(0, 128, 106, 0.05); }
.db-table th.col-derived { border-bottom: 2px solid var(--mark-wae); }
.db-table th.col-entered { border-bottom: 2px solid var(--line); }
.db-table .col-origin {
  display: block; font-size: 0.6rem; font-weight: 400; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.db-table th.col-derived .col-origin { color: var(--wae); }
.db-table tbody th { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink); white-space: nowrap; }
.db-table td.col-derived { font-variant-numeric: tabular-nums; }
.db-table .cell-note { display: block; font-size: 0.66rem; color: var(--muted); }
.db-table input[type="text"], .db-table input[type="number"],
.db-table input[type="date"], .db-table select, .db-table textarea {
  font: inherit; font-size: 0.84rem; width: 100%; min-width: 6.5rem;
  padding: 0.25rem 0.4rem; border: 1px solid var(--line); border-radius: 4px;
  background: var(--paper); color: var(--ink);
}
.db-table textarea { min-width: 12rem; resize: vertical; }
.db-table input:focus, .db-table select:focus, .db-table textarea:focus {
  outline: 2px solid var(--brand-light); outline-offset: 0; border-color: var(--brand-light);
}
.row-save button {
  font: inherit; font-size: 0.8rem; padding: 0.25rem 0.7rem; cursor: pointer;
  border: 1px solid var(--brand); background: var(--brand); color: #fff; border-radius: 4px;
}
.row-save button:hover { background: var(--brand-deep); }
.stat-none { border-inline-start-color: var(--line); }

/* Opening a space — ADT only, folded away until it is wanted. */
.opener {
  background: var(--paper); border: 1px solid var(--line); border-radius: 12px;
  padding: 0.9rem 1.25rem; margin-bottom: 1.5rem;
  border-inline-start: 4px solid var(--gold);
}
.opener > summary {
  cursor: pointer; font-weight: 650; color: var(--brand-deep); font-size: 0.98rem;
  list-style-position: outside;
}
.opener > summary::marker { color: var(--gold); }
.opener[open] > summary { margin-bottom: 0.6rem; }
.opener-h {
  margin: 1.5rem 0 0.3rem; font-size: 0.88rem; color: var(--brand);
  text-transform: uppercase; letter-spacing: 0.05em;
}
.opener .controls { margin-bottom: 0.75rem; }
.opener table select, .opener table button { font: inherit; font-size: 0.84rem; }
.opener table select {
  padding: 0.2rem 0.35rem; border: 1px solid var(--line); border-radius: 4px;
  background: var(--paper);
}
.opener table button {
  padding: 0.25rem 0.7rem; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--brand); background: var(--brand); color: #fff; border-radius: 4px;
}
.opener table button:hover { background: var(--brand-deep); }

label.tick {
  flex-direction: row; align-items: center; gap: 0.35rem;
  font-size: 0.78rem; color: var(--muted); white-space: nowrap;
}
label.tick input { width: auto; }

/* ===================================================================== */
/* Hover cards — the answer to "which pupil is that dot" and "what does  */
/* Low in Informational Text mean".                                      */
/*                                                                       */
/* Pure CSS, deliberately. The card is a child of the thing you point at, */
/* it appears on :hover and on :focus-within, and nothing measures        */
/* anything: charts.py already decided which side each card opens on and  */
/* wrote it into the markup as a class. Print never shows one, so the PDF */
/* still renders from the SVG alone.                                     */
/* ===================================================================== */

.plot-wrap { position: relative; }
/* With a hotspot layer over it the SVG is a picture and nothing more —
   otherwise its own <a> and <title> compete with the cards for the pointer. */
.chart-has-hotspots .plot { pointer-events: none; }

.hotspots { position: absolute; inset: 0; pointer-events: none; }
.hotspot {
  position: absolute; pointer-events: auto;
  width: 26px; height: 26px; transform: translate(-50%, -50%);
  border-radius: 50%; display: block;
}
.hotspot:focus { outline: 2px solid var(--brand-deep); outline-offset: 2px; }
.hotspot:hover::after {
  content: ""; position: absolute; inset: 4px;
  border: 2px solid var(--brand-deep); border-radius: 50%;
}
/* A sized target is placed by its own corner, so it must not be nudged by a
   transform — see charts.hotspot() for why that matters to the card inside. */
.hotspot.is-box { transform: none; border-radius: 4px; }
.chart-strand-profile .hotspot:hover { background: rgba(0, 78, 66, 0.06); }
.chart-strand-profile .hotspot:hover::after { content: none; }

.tipcard {
  display: none; position: absolute; z-index: 60;
  width: 22rem; max-width: min(92vw, 30rem);
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--line); border-inline-start: 4px solid var(--gold);
  border-radius: 10px; padding: 0.6rem 0.75rem;
  box-shadow: 0 10px 26px rgba(0, 30, 24, 0.18);
  font-size: 0.78rem; line-height: 1.4; text-align: start;
  white-space: normal; font-weight: 400; letter-spacing: 0;
  text-transform: none; cursor: default;
  /* Cards are read-only display — never intercept clicks on filters / rows
     beneath a pinned or overflowing card. */
  pointer-events: none;
}
.hotspot:hover > .tipcard,
.hotspot:focus > .tipcard,
.hotspot:focus-within > .tipcard,
.heatmap .cell:hover > .tipcard,
.heatmap th.strand:hover > .tipcard { display: block; }
/* The Reading Fluency measure grid opens its cards on keyboard focus too;
   kept after the base rule, like every other reveal. */
.fl-grid th.fl-grid-head:focus > .tipcard,
.fl-grid th.fl-grid-head:focus-within > .tipcard,
.fl-grid .cell:focus-within > .tipcard { display: block; }

/* Inclusion mark cells and the class Past-marks grid share .has-tip. Pin the
   card to the corner so a horizontally scrolling table cannot clip it. */
.has-tip { display: inline-block; }
.has-tip .tip-anchor { cursor: help; }
.has-tip a.tip-anchor { cursor: pointer; }
.has-tip:hover > .tipcard,
.has-tip:focus-within > .tipcard {
  display: block;
  position: fixed;
  inset-inline-end: 1.25rem;
  bottom: 1.25rem;
  inset-inline-start: auto;
  top: auto;
  width: 24rem;
  max-width: min(92vw, 30rem);
  /* A card taller than the screen grows off the *top*, which is where its own
     title is — so a long one would lose the name of the thing it describes and
     keep the disclaimer. Capped, it clips the tail instead: the card is
     ordered head-first for exactly this reason. It cannot scroll, because
     `pointer-events: none` above is what stops a pinned card blocking the
     filters underneath it. */
  max-height: calc(100vh - 2.5rem);
  overflow: hidden;
  z-index: 80;
}

/* On a chart the card hangs off the mark; charts.py chose the side. */
.hotspots .tipcard { inset-inline-start: 18px; top: 12px; }
.hotspots .tipcard.is-left { inset-inline-start: auto; inset-inline-end: 18px; }
.hotspots .tipcard.is-up { top: auto; bottom: 12px; }
/* A strand card is pinned to the corner of the screen, and that is deliberate
   rather than lazy. Two reasons, one per place it is used:
   * the grid lives in a horizontally scrolling box, and a box that scrolls also
     clips — a card hung off a cell would be cut in half by the very container
     that makes an eight-strand grid usable;
   * a strand card carries prose, so it is taller than the chart it belongs to
     and would hang off the top of a short one.
   Pinned, it is always whole and always in the same place, and it names the
   pupil and the strand it describes, so it never needs an arrow to be read. */
.heatmap .tipcard,
.chart-strand-profile .hotspots .tipcard,
.chart-strand-profile .hotspots .tipcard.is-left,
.chart-strand-profile .hotspots .tipcard.is-up {
  position: fixed; inset-inline-end: 1.25rem; bottom: 1.25rem;
  inset-inline-start: auto; top: auto;
  width: 24rem;
}

.tipcard .tip-head { display: flex; gap: 0.4rem; align-items: baseline; flex-wrap: wrap; }
.tipcard .tip-head b { color: var(--brand-deep); font-size: 0.86rem; }
.tipcard .tip-sub {
  display: block; color: var(--muted); font-size: 0.7rem; margin-top: 0.1rem;
}
.tipcard .tip-mini-chart {
  display: block; margin-top: 0.55rem; padding: 0.45rem 0.5rem;
  border: 1px solid var(--line); border-radius: 7px; background: var(--wash);
}
.tipcard .tip-mini-row {
  display: grid; grid-template-columns: 4.8rem minmax(5rem, 1fr) 3.2rem;
  align-items: center; gap: 0.45rem; min-height: 1.15rem;
}
.tipcard .tip-mini-row + .tip-mini-row { margin-top: 0.18rem; }
.tipcard .tip-mini-label,
.tipcard .tip-mini-value { font-size: 0.68rem; font-variant-numeric: tabular-nums; }
.tipcard .tip-mini-label {
  color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tipcard .tip-mini-value { text-align: end; color: var(--brand-deep); font-weight: 650; }
.tipcard .tip-mini-track {
  display: block; height: 0.48rem; overflow: hidden;
  border-radius: 999px; background: rgba(0, 78, 66, 0.09);
}
.tipcard .tip-mini-fill {
  display: block; height: 100%; min-width: 0; border-radius: inherit;
  background: linear-gradient(90deg, var(--brand-light), var(--brand));
}
.tipcard .tip-mini-axis {
  display: block; margin-top: 0.3rem; color: var(--muted);
  font-size: 0.62rem; text-align: end;
}
.tipcard .tip-rows { display: block; margin-top: 0.45rem; }
.tipcard .tip-row {
  display: grid; grid-template-columns: 7.2rem 1fr; gap: 0.5rem;
  padding: 0.16rem 0; border-top: 1px solid var(--wash);
}
.tipcard .tip-row .k { color: var(--muted); font-size: 0.72rem; }
.tipcard .tip-row .v { font-weight: 560; white-space: pre-wrap; }
.tipcard .tip-row.tone-bad .v { color: var(--wte); }
.tipcard .tip-row.tone-good .v { color: var(--wae); }
.tipcard .tip-row.tone-warn .v { color: #7a5a10; }
.tipcard .tip-row.tone-muted .v { font-weight: 400; color: var(--muted); }
.tipcard .tip-note {
  display: block; margin-top: 0.45rem; padding: 0.3rem 0.45rem;
  background: var(--warn-bg); border-radius: 4px; font-size: 0.72rem;
}
.tipcard .tip-caution {
  display: block; margin-top: 0.45rem; padding-inline-start: 0.5rem;
  border-inline-start: 2px solid var(--gold); color: var(--muted); font-size: 0.71rem;
}
.tipcard .tip-source {
  display: block; margin-top: 0.35rem; color: var(--muted); font-size: 0.66rem;
}
.tipcard.tone-WAE { border-inline-start-color: var(--mark-wae); }
.tipcard.tone-WWE { border-inline-start-color: var(--mark-wwe); }
.tipcard.tone-WTE { border-inline-start-color: var(--mark-wte); }

@media print { .tipcard, .hotspots { display: none !important; } }

/* ---------------------------------------------- the new chart marks */

.plot .zone { stroke: var(--paper); stroke-width: 1; }
.plot .zone-WAE { fill: rgba(0, 128, 106, 0.13); }
.plot .zone-WWE { fill: rgba(171, 152, 53, 0.15); }
.plot .zone-WTE { fill: rgba(176, 71, 62, 0.12); }

.plot .bar-observed { fill: var(--mark-wae); fill-opacity: 0.8; }
.plot .bar-projected {
  fill: var(--paper); fill-opacity: 1;
  stroke: var(--mark-fit); stroke-width: 1.5; stroke-dasharray: 4 3;
}
.plot .bar-s1 { fill: var(--shade-1); fill-opacity: 0.9; }
.plot .bar-s2 { fill: var(--shade-2); fill-opacity: 1; }
.plot .bar-s3 { fill: var(--shade-3); fill-opacity: 1; }
.plot .bar-s4 { fill: var(--shade-4); fill-opacity: 1; }
.plot .bar-s5 { fill: var(--shade-5); fill-opacity: 0.9; }
.plot .bar-none { fill: var(--line); fill-opacity: 1; }
.plot .bar-abt-0 { fill: var(--mark-wae); fill-opacity: 0.85; }
.plot .bar-abt-1 { fill: var(--mark-wwe); fill-opacity: 0.85; }
.plot .bar-abt-2 { fill: var(--brand-light); fill-opacity: 0.85; }
.plot .bar-abt-3 { fill: var(--mark-norm); fill-opacity: 0.7; }
.plot .bar-sen { fill: var(--mark-wae); fill-opacity: 0.85; }
.plot .bar-school { fill: var(--mark-norm); fill-opacity: 0.55; }
.plot .bar-gt { fill: var(--gold); fill-opacity: 0.8; }
.plot .bar-reliable { fill: var(--brand); fill-opacity: 0.8; }
.plot .bar-thin { fill: var(--mark-norm); fill-opacity: 0.35; }
.plot .mark-label-value {
  font-size: 11px; fill: var(--muted); font-variant-numeric: tabular-nums;
  paint-order: stroke; stroke: var(--paper); stroke-width: 3px; stroke-linejoin: round;
}
.plot .mark-label-gap { font-size: 9px; fill: var(--muted); }
/* The count on a horizontal mix bar. Inside the bar it sits on the fill, so it
   is drawn in the paper colour; outside it is ink on the panel. Both are
   tabular so a column of them lines up. */
.plot .mark-label-count-in {
  font-size: 11px; font-weight: 650; fill: var(--paper);
  font-variant-numeric: tabular-nums;
}
.plot .mark-label-count-out {
  font-size: 11px; font-weight: 650; fill: var(--ink-2, #2a322f);
  font-variant-numeric: tabular-nums;
}
.legend .legend-reliable i { background: var(--brand); height: 10px; border-radius: 3px; }
.legend .legend-thin i { background: var(--mark-norm); opacity: 0.4; height: 10px; border-radius: 3px; }
.plot .bar-high { fill: var(--mark-wte); fill-opacity: 0.85; }
.plot .bar-medium { fill: var(--mark-wwe); fill-opacity: 0.85; }
.plot .bar-low { fill: var(--mark-wae); fill-opacity: 0.75; }
.plot .bar-above { fill: var(--mark-wwe); fill-opacity: 0.85; }
.plot .bar-below { fill: var(--mark-wte); fill-opacity: 0.75; }
.plot .bar-growth { fill: var(--brand); fill-opacity: 0.8; }
.plot .bar-hidden { fill: var(--mark-wte); fill-opacity: 0.8; }
.plot .bar-top { fill: var(--mark-wae); fill-opacity: 0.85; }
.plot .bar-WAE { fill: var(--mark-wae); fill-opacity: 0.9; }
.plot .bar-WWE { fill: var(--mark-wwe); fill-opacity: 0.9; }
.plot .bar-WTE { fill: var(--mark-wte); fill-opacity: 0.9; }
/* A measure that is no band — an appraisal card's absence share by grade —
   wears no band colour: the house green chart marks are drawn in. */
.plot .bar-measure { fill: var(--brand); fill-opacity: 0.8; }
/* Value labels (charts.py, value_labels=True): a mark's own figure, at the
   mark. Ink beside a mark; paper on a dark fill, ink on a light one; tabular
   so a column of them lines up. One class deeper than `.plot .mark-label`,
   which is declared further down. The screen's phone size is
   appraisal-kpi.css's, and charts.DATUM_FONT is the room measured for it. */
.chart .plot :is(.mark-label-datum, .mark-label-datum-in, .mark-label-datum-in-light) {
  font-size: 11px; font-weight: 650; fill: var(--ink); font-variant-numeric: tabular-nums;
}
.chart .plot .mark-label-datum-in { fill: var(--paper); }
/* An appraisal figure chart on a phone (figures.FIGURE_CHART_WIDTH = 400
   units, drawn about 290 px wide). SVG text is sized in viewBox user units,
   not CSS px: 14 units reads ~10 px there, and charts.DATUM_FONT = 14 is the
   room every value label is measured for; a tick or a row's name takes 13
   units (~9.5 px), which the figure frames' 150-unit name columns hold. A rem
   token would be the wrong unit, which is why these sit here with the chart
   rules and not in the appraisal sheets' type tokens. */
@media (max-width: 600px) {
  .appr-figure .chart .plot :is(.mark-label-datum, .mark-label-datum-in, .mark-label-datum-in-light) { font-size: 14px; }
  .appr-figure .chart .plot :is(.tick, .mark-label-name) { font-size: 13px; }
  /* charts.mix_rows (the lesson-observation cards): a row's name as a value
     label, its sub-line and its sentence as a tick; charts.datum_width
     measured their room at these sizes. */
  .appr-figure .chart .plot .mark-label-mix-name { font-size: 14px; }
  .appr-figure .chart .plot :is(.mark-label-mix-sub, .mark-label-mix-note) { font-size: 13px; }
}
/* charts.value_stacks — one recorded mark value per rung, low to high.
   A sequential ramp of the house green mixed with paper, 20% to 100%, written
   out as hex rather than color-mix() because these same segments render
   through WeasyPrint into the printed reports. Eight rungs, and a scale of any
   length is stretched across all eight (charts.value_role), so the top of a
   four-step PYP scale and the top of a thirteen-step AHSD scale are both the
   full house green.

   --wte red is deliberately absent. A low mark is not a Working Below band:
   these are the marks as a teacher recorded them, and colouring the bottom of
   a scale as a shortfall is a judgement this chart does not make. */
.plot .bar-v1 { fill: #E2ECE9; fill-opacity: 1; }
.plot .bar-v2 { fill: #C6DAD5; fill-opacity: 1; }
.plot .bar-v3 { fill: #A8C8C0; fill-opacity: 1; }
.plot .bar-v4 { fill: #88B4A9; fill-opacity: 1; }
.plot .bar-v5 { fill: #67A093; fill-opacity: 1; }
.plot .bar-v6 { fill: #458B7C; fill-opacity: 1; }
.plot .bar-v7 { fill: #227060; fill-opacity: 1; }
.plot .bar-v8 { fill: #004E42; fill-opacity: 1; }
/* A value that is on no known scale takes no rung of the ramp: it is drawn in
   the neutral grey the rest of the platform uses for "not measured against
   anything", never in the tint that would say it is the top. */
/* Adjacent rungs at the dark end of the ramp are close in tint, so every
   segment carries a paper hairline: two dark greens still read as two. */
.chart-value-stacks .plot .mark-bar { stroke: var(--paper); stroke-width: 1; }
.plot .bar-unordered { fill: var(--mark-norm); fill-opacity: 0.45; }
.legend .legend-v1 i { background: #E2ECE9; height: 10px; border-radius: 3px; }
.legend .legend-v2 i { background: #C6DAD5; height: 10px; border-radius: 3px; }
.legend .legend-v3 i { background: #A8C8C0; height: 10px; border-radius: 3px; }
.legend .legend-v4 i { background: #88B4A9; height: 10px; border-radius: 3px; }
.legend .legend-v5 i { background: #67A093; height: 10px; border-radius: 3px; }
.legend .legend-v6 i { background: #458B7C; height: 10px; border-radius: 3px; }
.legend .legend-v7 i { background: #227060; height: 10px; border-radius: 3px; }
.legend .legend-v8 i { background: #004E42; height: 10px; border-radius: 3px; }
.legend .legend-unordered i { background: var(--mark-norm); opacity: 0.45; height: 10px; border-radius: 3px; }
/* The second line in a horizontal row's left gutter — the teachers behind a
   class, the class count behind a teacher. Quiet, because it qualifies the
   name above it rather than competing with it. */
.plot .mark-label-sub { font-size: 9px; fill: var(--muted); }
.plot .cut-zero { stroke: var(--ink); stroke-opacity: 0.35; stroke-dasharray: 3 3; }

/* --------------------------------------------------------- analysis tabs */
/* Thirteen of them, so this is a wrapping row of quiet links rather than the
   two-line filing tabs the reports screen uses: at this count a tab strip that
   tall becomes the page. The live one is filled and underlined in gold so it
   reads at a glance in a block of thirteen. */
.analysis-tabs {
  display: flex; flex-wrap: wrap; gap: 0.35rem 1.1rem;
  margin: -0.25rem 0 1.25rem; padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
}
/* Four named groups rather than thirteen equal links on two rows. The rows
   wrap the same way; what changes is that each row now says what kind of
   question it answers. A group heading is decoration for a screen reader —
   the tabs are already inside a labelled nav — so it is aria-hidden. */
.analysis-tabs .tab-group { display: flex; flex-direction: column; gap: 0.15rem; }
.analysis-tabs .tab-group + .tab-group { position: relative; }
.analysis-tabs .tab-group + .tab-group::before {
  content: ""; position: absolute; inset-inline-start: -0.55rem; top: 0.2rem; bottom: 0.2rem;
  width: 1px; background: var(--line);
}
.analysis-tabs .tab-group > .eyebrow {
  padding-inline-start: 0.7rem;
  font-size: 0.6rem; letter-spacing: 0.08em; color: var(--muted);
}
.analysis-tabs .tab-group-links { display: flex; flex-wrap: wrap; gap: 0.15rem; }
.analysis-tabs a {
  padding: 0.3rem 0.7rem; border-radius: 7px;
  font-size: 0.85rem; color: var(--muted); text-decoration: none;
  border: 1px solid transparent; white-space: nowrap;
}
.analysis-tabs a:hover { background: var(--wash); color: var(--brand); }
.analysis-tabs a.on {
  background: var(--brand); color: #fff; font-weight: 600;
  box-shadow: inset 0 -3px 0 var(--gold);
}
.analysis-tabs a.on:hover { background: var(--brand-deep); color: #fff; }
.analysis-tabs a:focus-visible {
  outline: 2px solid var(--gold); outline-offset: 2px;
}
.band-key {
  margin: 0 0 1rem; font-size: 0.82rem; color: var(--muted);
}

/* Workbook provenance sits above the strip, folded. It is a footnote about
   a retired spreadsheet, not a header the first figure has to wait behind. */
.analysis-footnote {
  margin: 0 0 0.75rem; font-size: 0.88rem;
  border: 1px solid var(--line); border-radius: 10px; background: var(--paper);
}
.analysis-footnote > summary {
  cursor: pointer; padding: 0.45rem 0.85rem; color: var(--muted);
  font-weight: 600; list-style-position: inside;
}
.analysis-footnote > summary:hover { color: var(--brand); }
.analysis-footnote .lede {
  margin: 0 0.85rem 0.7rem; max-width: none;
}
.analysis-footnote .tab-provenance { margin: 0 0.85rem 0.7rem; }
.analysis-footnote .sources-head {
  margin: 0 0.85rem 0.35rem; font-size: 0.76rem;
  text-transform: uppercase; letter-spacing: 0.04em; color: var(--brand);
  font-weight: 650;
}
.coverage-partial > p { margin: 0; }
.coverage-partial > details { margin: 0.45rem 0 0; }

/* Filters and the tab strip stick once the year line has scrolled away, so a
   reader halfway down Groups & equity can change the window without losing
   the strip — and without a Show click sending them back to the top. */
.analysis-chrome {
  position: sticky; top: 0; z-index: 8;
  background: var(--bg);
  padding: 0.55rem 0 0.35rem;
  margin: 0 0 1.1rem;
  border-bottom: 1px solid var(--line);
}
.analysis-chrome .controls { margin-bottom: 0.65rem; }
.analysis-chrome .analysis-tabs {
  margin: 0; padding-bottom: 0.35rem; border-bottom: 0;
}
#analysis-body.is-refreshing {
  opacity: 0.55; pointer-events: none; cursor: progress;
  transition: opacity 0.12s ease;
}
/* The question sits close to the figure; section heads keep the page h2
   rhythm. Scroll-margin clears the sticky strip, and the rail when it is on. */
#analysis-body > h2#question { margin-bottom: 0.15rem; }
#analysis-body h2[id] { scroll-margin-top: 8rem; }
/* On analysis the strip is already sticky, so the rail rides inside it
   rather than competing for top: 0. */
.is-analysis .section-rail {
  position: static; top: auto; z-index: auto;
  margin: 0.45rem -0.25rem 0; padding: 0.35rem 0.25rem 0;
  background: transparent;
}
/* The tab label and the sheet it replaces sit in the page footnote, in
   small type, rather than as a heading under the question. */
.tab-provenance { margin-top: 0; }
.tab-provenance .eyebrow {
  display: inline-block; margin-inline-end: 0.45rem;
  padding: 0.05rem 0.4rem; border-radius: 4px;
  background: var(--wash); color: var(--brand); font-weight: 650;
}
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
@media (prefers-reduced-motion: reduce) {
  #analysis-body.is-refreshing { transition: none; }
}

/* ------------------------------------------------------- data provenance */
/* Folded shut by default: it is the answer to "why did this move", which is a
   question people ask occasionally and loudly, not continuously. The card
   itself is `.analysis-footnote`; these rules type the feeds list inside it. */
.analysis-footnote .sources-body { padding: 0 0.85rem 0.8rem; }
.analysis-footnote dl { margin: 0; display: grid; gap: 0.6rem; }
.analysis-footnote dt {
  font-weight: 650; color: var(--brand); font-size: 0.76rem;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.analysis-footnote dd { margin: 0.15rem 0 0; color: var(--ink); line-height: 1.5; }

/* ------------------------------------------------- judgement and RAG chips */
/* A word or a number that carries a verdict: the inspection judgement, a
   growth median against 55/45, an alignment score against 60/45.

   Tinted background rather than coloured text, because these sit inside table
   cells next to plain figures and a red *number* reads as part of the number.
   Every one of them also states its own value, so the colour is a second
   signal and never the only one. */
.judge {
  display: inline-block; padding: 0.1rem 0.45rem; border-radius: 5px;
  font-size: 0.78rem; font-weight: 600; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.judge-concern, .judge-red { background: rgba(176, 71, 62, 0.13); color: #8C332C; }
.judge-neutral, .judge-amber { background: rgba(171, 152, 53, 0.16); color: #6F6218; }
.judge-strong, .judge-green { background: rgba(0, 128, 106, 0.13); color: var(--brand); }
.judge-none { background: var(--wash); color: var(--muted); font-weight: 400; }

/* The tab strip carries a second line, so its rows need room to breathe and
   the table under it needs a little air. */
.report-tabs + .banner { margin-top: 0; }

/* The band legend. Every stacked-band chart emits these three roles, so
   without them the swatches all fell back to the same grey dash and the
   legend said nothing. */
.legend .legend-WAE i { background: var(--mark-wae); height: 10px; border-radius: 3px; }
.legend .legend-WWE i { background: var(--mark-wwe); height: 10px; border-radius: 3px; }
.legend .legend-WTE i { background: var(--mark-wte); height: 10px; border-radius: 3px; }
.legend .legend-divider i {
  height: 0; width: 16px; border-radius: 0; background: transparent;
  border-top: 2px dashed #9BA3A3;
}

/* Categorical series for grouped_bars — boys against girls, Fall against
   Spring, SEN against the rest. Four steps, not a scale: these compare things
   that have no order, so they must not borrow the strand shades (--shade-1..5),
   which are a diverging red→green ramp and mean "worse" and "better", nor the
   band marks (--mark-wae/wwe/wte), which mean a standing against a norm. The
   tokens and why they are these four: --cmp-1..4 at the top of this file.

   The bar and its legend swatch read the same token at full strength, so the
   key shows the colour the bar is drawn in; the explicit fill-opacity undoes
   .plot .mark-bar's 0.5. Until 2026-10-01 the bars were the band marks and a
   later, unscoped legend rule for the gap lines repainted every swatch rust
   and blue. Every chart using them carries a legend and a text label per bar,
   so no figure is ever carried by colour alone. */
.plot .bar-cmp-1 { fill: var(--cmp-1); fill-opacity: 1; }
.plot .bar-cmp-2 { fill: var(--cmp-2); fill-opacity: 1; }
.plot .bar-cmp-3 { fill: var(--cmp-3); fill-opacity: 1; }
.plot .bar-cmp-4 { fill: var(--cmp-4); fill-opacity: 1; }
.legend .legend-cmp-1 i { background: var(--cmp-1); height: 10px; border-radius: 3px; }
.legend .legend-cmp-2 i { background: var(--cmp-2); height: 10px; border-radius: 3px; }
.legend .legend-cmp-3 i { background: var(--cmp-3); height: 10px; border-radius: 3px; }
.legend .legend-cmp-4 i { background: var(--cmp-4); height: 10px; border-radius: 3px; }

.teacher-charts {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  align-items: start;
}

/* One subject's run of years, on a teacher's own class page. */
.history-subject { margin: 1.25rem 0 0; }
.history-subject + .history-subject {
  padding-top: 1.25rem; border-top: 1px solid var(--line);
}
.history-subject h3 {
  margin: 0 0 0.4rem; font-size: 0.95rem; display: flex;
  flex-wrap: wrap; gap: 0.5rem; align-items: baseline;
}
.history-subject h3 .note { font-weight: 400; }
.teacher-class-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  margin-bottom: 1.5rem;
}
.teacher-class-grid .stand-card h3 {
  margin: 0;
  font-size: 1rem;
  border: 0;
  padding: 0;
}
.legend .legend-sen i { background: var(--mark-wae); height: 10px; border-radius: 3px; }
.legend .legend-school i { background: var(--mark-norm); height: 10px; border-radius: 3px; }
.legend .legend-high i { background: var(--mark-wte); height: 10px; border-radius: 3px; }
.legend .legend-medium i { background: var(--mark-wwe); height: 10px; border-radius: 3px; }
.legend .legend-low i { background: var(--mark-wae); height: 10px; border-radius: 3px; }
.legend .legend-none i { background: var(--line); height: 10px; border-radius: 3px; }

.plot .cut-zero { stroke: #9BA3A3; stroke-dasharray: none; stroke-width: 1; }
.plot .cut-abt-cut { stroke: var(--gold); stroke-dasharray: 5 4; }

.plot .mark-label { font-size: 10px; fill: var(--ink); }
.plot .mark-label-row { fill: var(--ink); font-size: 10.5px; }
.plot .mark-label-value { fill: var(--muted); font-variant-numeric: tabular-nums; }
.plot .mark-label-gap {
  font-size: 9px; fill: var(--muted);
  paint-order: stroke; stroke: var(--paper); stroke-width: 3px; stroke-linejoin: round;
}
/* charts.mix_rows — the appraisal's lesson-observation cards (DR-555): a row's
   name, its sub-line and the sentence a row draws in place of a bar. After
   `.plot .mark-label` so they win at its weight, and rooted at `.plot ` so
   appraisal/pdf.py inlines them into each chart. The fills and legend are
   appraisal.css's; the phone sizes are with the figure charts' above. */
.plot .mark-label-mix-name { font-size: 11px; font-weight: 650; fill: var(--ink); }
.plot .mark-label-mix-sub { font-size: 10px; fill: var(--muted); font-variant-numeric: tabular-nums; }
.plot .mark-label-mix-note { font-size: 10px; font-style: italic; fill: var(--muted); }

.legend .legend-zone-WAE i { background: rgba(0, 128, 106, 0.35); height: 10px; border-radius: 3px; }
.legend .legend-zone-WWE i { background: rgba(171, 152, 53, 0.4); height: 10px; border-radius: 3px; }
.legend .legend-zone-WTE i { background: rgba(176, 71, 62, 0.35); height: 10px; border-radius: 3px; }
.legend .legend-observed i { background: var(--mark-wae); height: 10px; border-radius: 3px; }
.legend .legend-projected i {
  background: repeating-linear-gradient(90deg, var(--mark-fit) 0 4px, transparent 4px 7px);
  height: 10px; border-radius: 3px;
}
.legend .legend-abt-0 i { background: var(--mark-wae); height: 10px; border-radius: 3px; }
.legend .legend-abt-1 i { background: var(--mark-wwe); height: 10px; border-radius: 3px; }
.legend .legend-abt-2 i { background: var(--brand-light); height: 10px; border-radius: 3px; }
.legend .legend-abt-3 i { background: var(--mark-norm); height: 10px; border-radius: 3px; }

/* ===================================================================== */
/* One pupil — the page a tutor, a coordinator and a parent meeting all   */
/* open. Tiles for where they stand now, then one block per subject.     */
/* ===================================================================== */

.standing {
  display: grid; gap: 0.75rem; margin-bottom: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.stand-card {
  background: var(--paper); border: 1px solid var(--line); border-radius: 12px;
  border-inline-start: 4px solid var(--line);
  padding: 0.8rem 1rem 0.7rem;
}
.stand-WAE { border-inline-start-color: var(--mark-wae); }
.stand-WWE { border-inline-start-color: var(--mark-wwe); }
.stand-WTE { border-inline-start-color: var(--mark-wte); }
.stand-risk { border-inline-start-color: var(--gold); }
.stand-risk.is-alert { border-inline-start-color: var(--wte); }
.stand-card header {
  display: flex; gap: 0.5rem; align-items: center; justify-content: space-between;
}
.stand-subject {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted);
}
.stand-band {
  margin: 0.3rem 0 0.55rem; font-weight: 650; color: var(--brand-deep);
  font-size: 1.12rem; line-height: 1.25;
}
.stand-facts { display: flex; gap: 1rem; flex-wrap: wrap; margin: 0; }
.stand-facts dt { font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); }
.stand-facts dd {
  margin: 0.1rem 0 0; font-size: 1.05rem; font-weight: 650;
  color: var(--brand-deep); font-variant-numeric: tabular-nums;
}
.stand-facts dd.small { font-size: 0.8rem; font-weight: 500; }
.stand-foot { margin: 0.6rem 0 0; font-size: 0.7rem; color: var(--muted); }
.stand-flags { margin: 0.35rem 0 0; display: flex; gap: 0.3rem; flex-wrap: wrap; }
.stand-flags:empty { display: none; }

.pupil-page .page-head .lede { margin-bottom: 0.2rem; }
/* Homeroom advisor and graduating year — stored facts under the standing
   line, quieter than it because they are administration rather than a
   judgement. Absent entirely when neither is set, so there is no empty row. */
.pupil-page .lede-enrol {
  margin: 0.1rem 0 0; color: var(--muted); font-size: 0.82rem;
}
.pupil-page .pupil-ar {
  margin: 0 0 0.2rem; color: var(--gold); font-size: 1.05rem;
}
/* The same bilingual-name treatment as .pupil-ar, unscoped: a teacher page
   carries no .pupil-page wrapper, and a teacher's name is as bilingual as a
   pupil's. */
.name-ar { margin: 0 0 0.2rem; color: var(--gold); font-size: 1.05rem; }
.pupil-page h2 .chip { vertical-align: 0.15em; margin-inline-start: 0.4rem; }
.pupil-page .table-wrap table { min-width: 36rem; }
.pupil-page .pupil-marks td.mark-cell { text-align: start; }
@media print {
  .pupil-page .stand-card,
  .pupil-page .chart { break-inside: avoid; }
}

.sub-h { margin: 0 0 0.5rem; font-size: 0.88rem; color: var(--brand); }
.pair { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr)); }
.pair-half { min-width: 0; }

.class-list { list-style: none; margin: 0 0 1.25rem; padding: 0; }
.class-list li {
  display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: baseline;
  padding: 0.4rem 0; border-bottom: 1px solid var(--line);
}
.class-list .suppressed { font-size: 0.8rem; }

/* The teacher's record — presence and what they keep. Link lists, never
   scores: three open actions is not a worse number than one, so nothing here
   is coloured, ranked or totalled. */
.plain-list { list-style: none; margin: 0 0 1rem; padding: 0; }
.plain-list li {
  display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: baseline;
  padding: 0.4rem 0; border-bottom: 1px solid var(--line);
}
.plain-list li:last-child { border-bottom: 0; }

/* Teaching history — a run of teachers, and the handover it implies. */
.teaching-subjects td.is-gap { color: #7a5a10; font-weight: 600; }
.teaching-subjects td.run { font-size: 0.82rem; color: var(--ink); }
.note.is-gap {
  color: #7a5a10; background: var(--warn-bg, #fbf3df);
  border-left: 3px solid var(--warn-line, #ab9835);
  padding: 0.45rem 0.7rem; border-radius: 0 4px 4px 0;
}

/* --------------------------------------- Reading and Language Usage */

.measured-on { margin: 0.3rem 0 0; font-size: 0.76rem; color: var(--muted); }
.measured-on b { color: var(--brand-deep); }
.subject-note { margin: -0.5rem 0 1.25rem; max-width: 70ch; }

.companion-strip { display: flex; gap: 0.5rem; align-items: center; }
.companion-label {
  font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); white-space: nowrap;
}
.companion-strip .band-bar { flex: 1; height: 0.45rem; }
.companion-counts { font-size: 0.7rem; color: var(--muted); white-space: nowrap; }

.companion {
  background: var(--paper); border: 1px solid var(--line);
  border-inline-start: 4px solid var(--brand-light);
  border-radius: 12px; padding: 1rem 1.25rem 0.5rem; margin-bottom: 1.75rem;
}
.companion .strip { margin-bottom: 1rem; }
.companion .chart { border: 0; padding: 0; }
.roster .col-companion { background: rgba(46, 139, 122, 0.07); }
.roster th.col-companion { border-bottom: 2px solid var(--brand-light); }

/* The Lexile block and its column. Gold rather than green, and with no chip
   anywhere in it: on a page of band chips, a coloured Lexile cell would be
   read as a fourth band, and a Lexile is a reading measure that decides no
   standing (CLAUDE.md rule 12b). */
.lexile-block {
  background: var(--paper); border: 1px solid var(--line);
  border-inline-start: 4px solid var(--gold);
  border-radius: 12px; padding: 1rem 1.25rem 0.6rem; margin-bottom: 1.75rem;
}
.lexile-block .strip { margin-bottom: 0.5rem; }
.lexile-block .stat .value { font-variant-numeric: tabular-nums; }
.roster .col-lexile { background: rgba(171, 152, 53, 0.07); white-space: nowrap; }
.roster th.col-lexile { border-bottom: 2px solid var(--gold); }
.roster th .th-note {
  display: block; font-weight: 400; font-size: 0.62rem; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--muted);
}
.roster .lex-val { font-weight: 600; font-variant-numeric: tabular-nums; }
.roster .lex-shelf { display: block; font-size: 0.68rem; color: var(--muted); }

.folded > summary {
  cursor: pointer; font-size: 0.85rem; color: var(--brand);
  padding: 0.4rem 0; font-weight: 560;
}
.folded > summary::marker { color: var(--gold); }
.folded[open] > summary { margin-bottom: 0.6rem; }

.space-link { margin: -0.5rem 0 1.25rem; }
.space-link .pill { margin-inline-start: 0.25rem; }
.space-open-inline {
  align-items: center; background: var(--paper); border: 1px solid var(--line);
  border-inline-start: 4px solid var(--gold); border-radius: 10px;
  padding: 0.6rem 0.9rem; margin-bottom: 1.25rem;
}
.space-open-inline button {
  font: inherit; padding: 0.35rem 0.85rem; cursor: pointer;
  border: 1px solid var(--brand); background: var(--brand); color: #fff; border-radius: 4px;
}
.space-open-inline button:hover { background: var(--brand-deep); }
.space-open-inline .note { flex: 1 1 16rem; }
/* "I'll pick this up" — the class page's own call to act, shown when no room
   exists yet. Deliberately louder than the old "Open a space" form: this is
   the page where a teacher is looking at the children, and it is the only
   place the work has ever been one press away. */
.pick-up-banner {
  background: var(--paper); border: 1px solid var(--line);
  border-inline-start: 4px solid var(--gold); border-radius: 10px;
  padding: 0.85rem 1.1rem; margin: -0.25rem 0 1.25rem;
  border-bottom: 1px solid var(--line);
}
.pick-up-banner p { margin: 0 0 0.55rem; }
.pick-up-banner p:last-child { margin-bottom: 0; }
.pick-up-banner form { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin: 0; }
.pick-up-banner button {
  font: inherit; padding: 0.4rem 1rem; cursor: pointer;
  border: 1px solid var(--brand); background: var(--brand); color: #fff; border-radius: 4px;
}
.pick-up-banner button:hover { background: var(--brand-deep); }
.pick-up-banner .note { flex: 1 1 20rem; margin: 0; }

.card-space-open button {
  font: inherit; font-size: 0.85rem; padding: 0.3rem 0.7rem; cursor: pointer;
  border: 1px solid var(--brand); background: var(--brand); color: #fff; border-radius: 4px;
}
.card-space-open button:hover { background: var(--brand-deep); }

/* Refer to Inclusion — open to anybody who may see the pupil (Wave I1). */
.inclusion-refer { margin-top: 0.75rem; }
.inclusion-refer > summary { cursor: pointer; font-weight: 600; color: var(--brand); }
.inclusion-refer form { margin-top: 0.6rem; max-width: 34rem; }
.inclusion-refer textarea { width: 100%; font: inherit; padding: 0.4rem; }
.inclusion-refer .btn {
  font: inherit; padding: 0.35rem 0.85rem; cursor: pointer;
  border: 1px solid var(--brand); background: var(--brand); color: #fff; border-radius: 4px;
}

.spaces-stub { margin: 1.5rem 0 1rem; }
.spaces-stub h2 { margin-bottom: 0.35rem; }
.spaces-coverage-list { margin: 0.5rem 0 0; padding-left: 1.25rem; }
.spaces-coverage-list li { margin: 0.25rem 0; }

/* --- the report tabs -------------------------------------------------- */
/* Shared by the two things the reports screen prints: one pupil per sheet,
   or one class per sheet. Here rather than in either feature's stylesheet so
   a third reading cannot end up styled on only one of them. */
.report-tabs {
  display: flex; gap: 0.5rem; flex-wrap: wrap;
  margin: -0.5rem 0 1.25rem;
  border-bottom: 1px solid var(--line);
}
.report-tabs a {
  display: flex; flex-direction: column; gap: 0.1rem;
  padding: 0.55rem 1rem 0.6rem; text-decoration: none;
  border: 1px solid transparent; border-bottom: 0;
  border-radius: 9px 9px 0 0; margin-bottom: -1px;
  color: var(--muted); background: transparent;
}
.report-tabs a:hover { background: var(--wash); color: var(--brand); }
.report-tabs a.on {
  background: var(--paper); border-color: var(--line); color: var(--brand);
  border-top: 3px solid var(--gold); padding-top: calc(0.55rem - 2px);
}
.report-tabs .t { font-size: 0.95rem; font-weight: 650; }
.report-tabs .s { font-size: 0.7rem; color: var(--muted); letter-spacing: 0.01em; }

/* ── Two-pole quadrant and identification butterfly ────────────────────────
   The quadrant is counted rather than scattered: at school grain 1,669 dots
   is a cloud, and the question is which corner and how many. Corner colour
   follows the existing four-box roles so the two charts cannot disagree.
   ------------------------------------------------------------------------ */
.plot .quad rect { pointer-events: none; }
.chart-two-pole .plot .mark-label { font-size: 11px; fill: var(--muted); }
.chart-two-pole .plot text[class*="-count"] {
  font-size: 26px;
  font-weight: 700;
  fill: var(--ink);
}
.chart-two-pole .plot text[class*="-name"] { font-size: 12.5px; font-weight: 600; fill: var(--ink); }
.chart-two-pole .plot text[class*="-action"] { font-style: italic; }
.chart-two-pole .plot .mark-label-need-count { fill: #A8582F; }
.chart-two-pole .plot .mark-label-stalled-count { fill: #2F6D86; }
.chart-two-pole .plot .mark-label-catching_up-count { fill: var(--mark-wae); }
.chart-two-pole .plot .mark-label-thriving-count { fill: var(--gold); }

/* Need warm, stretch cool. Neither is the alarm colour: a child who may need
   support is not a hazard, and a gifted child is not a reward. */
.chart-butterfly .plot .bar-need-found { fill: rgba(168, 88, 47, 0.22); }
.chart-butterfly .plot .bar-need-register { fill: #A8582F; }
.chart-butterfly .plot .bar-stretch-found { fill: rgba(47, 109, 134, 0.22); }
.chart-butterfly .plot .bar-stretch-register { fill: #2F6D86; }
.chart-butterfly .plot .mark-label-grade { font-size: 11px; font-weight: 600; fill: var(--muted); }
.chart-butterfly .plot .mark-label-count { font-size: 10.5px; fill: var(--muted); }
.chart-butterfly .plot .mark-label-grade-empty { font-size: 10.5px; font-style: italic; fill: var(--muted); }
.chart-butterfly .plot .mark-label-axis-need,
.chart-butterfly .plot .mark-label-axis-stretch { font-size: 10.5px; fill: var(--muted); }
/* A hover target here is a whole grade row, not a dot, so the ring that marks
   a dot would draw an ellipse the width of the plot. The row lights instead —
   the same choice, for the same reason, as the strand profile. */
.chart-butterfly .hotspot:hover,
.chart-butterfly .hotspot:focus-within { background: rgba(0, 78, 66, 0.07); }
.chart-butterfly .hotspot:hover::after { content: none; }
.legend .legend-need-register i,
.legend .legend-need-found i,
.legend .legend-stretch-register i,
.legend .legend-stretch-found i { height: 10px; border-radius: 2px; }
.legend .legend-need-register i { background: #A8582F; }
.legend .legend-need-found i { background: rgba(168, 88, 47, 0.22); }
.legend .legend-stretch-register i { background: #2F6D86; }
.legend .legend-stretch-found i { background: rgba(47, 109, 134, 0.22); }
.legend .legend-catching-up i,
.legend .legend-thriving i,
.legend .legend-priority i,
.legend .legend-coasting i { height: 10px; border-radius: 2px; }
.legend .legend-catching-up i { background: rgba(171, 152, 53, 0.45); }
.legend .legend-thriving i { background: rgba(0, 128, 106, 0.45); }
.legend .legend-priority i { background: rgba(176, 71, 62, 0.45); }
.legend .legend-coasting i { background: rgba(47, 109, 134, 0.45); }
.legend .legend-stretch-register i { background: #2F6D86; }
.legend .legend-stretch-found i { background: rgba(47, 109, 134, 0.22); }

/* Growth box rows and the gap-over-time lines (SEN/G&T Wave 4) */
.chart-growth-box .plot .bar-box-sen { fill: rgba(168, 88, 47, 0.28); }
.chart-growth-box .plot .bar-box-gt { fill: rgba(47, 109, 134, 0.28); }
.chart-growth-box .plot .bar-box-school { fill: rgba(110, 118, 121, 0.20); }
.chart-growth-box .plot .cut-median-sen { stroke: #A8582F; stroke-width: 2.5; }
.chart-growth-box .plot .cut-median-gt { stroke: #2F6D86; stroke-width: 2.5; }
.chart-growth-box .plot .cut-median-school { stroke: var(--muted); stroke-width: 2.5; }
.chart-growth-box .plot .mark-label-group { font-size: 11.5px; font-weight: 600; fill: var(--ink); }
.chart-growth-box .plot .mark-label-median-value { font-size: 10.5px; fill: var(--muted); }
.chart-growth-box .plot .mark-label-unreliable,
.chart-growth-box .plot .mark-label-group-empty { font-size: 10.5px; font-style: italic; fill: var(--muted); }
/* The lines take the cmp tokens; the legend swatches are the shared
   .legend-cmp-N rules beside grouped_bars, never repeated here. */
.chart-gap-lines .plot .line-cmp-1 { stroke: var(--cmp-1); stroke-width: 2; }
.chart-gap-lines .plot .line-cmp-2 { stroke: var(--cmp-2); stroke-width: 2; }
.chart-gap-lines .plot .line-cmp-3 { stroke: var(--cmp-3); stroke-width: 2; stroke-dasharray: 4 3; }
.chart-gap-lines .plot .line-cmp-4 { stroke: var(--cmp-4); stroke-width: 2; }
.chart-gap-lines .plot .dot-cmp-1 { fill: var(--cmp-1); }
.chart-gap-lines .plot .dot-cmp-2 { fill: var(--cmp-2); }
.chart-gap-lines .plot .dot-cmp-3 { fill: var(--cmp-3); }
.chart-gap-lines .plot .dot-cmp-4 { fill: var(--cmp-4); }

/* ── Support and stretch, on the pupil page (SEN/G&T Wave 5) ───────────────
   Need warm, stretch cool, and neither is the alarm colour: a child who may
   need support is not a hazard and a gifted child is not a prize.
   ------------------------------------------------------------------------ */
.ident-panel .ident-pole {
  display: inline-block; margin: 0 0 0.6rem;
  padding: 0.1rem 0.6rem; border-radius: 999px;
  font-size: 0.78rem; font-weight: 600;
  border: 1px solid var(--line); color: var(--muted);
}
.ident-panel .ident-pole-need { border-color: #C98A3A; color: #8A5A20; }
.ident-panel .ident-pole-stretch { border-color: #3F7F96; color: #2C5F73; }
.ident-panel .ident-pole-both { border-color: var(--brand); color: var(--brand); }
.ident-signals { list-style: none; margin: 0 0 0.6rem; padding: 0; display: grid; gap: 0.5rem; }
.ident-signal {
  display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: baseline;
  padding-inline-start: 0.7rem; border-inline-start: 2px solid var(--line);
  font-size: 0.86rem; line-height: 1.5;
}
.ident-signal-need { border-inline-start-color: #C98A3A; }
.ident-signal-stretch { border-inline-start-color: #3F7F96; }
.ident-signal-both { border-inline-start-color: var(--brand); }
.ident-code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.76rem; font-weight: 600; color: var(--muted); min-width: 1.8rem;
}
.ident-caveat {
  flex-basis: 100%; margin-inline-start: 0.7rem;
  padding: 0.2rem 0.5rem; background: var(--wash); border-radius: 4px;
  font-size: 0.78rem; color: var(--muted);
}
.ident-set-aside {
  list-style: none; margin: 0.6rem 0 0; padding: 0; display: grid; gap: 0.35rem;
  font-size: 0.82rem; color: var(--muted);
}
.ident-set-aside li {
  padding-inline-start: 0.7rem; border-inline-start: 2px solid var(--line);
}
.class-poles { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0.5rem 0 0; }
.class-pole {
  padding: 0.35rem 0.7rem; border: 1px solid var(--line); border-radius: 6px;
  font-size: 0.82rem; color: var(--muted);
}
.class-pole strong { color: var(--ink); font-weight: 600; }
.class-pole-need { border-inline-start: 3px solid #C98A3A; }
.class-pole-stretch { border-inline-start: 3px solid #3F7F96; }

/* Supported responsive layouts: 360 px phone, 768 px tablet, and desktop.
   Tables retain their own horizontal scroll container rather than forcing the
   whole document wider than its viewport. */
:focus-visible {
  outline: 3px solid var(--gold-text);
  outline-offset: 2px;
}
/* The topbar and the footer are --ground, where that ring is 2.17:1 and a
   focus indicator needs 3:1; --on-ground reads 12.2:1 there. The search
   dropdown hangs out of the topbar on white and keeps its own ring. */
.topbar :focus-visible:not(.topsearch-panel *),
body > footer :focus-visible { outline-color: var(--on-ground); }
@media (max-width: 768px) {
  /* Not on Inspection (revamp P7). Hidden overflow turns anything wide into
     text nobody can reach; the inspection pages put every wide table in its
     own scrolling box instead, so the page itself never needs clipping. The
     body rule is what clips the viewport; the html one needs :has(), and a
     browser without it still has the body rule on every other page. */
  body:not(.is-inspection) { overflow-x: hidden; }
  html:has(> body:not(.is-inspection)) { overflow-x: hidden; }
  .topbar { gap: 0.75rem; padding: 0.65rem 1rem; }
  .topbar::before { width: 5px; margin-inline-end: 0; }
  .nav-groups { flex-basis: 100%; order: 3; gap: 0.85rem; }
  .nav-group .links { gap: 0.45rem 0.65rem; }
  /* The box takes its own row above the nav groups rather than competing with
     the brand for the first one. Its panel stays absolute, so opening it does
     not shove the nav down the screen. */
  .topsearch { flex-basis: 100%; order: 2; flex-grow: 1; }
  .topsearch-panel { width: min(26rem, calc(100vw - 2rem)); }
  .who { margin-inline-start: 0; }
  main { padding: 1rem; }
  .app-feedback { margin-top: 0.75rem; padding-inline: 1rem; }
  .controls { gap: 0.7rem; }
  .controls label { flex: 1 1 12rem; }
  .controls select, .controls input { width: 100%; max-width: 100%; }
  .pair { grid-template-columns: minmax(0, 1fr); }
  .stats .stat { flex: 1 1 9rem; min-width: 0; }
  .chart { padding: 0.75rem; }
  /* The full filter and thirteen-tab chrome can wrap to almost the height of
     a phone screen. Keep it in normal flow on compact screens and make the
     tab groups one horizontal rail, leaving the viewport to the analysis. */
  .analysis-chrome { position: static; padding-top: 0; }
  .analysis-chrome .analysis-tabs {
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-inline: contain;
    gap: 0.55rem; padding-bottom: 0.5rem; scrollbar-width: thin;
  }
  .analysis-chrome .analysis-tabs .tab-group { flex: 0 0 auto; min-width: max-content; }
  .analysis-chrome .analysis-tabs .tab-group-links { flex-wrap: nowrap; }
}
@media (max-width: 420px) {
  body { font-size: 14px; }
  .topbar { align-items: flex-start; }
  .brand img { height: 36px; }
  .brand-school { display: none; }
  .nav-group { width: 100%; }
  .nav-more-panel { position: static; margin-top: 0.35rem; }
  /* The submit button stays at every width: it is the whole no-JavaScript
     path, so it is the last thing that may be dropped. Only the placeholder
     shortens. */
  .topsearch input::placeholder { font-size: 0.8rem; }
  .report-tabs a { flex: 1 1 9rem; }
  .stat .value { font-size: 1.35rem; }
}

/* ------------------------------------------------------- the grade matrix

   Pupils down the side, subjects across the top. A grade is up to 240 pupils
   and 27 subjects, so the table scrolls inside its own frame with the pupil
   column and the header row pinned — the two things you lose first and cannot
   read the grid without.

   Colour carries the band, and never alone: every cell prints its own mark,
   the footer prints the share it was computed from, and the legend names all
   five states including the two that are absences rather than low marks.  */
.matrix-panel {
  background: linear-gradient(180deg, #FFFFFF 0, var(--paper) 9rem);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 1.2rem 1.2rem 1.35rem;
  margin: 1.25rem 0 1.5rem;
  box-shadow: 0 16px 38px rgba(34, 40, 43, 0.07), inset 0 3px 0 var(--brand);
  /* Clears the sticky window/subject chrome above. Without it, following a
     grade link parks the panel's own sticky header underneath the page's, and
     the reader scrolls a grid whose column names are hidden behind a filter
     bar. */
  scroll-margin-top: 12.5rem;
}
.matrix-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; padding: 0.15rem 0 0.9rem;
  border-bottom: 1px solid var(--line);
}
.matrix-head h2, .matrix-head h3 {
  margin: 0.1rem 0 0.3rem; font-size: clamp(1.1rem, 2vw, 1.35rem);
  color: var(--brand-deep); letter-spacing: -0.015em;
}
.matrix-head .note { margin: 0; max-width: 62ch; }
.matrix-close {
  font-size: 0.8rem; text-decoration: none; white-space: nowrap;
  border: 1px solid var(--line); border-radius: 999px;
  padding: 0.2rem 0.7rem; color: var(--muted);
}
.matrix-close:hover { border-color: var(--brand-light); color: var(--brand); }

/* Compact facts answer "what am I looking at?" before the methodology. The
   same primitive is used on both matrices so their grain changes without
   making the reader learn a second interface. */
.matrix-summary {
  display: flex; flex-wrap: wrap; gap: 0.55rem; margin: 0.85rem 0;
}
.matrix-summary-item {
  min-width: 7.5rem; padding: 0.5rem 0.7rem;
  border: 1px solid var(--line); border-radius: 10px; background: var(--wash);
}
.matrix-summary-item .label,
.matrix-summary-item dt,
.matrix-summary-item > span:not(.tipcard) {
  display: block; color: var(--muted); font-size: 0.66rem; font-weight: 650;
  letter-spacing: 0.05em; text-transform: uppercase;
}
.matrix-summary-item .value,
.matrix-summary-item dd,
.matrix-summary-item > strong {
  display: block; margin-top: 0.08rem; color: var(--brand-deep);
  font-size: 0.95rem; font-weight: 700; font-variant-numeric: tabular-nums;
}
.matrix-summary-item dd { margin-inline-start: 0; }
.matrix-summary-item.has-tip { display: block; cursor: help; }
.matrix-summary-item.has-tip:focus-visible {
  outline: 2px solid var(--brand-light); outline-offset: 2px;
}
.matrix-help {
  margin: 0.75rem 0; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper); font-size: 0.82rem;
}
.matrix-help > summary {
  cursor: pointer; padding: 0.55rem 0.75rem; color: var(--brand); font-weight: 650;
}
.matrix-help > summary:hover { background: var(--wash); }
.matrix-help > .matrix-caveats { margin: 0; border-radius: 0 0 9px 9px; }
.matrix-scroll-hint {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  margin: 0.5rem 0 0.4rem; color: var(--muted); font-size: 0.72rem;
}
.matrix-scroll-hint::after {
  content: "\2194"; color: var(--brand); font-size: 1rem; line-height: 1;
}

.matrix-caveats {
  list-style: none; margin: 0.9rem 0 0; padding: 0.6rem 0.85rem;
  background: var(--warn-bg); border-inline-start: 3px solid var(--warn-line);
  border-radius: 0 8px 8px 0; font-size: 0.82rem;
}
.matrix-caveats li + li { margin-top: 0.35rem; }

/* One-click Boys / Girls. A segmented control, not three loose links —
   they are one choice and read as one control. */
.matrix-toggle {
  display: inline-flex; margin: 0 0 0.9rem;
  border: 1px solid var(--line); border-radius: 999px;
  padding: 3px; background: var(--wash);
}
.matrix-toggle a {
  text-decoration: none; font-size: 0.82rem; font-weight: 550;
  padding: 0.28rem 0.95rem; border-radius: 999px; color: var(--muted);
}
.matrix-toggle a:hover { color: var(--brand); }
.matrix-toggle a.on { background: var(--paper); color: var(--brand-deep); box-shadow: 0 1px 2px rgba(34, 40, 43, 0.12); }
.measure-switch .measure-tip { display: flex; }
.measure-switch .measure-tip > a { display: block; }

.matrix-legend {
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.1rem;
  font-size: 0.75rem; color: var(--muted); margin-bottom: 0.6rem;
}
.matrix-legend .key { display: inline-flex; align-items: center; gap: 0.35rem; }
.matrix-legend .sw {
  width: 13px; height: 13px; border-radius: 3px; border: 1px solid var(--line);
  display: inline-block;
}
.matrix-legend .sw.band-WAE { background: rgba(0, 128, 106, 0.22); border-color: rgba(0, 128, 106, 0.45); }
.matrix-legend .sw.band-WWE { background: rgba(166, 140, 34, 0.24); border-color: rgba(166, 140, 34, 0.5); }
.matrix-legend .sw.band-WTE { background: rgba(176, 71, 62, 0.22); border-color: rgba(176, 71, 62, 0.45); }
.matrix-legend .sw.band-none { background: var(--wash); }
.matrix-legend .sw.is-empty { background: repeating-linear-gradient(45deg, var(--paper) 0 3px, var(--wash) 3px 6px); }

/* The grid scrolls inside its own frame rather than moving the page: the page
   header is sticky and 186px tall, so a table that scrolled the document would
   push its own pinned column names underneath it. Sized to the room left below
   that header. */
.matrix-wrap {
  overflow: auto; max-height: calc(100vh - 14rem); min-height: 16rem;
  border: 1px solid var(--line); border-radius: 12px; background: var(--paper);
  overscroll-behavior: contain; scrollbar-width: thin;
  scrollbar-color: var(--brand-light) var(--wash);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.8);
}
.matrix-wrap::-webkit-scrollbar { width: 10px; height: 10px; }
.matrix-wrap::-webkit-scrollbar-track { background: var(--wash); border-radius: 999px; }
.matrix-wrap::-webkit-scrollbar-thumb {
  background: var(--brand-light); border: 2px solid var(--wash); border-radius: 999px;
}
.matrix-wrap:focus-visible { outline: 2px solid var(--brand-light); outline-offset: 2px; }

.grade-matrix { border-collapse: separate; border-spacing: 0; width: auto; min-width: 100%; font-size: 0.82rem; }
.grade-matrix th, .grade-matrix td { border: 0; border-bottom: 1px solid var(--line); padding: 0.3rem 0.45rem; }
.grade-matrix thead th {
  position: sticky; top: 0; z-index: 3;
  background: linear-gradient(180deg, #F8FAF9, var(--wash)); border-bottom: 2px solid var(--line);
  vertical-align: bottom; text-transform: none; letter-spacing: 0;
  font-size: 0.72rem; color: var(--brand-deep);
}
/* The pupil column is the row's label — losing it while scrolling right turns
   the grid into anonymous colour. */
.grade-matrix .mx-name {
  position: sticky; inset-inline-start: 0; z-index: 2;
  background: var(--paper); text-align: start; font-weight: 500;
  white-space: nowrap; min-width: 12rem;
  border-inline-end: 1px solid var(--line);
  box-shadow: 5px 0 10px -10px rgba(34, 40, 43, 0.8);
}
.grade-matrix thead .mx-name { z-index: 4; background: var(--wash); }
.grade-matrix tbody tr:hover .mx-name { background: #F8FAF9; }
.grade-matrix .mx-name a { text-decoration: none; }
.grade-matrix .mx-name a:hover { text-decoration: underline; }

.grade-matrix .mx-sum { text-align: center; width: 3.6rem; color: var(--muted); }
.grade-matrix thead .mx-sum { white-space: nowrap; }
/* A tally is coloured only when it counts something. A zero in the Below
   column is the best news on the row and must not arrive in alarm red. */
.grade-matrix .tally { font-weight: 650; color: #B7BEBE; }
.grade-matrix .tally.above { color: var(--mark-wae); }
.grade-matrix .tally.below { color: var(--mark-wte); }

.mx-sort { text-decoration: none; color: inherit; }
.mx-sort:hover { color: var(--brand); text-decoration: underline; }
.mx-sort.on { color: var(--brand); font-weight: 700; }

.grade-matrix .mx-subject {
  width: 5.6rem; min-width: 5.6rem; max-width: 5.6rem;
  text-align: center; font-weight: 600; line-height: 1.15;
}
.mx-subject-name { display: block; overflow-wrap: anywhere; hyphens: auto; }
.mx-subject-n { display: block; font-weight: 400; color: var(--muted); font-size: 0.66rem; margin-top: 0.15rem; }

.grade-matrix td.mx-cell {
  text-align: center; font-weight: 650; white-space: nowrap; height: 2.5rem;
  border-inline-end: 1px solid rgba(221, 225, 225, 0.5);
}
.grade-matrix td.mx-cell[tabindex] { cursor: pointer; }
.grade-matrix td.mx-cell[aria-expanded="true"] { box-shadow: inset 0 0 0 2px var(--brand); }
.grade-matrix td.mx-cell.band-WAE { background: rgba(0, 128, 106, 0.17); color: #00604F; }
.grade-matrix td.mx-cell.band-WWE { background: rgba(166, 140, 34, 0.22); color: #6F5B00; }
.grade-matrix td.mx-cell.band-WTE { background: rgba(176, 71, 62, 0.19); color: #93362E; }
.grade-matrix td.mx-cell.band-none { color: var(--muted); font-weight: 500; }
/* Not marked is not a low mark, and must not look like one: no fill, a hatch
   so faint it reads as paper, and a dot rather than a zero. */
.grade-matrix td.mx-cell.is-empty {
  background: repeating-linear-gradient(45deg, var(--paper) 0 4px, #FAFBFB 4px 8px);
}
.mx-gap { color: #B7BEBE; }
.mx-mark { font-variant-numeric: tabular-nums; }
/* "Incomplete" is a word where every other cell holds one or two characters.
   It is set smaller so a term whose marks are mostly unfinished does not widen
   every column — the word is kept, because "INC" was the thing nobody read. */
.grade-matrix td.mx-cell[data-inc] .mx-mark {
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0;
}
.mx-note { display: block; font-size: 0.6rem; font-weight: 400; color: var(--muted); }
.mx-flag {
  display: inline-block; margin-inline-start: 0.15rem;
  font-size: 0.62rem; font-weight: 700; color: var(--gold-text);
  vertical-align: super;
}
.matrix-legend .mx-flag { vertical-align: baseline; font-size: 0.75rem; }

/* The movement chip. Deliberately small — it is a second-order figure beside
   the mark itself, and the screenshot's balance had it the other way round. */
.mx-step {
  display: inline-block; margin-inline-start: 0.2rem;
  min-width: 1.35rem; padding: 0 0.25rem;
  font-size: 0.63rem; font-weight: 700; line-height: 1.5;
  border-radius: 999px; vertical-align: 0.08em;
  font-variant-numeric: tabular-nums;
}
.mx-step.up { background: rgba(0, 128, 106, 0.9); color: #fff; }
.mx-step.down { background: rgba(176, 71, 62, 0.9); color: #fff; }

/* Seventeen columns is a long way to carry your eye along one row. The stripe
   is on the sticky name column and on the summary block only — striping the
   marks would fight the band colour, which is the one thing the grid is for. */
.grade-matrix tbody tr:nth-child(even) .mx-name,
.grade-matrix tbody tr:nth-child(even) .mx-sum { background: #FAFBFB; }
.grade-matrix tbody tr:hover .mx-sum { background: #F2F6F5; }
.grade-matrix tbody tr.is-unmarked .mx-name { color: var(--muted); font-style: italic; }
/* Row hover on the mark grid. Scoped away from the teacher matrix on purpose:
   `filter` makes an element a containing block for `position: fixed`, so a
   hover card inside a filtered cell stops being pinned to the viewport and is
   clipped by the scrolling table instead. The grade matrix has no cards in its
   cells, so it can keep the effect. */
.grade-matrix:not(.tm-grid) tbody tr:hover td.mx-cell { filter: brightness(0.97); }
.grade-matrix.tm-grid tbody tr:hover td.mx-cell { box-shadow: inset 0 0 0 99em rgba(34, 40, 43, 0.03); }

.grade-matrix tfoot th, .grade-matrix tfoot td {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--wash); border-top: 2px solid var(--line); border-bottom: 0;
  font-size: 0.7rem; text-align: center;
}
.grade-matrix tfoot .mx-name { z-index: 3; text-align: start; white-space: normal; }
.mx-foot-pct { display: block; font-weight: 700; color: var(--ink); font-size: 0.8rem; }
.mx-foot-n { display: block; color: var(--muted); }
.grade-matrix tfoot td.is-hot .mx-foot-pct { color: var(--mark-wte); }

/* The grade table above, once one of its rows is the one on screen below. */
.th-hint { display: block; font-weight: 400; text-transform: none; letter-spacing: 0; font-size: 0.68rem; color: var(--muted); }
a.grade-open { text-decoration: none; font-weight: 650; }
a.grade-open:hover { text-decoration: underline; }
tr.is-open { background: rgba(0, 78, 66, 0.05); }
tr.is-open > th:first-child { box-shadow: inset 3px 0 0 var(--brand); }

@media (max-width: 720px) {
  .grade-matrix .mx-name {
    width: 8.5rem; min-width: 8.5rem; max-width: 8.5rem;
    overflow: hidden; text-overflow: ellipsis;
  }
  .grade-matrix .mx-name .has-tip,
  .grade-matrix .mx-name .matrix-primary-wrap,
  .grade-matrix .mx-name .tip-anchor {
    display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  }
  .matrix-wrap { max-height: 68vh; min-height: 13rem; }
  .matrix-panel {
    scroll-margin-top: 1rem; margin-inline: -0.35rem;
    padding: 0.85rem 0.75rem 1rem; border-radius: 14px;
  }
  .matrix-summary { gap: 0.4rem; }
  .matrix-summary-item { flex: 1 1 7rem; min-width: 0; }
  .matrix-toggle { max-width: 100%; overflow-x: auto; }
  .matrix-toggle a { padding-inline: 0.75rem; }
  .matrix-caveats { padding: 0.55rem 0.65rem; }
  .tipcard.is-floating {
    inset-inline: 0.75rem; bottom: 0.75rem; width: auto; max-width: none;
    max-height: min(70vh, 30rem); overflow: auto;
  }
}

/* The grade matrix on paper (ported 2026-10-11 from the analysis-upgrade
   branch, 2688a94d). Its floor — a 12rem student column, 5.6rem subjects and
   3.6rem summaries — is wider than a sheet, so a Grade 3 grid printed nine of
   its fifteen headings and the rest fell off the edge with nothing on the
   paper to say so (rule 15a). For print the fixed widths are released: with
   `table-layout: fixed` the columns divide the sheet and no cell's content can
   widen the table. The pinned column and the sticky head go static, because
   nothing scrolls on paper, and the scroll hint goes, because a sheet cannot
   obey it. No `@page` here: this stylesheet is the whole portal's, and a named
   landscape page printed fewer columns than none. The teacher matrix shares
   `.grade-matrix` and gets the same release. */
@media print {
  .matrix-wrap { max-height: none; overflow: visible; border: 0; }
  .matrix-toggle, .matrix-close, .matrix-scroll-hint { display: none; }
  .grade-matrix { table-layout: fixed; width: 100%; min-width: 0; font-size: 0.58rem; }
  .grade-matrix th, .grade-matrix td { padding: 0.15rem 0.2rem; overflow-wrap: anywhere; }
  .grade-matrix thead th { position: static; font-size: 0.55rem; }
  .grade-matrix .mx-name {
    position: static; box-shadow: none;
    width: 13%; min-width: 0; max-width: none; white-space: normal;
  }
  .grade-matrix .mx-subject { width: auto; min-width: 0; max-width: none; }
  .grade-matrix .mx-sum { width: auto; }
  .grade-matrix thead .mx-sum { white-space: normal; }
  .grade-matrix td.mx-cell { white-space: normal; height: auto; }
  .grade-matrix tfoot th, .grade-matrix tfoot td { position: static; }
}

/* ------------------------------------------------- the teacher matrix

   Reuses .matrix-panel / .grade-matrix from the Grades tab — the two grids
   are the same object at a different grain, and a reader who has learned one
   should not have to learn the other. What differs is the cell: this one
   carries a RAG on a chosen measure rather than a mark band, and the count it
   was computed from sits under the figure rather than in a footer, because a
   teacher cell over eight pupils is usually eight pupils.  */
.teacher-matrix { margin-top: 2rem; }
.teacher-matrix .tm-sub {
  margin: 1.4rem 0 0.2rem; font-size: 0.95rem; color: var(--brand-deep);
}
.teacher-matrix .tm-sub + .note { margin-top: 0; margin-bottom: 0.5rem; }
/* The yardstick grid is short and must not scroll away from the grid it is
   the yardstick for. */
.matrix-wrap.tm-reference { max-height: none; min-height: 0; margin-bottom: 0.4rem; }
.matrix-wrap.tm-reference,
.matrix-wrap.tm-teachers { scroll-behavior: smooth; }

.measure-switch { flex-wrap: wrap; max-width: 100%; }
.measure-switch a { white-space: nowrap; }

.grade-matrix.tm-grid .mx-subject {
  width: 4.6rem; min-width: 4.6rem; max-width: 4.6rem;
}
.tm-subject-col {
  white-space: nowrap; max-width: 11rem; overflow: hidden; text-overflow: ellipsis;
  color: var(--muted); font-size: 0.78rem;
}
.grade-matrix.tm-grid thead .tm-subject-col { color: var(--brand-deep); }
/* The teacher matrix's own widths, released on paper for the reason the grade
   matrix's are above. Here and not in that block: these rules are as specific
   as the screen ones just above and must come after them to win. */
@media print {
  .grade-matrix.tm-grid .mx-subject { width: auto; min-width: 0; max-width: none; }
  .tm-subject-col { white-space: normal; max-width: none; }
}

.tm-cell { line-height: 1.15; }
.tm-cell .mx-mark { display: block; font-variant-numeric: tabular-nums; }
/* The n is deliberately quiet and deliberately always there. */
.tm-cell .tm-n {
  display: block; font-size: 0.6rem; font-weight: 400; color: var(--muted);
}
/* No fill for in line — see `_prompt_only`. Only a prompt is coloured. */
.tm-cell.rag-none { background: var(--paper); color: var(--ink); }
.tm-cell.rag-amber { background: rgba(166, 140, 34, 0.20); color: #6F5B00; }
.tm-cell.rag-red { background: rgba(176, 71, 62, 0.19); color: #93362E; }
.tm-cell.rag-red .tm-n, .tm-cell.rag-amber .tm-n { color: inherit; opacity: 0.7; }

/* The teacher-matrix hover card. Two departures from the default card, both
   because this one lists people: the key column flexes so a full name is not
   squeezed into 7.2rem, and the value column sits right so a column of
   percentages reads as a ranking. */
.tipcard.card-teacher-matrix { width: 26rem; }
/* The key column keeps a floor so a two-word field label cannot be squeezed
   into three lines by a long value beside it, and a ceiling so a long teacher
   name cannot push the figures off the card. */
.tipcard.card-teacher-matrix .tip-row {
  grid-template-columns: minmax(7.5rem, 1fr) minmax(0, auto);
  align-items: baseline; gap: 0.75rem;
}
.tipcard.card-teacher-matrix .tip-row .k {
  overflow-wrap: anywhere; font-size: 0.73rem;
}
.tipcard.card-teacher-matrix .tip-row .v {
  text-align: end; font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}
/* The row for the teacher whose cell this is. */
.tipcard.card-teacher-matrix .tip-row.tone-good .k { font-weight: 650; color: var(--brand-deep); }
.tipcard.card-teacher-matrix .tip-row.tone-good .v { color: var(--brand-deep); }

/* The whole cell is the hover target, and the anchor fills it so there is no
   dead strip between the figure and the cell border. */
.tm-cell .has-tip { display: block; }
.tm-cell .has-tip .tip-anchor { display: block; }
.tm-cell .has-tip .tip-anchor:focus-visible {
  outline: 2px solid var(--brand-light); outline-offset: -2px; border-radius: 3px;
}
.tm-summary-cell .has-tip,
.tm-summary-cell .has-tip .tip-anchor,
.grade-matrix.tm-grid thead .mx-sum .has-tip,
.grade-matrix.tm-grid thead .mx-sum .tip-anchor { display: block; }
.tm-summary-cell .tip-anchor:focus-visible {
  outline: 2px solid var(--brand-light); outline-offset: -2px; border-radius: 3px;
}

/* Grade-matrix hover cards. Same pinned corner as everywhere else; the anchor
   fills its cell so there is no dead strip beside the mark. */
.tipcard.card-grade-matrix { width: 24rem; }
.grade-matrix .mx-cell .has-tip,
.grade-matrix .mx-subject .has-tip { display: block; }
.grade-matrix .mx-cell .has-tip .tip-anchor,
.grade-matrix .mx-subject .has-tip .tip-anchor { display: block; }
.grade-matrix .mx-name .has-tip { display: inline-block; max-width: 100%; }
.grade-matrix .has-tip .tip-anchor:focus-visible {
  outline: 2px solid var(--brand-light); outline-offset: -2px; border-radius: 3px;
}

/* The one browser-composed card (grade-matrix cells). Same .tipcard design as
   every server-rendered one; only the fill differs. Pinned to the corner for
   the same reason as `.has-tip` — the grid scrolls, and a box that scrolls
   also clips. */
.tipcard.is-floating {
  display: none;
  position: fixed; inset-inline-end: 1.25rem; bottom: 1.25rem;
  inset-inline-start: auto; top: auto;
  width: 24rem; z-index: 80;
}
.tipcard.is-floating.is-on { display: block; }

/* Charts remain part of the evidence, but the comparison matrix is the
   primary working surface. Supporting plots follow it in a native,
   keyboard-operable disclosure rather than forming a wall before the grid. */
.matrix-support {
  margin: 1rem 0 1.4rem; border: 1px solid var(--line);
  border-radius: 12px; background: var(--paper);
}
.matrix-support > summary {
  cursor: pointer; padding: 0.7rem 0.9rem; color: var(--brand); font-weight: 650;
}
.matrix-support > summary:hover { background: var(--wash); }
.matrix-support > :not(summary) { margin-inline: 0.9rem; }

/* =====================================================================
   THE KFS DIAMOND — the platform's visual identity
   =====================================================================
   The mark is the school's own logo reduced to its geometry: a gold
   diamond, and a smaller solid diamond at its centre. It is not an
   invention. adt_platform/static/img/kfs-logo.png is a green rhombus
   framing a gold-bordered square, and this is that shape with the
   portrait taken out of it.

   Drawn by scripts/build_brand_marks.py, which holds the geometry and
   the four weights. Written up in docs/brand.md. Nothing here should be
   edited to change the mark's proportions — change them there and run
   the script.

   Two rules govern where it appears:

     * On a dark surface the ground is --ground, never --brand. That is
       what makes gold legible as text (4.99:1 against 3.35:1).
     * The mark never appears twice in one field of view. The KFS logo
       already contains a diamond, so a page showing the logo shows the
       mark somewhere else, or not at all.
   --------------------------------------------------------------------- */

/* ---- The mark itself ----
   This class sets a size. It does not choose a file, and the two have to
   agree, because each weight is drawn for a band of rendered pixels and
   outside its band it either disappears or goes clumsy:

     mark-display.svg   200px and up
     mark.svg           32 - 200px      <- the 2rem default
     mark-compact.svg   20 - 32px
     mark-min.svg       under 20px      the centre alone

   A caller that shrinks .kfs-mark below 2rem changes the src too.

   Named .kfs-mark and not .mark because .mark is taken: it is the green
   masthead bar on the printed reports and the class banner, in seven
   templates, scoped by .report-card .masthead .mark and
   .class-banner .masthead .mark. Neither of those sets a height, so a bare
   .mark rule with one would have squashed every printed sheet. */
.kfs-mark {
  display: block;
  width: var(--mark-size, 2rem);
  height: var(--mark-size, 2rem);
  flex: none;
}

/* ---- The dark chrome: topbar and footer ----
   Both were --brand. On the ground every pair in them reads better:
   white 9.7 -> 14.4, --gold-light 4.7 -> 7.0, --soft-on-green 6.2 -> 9.2.
   --brand is untouched and still carries links, headings, buttons and
   every printed report. */
.topbar { background: var(--ground); }

/* The Analysis "More" panel hangs out of the topbar, so it followed it. One
   step darker rather than the same green: it is an overlay, and at --ground
   it would merge into the bar it drops from. The gold border and the shadow
   were already doing that job and now have something to work against. */
.nav-more-panel { background: var(--ground-2); }

/* The logo sat in a white chip, which on a dark bar reads as a hole rather
   than as a plate — and the logo is portrait, so a chip sized by height is
   nearly square and the white dominates the mark inside it.

   On --ground the chip is not needed. The logo's own green (#0B4F43) is a
   shade lighter than the ground, so its diamond stays visible, and the gold
   frame around the portrait does the identity work by itself. Larger,
   because it no longer has to share its box with a border. */
.brand img {
  height: 52px;
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
}
.brand-school { color: var(--on-ground-dim); }
.who { color: var(--on-ground-dim); }

/* ---- The footer carries the mark ----
   The topbar does not: it already shows the logo, and the logo is a
   diamond. Down here there is no logo, so this is where the mark signs
   the page. */
body > footer {
  align-items: center;
  background: var(--ground);
  color: var(--on-ground-dim);
}
body > footer span { color: var(--on-ground-dim); }
.footer-mark {
  display: flex; align-items: center; gap: 0.6rem;
  color: var(--on-ground-dim);
}
/* 1.1rem is 17.6px, which is under the outline's floor — so base.html
   points this one at mark-min.svg, the centre alone. */
.footer-mark .kfs-mark { --mark-size: 1.1rem; }

/* ---- The motif ----
   A rule with the mark set into it. Used to open a section on an
   identity surface — the sign-in card, a public page's masthead — and
   deliberately not used inside working screens, where a plain border is
   quieter and a table is already busy. */
.diamond-rule {
  display: flex; align-items: center; gap: 0.75rem;
  margin: 1.1rem 0;
  color: var(--gold);
}
.diamond-rule::before,
.diamond-rule::after {
  content: ""; height: 1px; flex: 1;
  background: currentColor; opacity: 0.45;
}
.diamond-rule .kfs-mark { --mark-size: 1.25rem; }  /* 20px = compact floor */

/* There is deliberately no .ground / .ground-watermark utility. The one
   dark identity surface that needs the gradient and the watermark is the
   sign-in page, and body.auth-page carries both directly. A second copy
   of the same two rules, used by nothing, is how the two drift apart —
   the same reason .kfs-masthead was written and then deleted below. */

/* The masthead the four public pages share is ``.public-top``, and it
   lives in reports.css where it already was. It is not restated here:
   two implementations of one strip is how the two drift apart. */

/* ============================================================ pages, 2026-09-05
   Shared parts for the class, pupil and teacher pages — the design pass in
   docs/notes/pages-design-plan-2026-09-05.md. Built once, assembled per page. */

/* The section rail: every h2 on the page as an anchor chip, the one in view
   lit, sticky under the top of the viewport. Filled by pages.js. */
/* A chip lands on its heading, not under the rail that is stuck above it. */
main h2[id] { scroll-margin-top: 3.25rem; }
.section-rail { position: sticky; top: 0; z-index: 6; margin: 0 -0.25rem 1.25rem; padding: 0.45rem 0.25rem; background: var(--bg); }
.section-rail ul {
  list-style: none; margin: 0; padding: 0.2rem 0; display: flex; gap: 0.35rem;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.section-rail ul::-webkit-scrollbar { display: none; }
.section-rail li { flex: 0 0 auto; }
.section-rail a {
  display: inline-block; padding: 0.28rem 0.7rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--paper); color: var(--brand-deep);
  font-size: 0.74rem; font-weight: 600; text-decoration: none; white-space: nowrap;
}
.section-rail a:hover { border-color: var(--brand-light); }
.section-rail a[aria-current] { background: var(--brand); border-color: var(--brand); color: #fff; }
@media print { .section-rail { display: none !important; } }

/* A chart's prose: one sentence visible, the rest behind one disclosure. */
.chart figcaption .chart-lead { display: block; }
.chart-about { margin-top: 0.35rem; }
.chart-about > summary { cursor: pointer; color: var(--brand); font-weight: 600; font-size: 0.76rem; }
.chart-about .chart-more { display: block; margin-top: 0.4rem; }
.chart-about .limitation { margin-top: 0.4rem; }
.chart-about .chart-data { display: block; margin-top: 0.55rem; font-size: 0.78rem; }
.chart-about .chart-data-head { display: block; color: var(--ink); font-weight: 600; }
.chart-about .chart-data ul { margin: 0.35rem 0 0; padding-inline-start: 1.25rem; }
@media print { .chart-about > summary { display: none; } .chart-about { display: block; } }

/* One line for every feed with nothing to say, in place of a section each. */
.absent-line {
  margin: 1.25rem 0 0; padding: 0.6rem 0.85rem; border: 1px dashed var(--line);
  border-radius: 8px; font-size: 0.8rem; color: var(--muted); line-height: 1.5;
}
.absent-line .absent-lead { color: var(--ink); font-weight: 600; margin-inline-end: 0.35rem; }
.absent-line .absent-item b { color: var(--ink); font-weight: 600; }
.absent-line .absent-item[title] { cursor: help; }

/* What a narrowed view covers, under the heading of every page that narrows.
   Quiet on purpose: it is a standing fact about the page, not an alert about
   it, so it takes the gold rule the house style keeps for a marker and none of
   the warning colours. */
.segment-view {
  margin: 0 0 1.25rem; padding: 0.55rem 0.85rem;
  border-inline-start: 3px solid var(--gold); background: var(--paper);
  font-size: 0.82rem; color: var(--muted); line-height: 1.5;
}
.segment-view p { margin: 0; }
.segment-view p + p { margin-top: 0.3rem; }
.segment-view b { color: var(--ink); font-weight: 600; }

/* The honest tile: nothing measured is a dash and a reason, never 0 of 0. */
.stat.is-unmeasured { border-inline-start-color: var(--line); flex: 1 1 100%; }
.stat.is-unmeasured .value { color: var(--muted); font-size: 1.3rem; }
.strip-unmeasured { margin-bottom: 1.25rem; }

/* Chip vocabulary — five kinds, one block. A *band* chip (.chip-WAE/WWE/WTE)
   prints a code only where a key sits beside it (a grid, a tile row); read
   alone it prints the word (page_tags.band_word). A *flag* pill (.pill-warn,
   .pill-alert, .pill-top10) marks a caution about a figure. A *state* chip
   (.chip-none, .pill without a tone) says a fact is absent or unfinished. ABT
   chips (.chip-abt-*) and inspection levels (.chip-outstanding …) keep their
   own families by rule 8 and rule 15c. The two shapes share one metric so a
   row of mixed chips sits on one line. */
.chip, .pill { line-height: 1.35; vertical-align: middle; }
.pill-top10 { color: var(--brand); border-color: var(--brand-light); }

/* A section that opens on its summary line and keeps its table behind it. */
.fold { margin: 0.5rem 0 0; }
.fold > summary { cursor: pointer; color: var(--brand); font-weight: 600; font-size: 0.82rem; }
.fold > summary::marker { color: var(--gold); }
.fold[open] > summary { margin-bottom: 0.6rem; }
@media print { .fold > summary { display: none; } }

/* A card with a switch: several same-shaped panels, one shown at a time. */
.switch-card { padding-top: 0.75rem; }
.switch-row { display: flex; gap: 0.35rem; flex-wrap: wrap; margin-bottom: 0.75rem; }
.switch-row a, .switch-row button {
  font: inherit; font-size: 0.76rem; font-weight: 600; padding: 0.3rem 0.75rem;
  border-radius: 999px; border: 1px solid var(--line); background: var(--paper);
  color: var(--brand-deep); cursor: pointer; text-decoration: none;
}
.switch-row [aria-selected="true"], .switch-row .is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
.switch-panel[hidden] { display: none; }
@media print { .switch-panel[hidden] { display: block; } .switch-row { display: none; } }

/* Start here — three sentences and the one button, in place of a yellow banner. */
.start-here { border-inline-start: 4px solid var(--gold); }
.start-here h2 { margin-top: 0; border: 0; padding: 0; }
.start-lines { list-style: none; margin: 0.25rem 0 0; padding: 0; }
.start-lines li { padding: 0.45rem 0; border-top: 1px solid var(--line); font-size: 0.92rem; line-height: 1.45; }
.start-lines li:first-child { border-top: 0; }
.start-lines b { color: var(--ink); }
.start-lines a { margin-inline-start: 0.35rem; font-size: 0.8rem; font-weight: 600; text-decoration: none; white-space: nowrap; }
.start-lines a::after { content: " →"; }
.start-action { display: flex; gap: 0.9rem; align-items: flex-start; flex-wrap: wrap; margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--line); }
.start-action button {
  font: inherit; font-weight: 600; padding: 0.45rem 1rem; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--brand); background: var(--brand); color: #fff; white-space: nowrap;
}
.start-action button:hover { background: var(--brand-deep); }
.start-action .note { flex: 1 1 20rem; }
.start-here .space-link { margin: 0.75rem 0 0; }
/* A disclosure inside a note: the rest of the sentence, one click away. */
.fold-inline { display: inline; }
.fold-inline > summary { display: inline; cursor: pointer; color: var(--brand); font-weight: 600; list-style: none; }
.fold-inline > summary::-webkit-details-marker { display: none; }
.fold-inline > summary::after { content: " ▸"; }
.fold-inline[open] > summary::after { content: " ▾"; }
.fold-inline[open] > summary { display: block; margin-top: 0.3rem; }
.agreement-line { margin-top: 0.75rem; }
.agreement-line.is-flagged { border-inline-start: 3px solid var(--warn-line); padding-inline-start: 0.6rem; }

/* The pupil page, 2026-09-05: a measure sits in the subject's own standing
   card; the per-subject history blocks share one card with a switch. */
.stand-measure { margin: 0.55rem 0 0; padding-top: 0.5rem; border-top: 1px dashed var(--line); font-size: 0.78rem; color: var(--muted); line-height: 1.45; }
.stand-measure b { color: var(--brand-deep); font-variant-numeric: tabular-nums; }
.history-card .pair { margin-top: 0.75rem; }
.history-card .sub-h { margin-top: 0; }
.risk-line { font-size: 0.92rem; }
.risk-line .chip { vertical-align: 0.1em; }
.section-summary { font-size: 0.92rem; margin: 0 0 0.5rem; }

/* The teacher page, 2026-09-05. */
.window-note { margin: -0.5rem 0 1rem; }
.window-note b { color: var(--ink); }
.teacher-class-grid { margin-bottom: 0.5rem; }

/* My classes, 2026-09-05: a school of cards reads by year group. */
.class-filter { display: flex; gap: 0.9rem; align-items: flex-end; flex-wrap: wrap; margin-bottom: 1rem; }
.class-filter label { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.78rem; color: var(--muted); }
.class-filter input, .class-filter select { font: inherit; padding: 0.35rem 0.5rem; border: 1px solid var(--line); border-radius: 4px; background: var(--paper); min-width: 12rem; }
.class-filter button { font: inherit; padding: 0.4rem 0.9rem; border: 1px solid var(--brand); background: var(--brand); color: #fff; border-radius: 4px; cursor: pointer; }
.class-filter-clear { font-size: 0.82rem; align-self: center; }
.class-group { margin: 0 0 0.75rem; }
.class-group > summary { font-size: 0.95rem; color: var(--ink); font-weight: 500; padding: 0.55rem 0; border-bottom: 1px solid var(--line); }
.class-group > summary b { color: var(--brand-deep); }
.class-group > summary .is-alert { color: var(--wte); font-weight: 600; }
.class-group[open] > summary { margin-bottom: 0.9rem; }
/* On a list of many cards the pick-up is a link, and a button only on the class page. */
.class-card .card-space-open .linklike {
  font: inherit; font-size: 0.82rem; font-weight: 600; padding: 0; border: 0;
  background: none; color: var(--brand); cursor: pointer; text-decoration: none;
}
.class-card .card-space-open .linklike:hover { text-decoration: underline; }

/* Analysis print — Phase 5. One block so a printed tab is the figures, not
   the strip. Teachers keep every column; type shrinks to fit. */
@media print {
  .noprint,
  .topbar,
  body > footer,
  .controls {
    display: none !important;
  }
  .table-wrap {
    overflow: visible;
    max-height: none;
  }
  .is-analysis table { font-size: 0.8rem; }
}

/* ---------------------------------------------------------------- feedback
   The button on every signed-in page, and the sheet behind it.

   Bottom-inline-start, z-index 45. `.orbit-dock` on Spaces is bottom-centre at
   40, so the two sit side by side in one glance without either overriding the
   other; `inset-inline-start` rather than `left` keeps the button off the
   reading edge when a page renders RTL.

   House tokens only: --brand for the ground the label sits on, --gold as its
   rule (gold on --brand is 3.35:1 and may never be text there), --paper and
   --line for the sheet, the same as every card on the platform. */
.feedback-fab {
  position: fixed;
  inset-inline-start: 1.25rem;
  bottom: 1.25rem;
  z-index: 45;
  background: var(--brand);
  color: #fff;
  border: 1px solid var(--gold);
  border-radius: 999px;
  padding: 0.55rem 1.1rem;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
}
.feedback-fab:hover { background: var(--brand-deep); }
/* the fixed button must not cover the last chart: button height + gap of room
   under <main>, above the footer — only on a page that draws the button, and
   never under the footer, where it showed as a strip of page background */
@media (max-width: 30rem) { body:has(.feedback-fab) > main { padding-bottom: 4.5rem; } }
.feedback-fab:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.feedback-dialog {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 1.25rem 1.4rem;
  width: min(34rem, 92vw);
  color: inherit;
}
.feedback-dialog::backdrop { background: rgba(4, 48, 40, 0.45); }
/* The sheet's title is a <p>, not an <h2>: the dialog sits above <main> on
   every page, and an h2[id] here becomes the page's first heading for the
   section rail (pages.js) and for a screen reader's heading list. */
.feedback-title {
  margin: 0 0 0.35rem;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.25;
  color: var(--brand);
}
.feedback-lede { color: var(--muted); font-size: 0.82rem; margin: 0 0 1rem; }
.feedback-sheet label {
  display: block;
  font-size: 0.78rem;
  color: var(--muted);
  margin: 0.8rem 0 0.2rem;
}
.feedback-hint { text-transform: none; letter-spacing: 0; }
.feedback-sheet select,
.feedback-sheet textarea,
.feedback-sheet input[type="file"] {
  width: 100%;
  font: inherit;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
  color: inherit;
}
.feedback-sheet textarea { resize: vertical; }
.feedback-error { color: var(--wte); font-size: 0.82rem; margin: 0.8rem 0 0; }
.feedback-row { display: flex; gap: 0.6rem; justify-content: flex-end; margin-top: 1.1rem; }
.feedback-btn {
  font: inherit;
  padding: 0.45rem 1rem;
  border-radius: 8px;
  cursor: pointer;
  border: 1px solid var(--line);
  background: var(--paper);
  color: inherit;
}
.feedback-btn.primary { background: var(--brand); color: #fff; border-color: var(--brand); }
.feedback-btn.primary:hover { background: var(--brand-deep); }
.feedback-btn:disabled { opacity: 0.55; cursor: default; }
.feedback-thanks { margin: 0; padding: 1.5rem 0; text-align: center; font-weight: 600; color: var(--brand); }

/* ------------------------------------------------------- the weekly report
   /weekly/ — thirteen sections, each a card, and every figure a count. The
   only new shapes are the seven-day sync strip, the numbered sentence list
   and the week-by-week table; everything else reuses .card, .stats, .note,
   .is-unmeasured and .table-wrap.
*/
.weekly-caption { border-inline-start: 3px solid var(--brand); padding-inline-start: 0.6rem; }
.weekly-weeks { display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 0.8rem 0 1.1rem; }
.weekly-weeks a {
  font-size: 0.85rem;
  padding: 0.3rem 0.65rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  text-decoration: none;
}
.weekly-weeks a:hover { border-color: var(--brand-light); }
.weekly-section { margin-bottom: 1.1rem; }
.weekly-section h2 { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; }
/* A section reading a real happened-at date says so on its own heading line,
   rather than leaning on the standing caption, which is about the sync's clock. */
.weekly-section h2 .real-dates { font-size: 0.78rem; font-weight: 400; }
.weekly-where { margin-top: 0.9rem; }

.weekly-sentences { margin: 0; padding-inline-start: 1.3rem; display: grid; gap: 0.6rem; }
.weekly-sentences .weekly-says { display: block; }
.weekly-sentences .weekly-source { display: block; font-size: 0.78rem; }

/* Seven cells, Sunday to Saturday, every day present including the ones
   nothing ran on. A colour says what happened and the words say it too — the
   cell is never a bare swatch. */
.weekly-strip {
  list-style: none;
  margin: 0.4rem 0 1rem;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.4rem;
}
.weekly-day {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.45rem 0.5rem;
  font-size: 0.78rem;
  display: grid;
  gap: 0.15rem;
}
.weekly-day b { font-size: 0.85rem; }
.weekly-day-date { color: var(--muted); }
.weekly-day.is-ok { border-color: var(--brand-light); }
.weekly-day.is-failed, .weekly-day.is-interrupted { border-color: var(--wte); }
.weekly-day.is-running { border-color: var(--gold); }
.weekly-day.is-none { background: var(--bg); color: var(--muted); }

.weekly-cannot { margin: 0.5rem 0 0; }
.weekly-cannot dt { font-weight: 600; margin-top: 0.6rem; }
.weekly-cannot dd { margin: 0.2rem 0 0; font-size: 0.85rem; color: var(--muted); }
.weekly-fold { margin-top: 1rem; }
.weekly-history tbody th .note { display: block; font-weight: 400; }
.weekly-history tr.is-current > th { border-inline-start: 3px solid var(--brand); }

@media (max-width: 640px) {
  /* Seven columns do not fit a phone; two rows of four and three do. */
  .weekly-strip { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media print {
  /* The week-by-week table is the part somebody takes to a meeting, so it
     stays; the rail and the week picker are navigation and go. Every fold is
     opened, because a printed page cannot be clicked. */
  .weekly-weeks { display: none !important; }
  .weekly-fold, .weekly-section details { display: block !important; }
  .weekly-fold > summary, .weekly-section details > summary { display: none !important; }
  .weekly-history { break-inside: auto; }
  .weekly-section { break-inside: avoid; }
}

/* ── NAFS, the national test (NAFS P3, 2026-10-01) ─────────────────────────
   ETEC's cards on the NAFS tab and the Dashboard. A NAFS performance level and
   the minimum-mastery share are ETEC's measures and never a band (DR-405), so
   nothing here borrows a band token (--mark-wae/wwe/wte, --wae, --wte) or a
   band class: the figures are ink, the levels ride the band-neutral ramp
   (.bar-v*), and the three card series keep one colour each on every chart
   that draws them — school rust, directorate blue, kingdom grey dashed — by
   roles of their own (nafs-school / -directorate / -kingdom, below), never the
   categorical cmp-1/cmp-2, whose fills are the WAE/WWE mark tokens.
   The tab is chart-first since DR-452 (one block per card, everything else in
   closed folds); the .nafs-table rules are the Dashboard's section.
   ------------------------------------------------------------------------ */
.nafs-table th.nafs-card-cell {
  vertical-align: top; text-transform: none; letter-spacing: 0;
  font-size: 0.85rem; color: var(--ink); font-weight: 600; min-width: 9rem;
}
.nafs-table th.nafs-card-cell .note { display: block; font-weight: 400; }
.nafs-table tbody + tbody { border-top: 2px solid var(--line); }
.nafs-table th.nafs-card-cell { width: 13rem; }
.nafs-table td.num, .nafs-table th.num { white-space: nowrap; }
.nafs-table .note { font-size: 0.75rem; }
.nafs-marker { color: var(--muted); font-size: 0.7em; margin-inline-start: 1px; }

.nafs-card {
  margin: 0 0 1.75rem; padding: 0.25rem 0 0;
  border-top: 3px solid var(--gold);
}
.nafs-card > h2 { margin: 0.75rem 0 0.15rem; }
.nafs-lead { max-width: 84ch; margin: 0 0 1.25rem; }
.nafs-tested { margin: 0 0 0.6rem; font-size: 0.9rem; color: var(--muted); }
/* The card's one chart, kept near its own size: a 720-wide frame stretched
   across the whole column printed its ticks at half again their size. */
.nafs-sitting { max-width: 48rem; }
.nafs-sitting .chart { margin-bottom: 0.6rem; }
/* Each bar's share, printed on it: ink, not the muted grey a figure beside a
   bar takes elsewhere — on this chart the figures are the point. */
.chart-nafs-sitting .plot .mark-label-value { font-size: 12px; font-weight: 650; fill: var(--ink); }
/* The card's own printed changes: ink on a wash, never green or red — green
   is the Working Above band's colour and a NAFS change is not a band (DR-405). */
.nafs-changes {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem 0.5rem;
  margin: 0 0 0.45rem; font-size: 0.88rem;
}
.nafs-changes-head { color: var(--muted); }
.nafs-chip {
  display: inline-block; padding: 0.1rem 0.6rem; border: 1px solid var(--line);
  border-radius: 999px; background: var(--wash); color: var(--ink);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.nafs-headline { margin: 0 0 0.5rem; font-size: 0.88rem; color: var(--ink); }
.nafs-beside > .note { max-width: 84ch; }
.nafs-subject { margin: 1.25rem 0 0; padding-top: 1rem; border-top: 1px solid var(--line); }
.nafs-subject-head { margin: 0 0 0.6rem; font-size: 1.05rem; color: var(--brand-deep); }
.nafs-subject-head small { display: block; font-size: 0.8rem; font-weight: 400; color: var(--muted); margin-top: 0.15rem; }
.nafs-subject .chart-nafs-levels { margin-bottom: 1rem; }
.nafs-charts { margin: 0.6rem 0 0.25rem; }
.nafs-mean-note { align-self: center; }
/* The three card series, by their own roles (charts.NAFS_SERIES_ROLES), on
   every chart that draws them: the tab, its lazy fold, ?card=all, the
   Dashboard and the appraisal's 6b card (DR-405). Rooted at .plot and under no
   container: an override scoped to .nafs-card left the appraisal's bars in the
   Working Above and Within tokens (fix cycle 1), and the appraisal PDF inlines
   only rules that start with ".plot " into each chart. They sit after
   ".plot .mark-bar", whose fill is the WAE token, and win on order. A swatch is
   drawn as its bars are; on the year-by-year lines it is the line it names. */
.plot .bar-nafs-school { fill: #A8582F; fill-opacity: 0.9; }
.plot .bar-nafs-directorate { fill: #2F6D86; fill-opacity: 0.9; }
.plot .bar-nafs-kingdom { fill: var(--muted); fill-opacity: 0.55; }
.plot .line-nafs-school { stroke: #A8582F; }
.plot .line-nafs-directorate { stroke: #2F6D86; }
.plot .line-nafs-kingdom { stroke: var(--muted); stroke-dasharray: 4 3; }
.plot .dot-nafs-school { fill: #A8582F; }
.plot .dot-nafs-directorate { fill: #2F6D86; }
.plot .dot-nafs-kingdom { fill: var(--muted); }
.legend .legend-nafs-school i { background: #A8582F; opacity: 0.9; height: 10px; border-radius: 3px; }
.legend .legend-nafs-directorate i { background: #2F6D86; opacity: 0.9; height: 10px; border-radius: 3px; }
.legend .legend-nafs-kingdom i { background: var(--muted); opacity: 0.55; height: 10px; border-radius: 3px; }
.chart-nafs-lines .legend .legend-nafs-school i,
.chart-nafs-lines .legend .legend-nafs-directorate i { height: 3px; opacity: 1; border-radius: 2px; }
.chart-nafs-lines .legend .legend-nafs-kingdom i {
  height: 0; opacity: 1; border-radius: 0; background: transparent;
  border-top: 2px dashed var(--muted);
}

/* The triangle: three tiles, dashed edges, no axis and no shared scale
   (DR-415). A tile's accent says which instrument it is — NAFS the rust of its
   own School series, our marks a violet, MAP blue — never how good its share
   is, so no tile wears the house gold (the --wwe hex) or green-light (beside
   --mark-wae) (DR-405, fix cycle 2). An absent corner is a dashed grey tile
   with its reason (rule 15a). */
.nafs-tri-grid {
  display: grid; gap: 1rem 1.25rem; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(30rem, 1fr));
}
.nafs-tri {
  margin: 0; background: var(--paper); border: 1px solid var(--line);
  border-radius: 12px; padding: 0.75rem 0.9rem 0.6rem; min-width: 0;
}
.nafs-tri-title { font-weight: 650; color: var(--brand-deep); font-size: 0.92rem; margin: 0 0 0.35rem; }
.nafs-tri-plot { width: 100%; height: auto; display: block; overflow: visible; }
.nafs-tri-tile rect { fill: var(--paper); stroke: var(--line); stroke-width: 1.5; }
.nafs-tri-nafs rect { stroke: #A8582F; stroke-width: 2.5; }
.nafs-tri-marks rect { stroke: #6B5B95; stroke-width: 2.5; }
.nafs-tri-map rect { stroke: #2F6D86; stroke-width: 2.5; }
.nafs-tri-tile.is-absent rect { fill: var(--wash); stroke: #9BA3A3; stroke-width: 1.5; stroke-dasharray: 6 4; }
.nafs-tri-edge { stroke: #9BA3A3; stroke-width: 1.5; stroke-dasharray: 5 5; }
.nafs-tri-edge.is-absent { stroke: var(--line); stroke-dasharray: 2 6; }
/* The four line roles charts.triangulation_tiles writes. */
.nafs-tri-instrument { font-size: 13.5px; font-weight: 650; fill: var(--brand-deep); }
.nafs-tri-share { font-size: 17px; font-weight: 700; fill: var(--ink); font-variant-numeric: tabular-nums; }
.nafs-tri-detail { font-size: 12px; fill: var(--muted); }
.nafs-tri-reason { font-size: 11.5px; fill: var(--muted); font-style: italic; }
.nafs-tri-tile.is-absent .nafs-tri-share { fill: var(--muted); font-weight: 600; }
.nafs-tri-note { margin: 0.4rem 0 0; max-width: 84ch; }
.nafs-tri-corners { margin: 0.4rem 0 0; padding-inline-start: 1.1rem; font-size: 0.82rem; }
.nafs-tri-corners li { margin: 0 0 0.35rem; }
.nafs-mix { color: var(--muted); }
.nafs-dash-head { margin: 1.25rem 0 0.4rem; color: var(--brand-deep); font-size: 1rem; }

/* About: one closed fold at the foot, its h2 in the summary so the rail's chip
   lands on something visible (DR-452). */
.nafs-about { margin: 2rem 0 0; }
.nafs-about > summary > h2 { display: inline; margin: 0; }
.nafs-about p { max-width: 84ch; }
.nafs-about h3 { margin: 1.25rem 0 0.5rem; color: var(--brand-deep); font-size: 1rem; }
.nafs-rules dt { font-weight: 600; margin-top: 0.7rem; }
.nafs-rules dd { margin: 0.2rem 0 0; font-size: 0.85rem; color: var(--muted); max-width: 84ch; }
.nafs-sources li { font-size: 0.85rem; max-width: 92ch; }
.nafs-check { display: block; margin-top: 0.25rem; color: #7a5a10; }

@media (max-width: 768px) {
  /* One triangle a row on a phone: two would print its words at half size. */
  .nafs-tri-grid { grid-template-columns: minmax(0, 1fr); }
}

@media print {
  /* pages.js opens every fold for print; a triangle stays on one page. */
  .nafs-tri { break-inside: avoid; }
}

/* ── The Dashboard's tests side by side (DR-531, DR-533) ────────────────────
   charts.yardstick_board (analytics/_yardstick_board.html) and
   charts.status_growth_map (drawn by analytics/_chart.html). Ink and grey
   only: the dot, the range and the yardstick are told apart by shape, and no
   verdict wears a colour — "in line" is unremarkable, not virtuous (rule 12j).
   Nothing here is written as a band token (--wae, --wwe, --wte, --mark-*) or
   as a cmp-* role, and every colour is a page token, never a hex, so a sheet
   that re-values the tokens carries these along (tests/test_charts_triangulation.py
   reads the colour the cascade gives each role, and under a re-valued :root).
   They are rooted at .plot like every chart rule, and sit after
   ".plot .dot" and ".plot .line", whose stroke they replace on order. ─────── */
.yard-plot .yard-stripe { fill: var(--wash); }
.yard-plot .yard-row-label { font-size: 11px; fill: var(--ink); }
.yard-plot .yard-col-title { font-size: 11.5px; font-weight: 600; fill: var(--ink); }
.yard-plot .yard-col-sub { font-size: 10px; fill: var(--muted); }
.yard-plot .yard-reason { font-size: 10px; fill: var(--ink); }
.yard-plot .yard-absent rect { fill: none; stroke: var(--muted); stroke-width: 1; stroke-dasharray: 4 3; }
.plot .dot-yard-share { fill: var(--ink); stroke: var(--paper); stroke-width: 1.5; }
.plot .dot-yard-share-shown { fill: var(--paper); stroke: var(--ink); stroke-width: 1.5; }
.plot .line-yard-range { stroke: var(--muted); stroke-width: 4; stroke-linecap: round; }
.plot .line-yard-line { stroke: var(--ink); stroke-width: 2; stroke-linecap: butt; }
.plot .line-yard-span { stroke: var(--line); stroke-width: 1.5; }
/* A cell's label is its share and its verdict word in one line ("30.3% below"), or the
   share alone where nothing was judged ("85.3%"). All ink: on the striped rows grey
   reads 4.25:1, under the 4.5:1 text needs. Above and below are told from in line by
   weight, the whole label, never by a fainter colour. */
.plot .mark-label-yard-verdict-above,
.plot .mark-label-yard-verdict-below,
.plot .mark-label-yard-figure,
.plot .mark-label-yard-agree { font-size: 11px; fill: var(--ink); }
.plot .mark-label-yard-verdict-above,
.plot .mark-label-yard-verdict-below { font-weight: 600; }
.plot .mark-label-yard-verdict-in-line { font-size: 11px; fill: var(--ink); }
.legend .legend-yard-share i { background: var(--ink); width: 10px; height: 10px; border-radius: 50%; }
.legend .legend-yard-share-shown i { background: var(--paper); border: 1.5px solid var(--ink); width: 10px; height: 10px; border-radius: 50%; }
.legend .legend-yard-range i { background: var(--muted); height: 4px; }
.legend .legend-yard-line i { background: var(--ink); width: 2px; height: 12px; }
/* A board is 800 units wide and its type is set in those units, so on a phone it
   scrolls inside its own card rather than shrinking the words to nothing. 712px is
   the floor it was always drawn at: nine tenths of a pixel to a unit (640 of 720). */
.yard-board { overflow-x: auto; }
.yard-board .yard-plot { min-width: 712px; }

/* Where each grade stands (Working Above across, median growth up): one ink dot a
   grade, a ring round the ones on the Flagged list, the norm group's own share as
   a dashed vertical and typical growth as a dotted horizontal with its band
   shaded. Shape says which is which. */
.plot .dot-status-grade { fill: var(--ink); }
.plot .mark-label-status-grade { font-size: 10px; fill: var(--ink); }
.plot .line-status-flag { stroke: var(--ink); stroke-width: 1.5; fill: none; }
.plot .cut-status-norm { stroke: var(--muted); stroke-dasharray: 4 3; }
.plot .cut-status-typical { stroke: var(--muted); stroke-dasharray: 2 3; }
.plot .zone-status-typical { fill: var(--wash); }
.legend .legend-status-grade i { background: var(--ink); width: 10px; height: 10px; border-radius: 50%; }
.legend .legend-status-flag i { background: transparent; border: 1.5px solid var(--ink); width: 12px; height: 12px; border-radius: 50%; }

/* ── The Dashboard around the board (analytics/_wb_dashboard.html and the
   _dash_* partials). Layout only: every figure is the payload's, every colour a
   page token, and nothing here is a band or cmp-* role. "How to read this page"
   is an open fold whose summary is its h2, like NAFS's About, so the rail's
   chip lands on something visible; the Five years and NAFS as printed folds
   start closed. The growth maps and a cohort's stacks are drawn on one frame
   width and laid out as equal columns, so their words print the same size. ── */
.dash-guide { margin: 0 0 1.25rem; }
.dash-guide > summary { cursor: pointer; }
.dash-guide > summary > h2 { display: inline; margin: 0; }
.dash-guide-lines { margin: 0.6rem 0 0; padding-inline-start: 1.25rem; max-width: 84ch; }
.dash-guide-lines li { margin: 0 0 0.4rem; }
.dash-sub { margin: 1.25rem 0 0.4rem; color: var(--brand-deep); font-size: 1rem; }
.dash-fold { margin: 1rem 0; }
.dash-fold > summary > h3 { display: inline; margin: 0; font-size: 1rem; }
.dash-coverage td { white-space: nowrap; }
.dash-overlap { margin: 1rem 0 1.25rem; }
.dash-overlap-table td .dash-chance { display: block; }
.stat .note .dash-tile-note { display: block; margin-top: 0.2rem; }
.dash-cohorts, .dash-maps {
  display: grid; gap: 1rem 1.25rem; align-items: start;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
}
.dash-cohort, .dash-map { min-width: 0; }
.dash-cohort .dash-sub { margin-top: 0.5rem; }

/* Unit Grading Book — teacher.
   The grid, its Save bar, the room's book, Copy to ManageBac and the panels on
   the homeroom, class and student pages. Its own --ub-* tokens and never a band
   or cmp-* colour (DR-581): a unit total is a classroom mark against the
   breakdown, not a MAP band. The sticky Save bar is a <div> — `body > footer`
   is the site footer's rule. */
:root {
  --ub-ink: #2F3A40;
  --ub-line: #C9D1D3;
  --ub-wash: #F2F5F6;
  --ub-late: #8A5A00;
  --ub-late-bg: #FFF4DC;
  --ub-invalid: #9B2C2C;
  --ub-invalid-bg: #FDECEC;
  --ub-stale-bg: #E8F0FB;
  --ub-stale: #2B4C7E;
  --ub-typed-bg: #F3EEFB;
}
.ub-alert p { margin: 0.15rem 0; }
.ub-h-link { font-size: 0.8rem; font-weight: 500; margin-inline-start: 0.6rem; }
.ub-unit-h { font-size: 0.92rem; margin: 0.9rem 0 0.4rem; color: var(--ub-ink); }
.ub-unit-h:first-child { margin-top: 0; }
.ub-strips { list-style: none; margin: 0; padding: 0; }
.ub-strip { border-top: 1px solid var(--line); padding: 0.4rem 0; }
.ub-strip-line { margin: 0 0 0.2rem; font-size: 0.88rem; }
.ub-fold > summary { font-size: 0.82rem; }
.ub-state { font-size: 0.78rem; padding: 0.05rem 0.4rem; border: 1px solid var(--ub-line); border-radius: 999px; background: var(--ub-wash); color: var(--ub-ink); white-space: nowrap; }
.ub-state-complete_late, .ub-state-nothing_entered { border-style: dashed; }
.ub-yours { background: var(--ub-wash); border-color: var(--ub-line); color: var(--ub-ink); }
.ub-card-line { margin: 0.25rem 0 0; font-size: 0.82rem; color: var(--ub-ink); }
.ub-terms { margin: 0; padding-inline-start: 1.1rem; }
.ub-term { font-size: 0.86rem; margin: 0.4rem 0; }
.ub-warn { border-inline-start: 3px solid var(--ub-late); padding: 0.3rem 0.6rem; background: var(--ub-late-bg); color: var(--ub-ink); font-size: 0.85rem; }
.ub-reason, .ub-version { font-size: 0.85rem; }
.ub-live { min-height: 1.2em; margin: 0.4rem 0; font-size: 0.88rem; color: var(--ub-ink); }
.ub-live:empty { margin: 0; }
.ub-scroll { overflow-x: auto; }
.ub-grid { font-size: 0.86rem; }
.ub-grid th, .ub-grid td { padding: 0.3rem 0.4rem; vertical-align: top; }
.ub-grid thead th { text-transform: none; letter-spacing: 0; font-size: 0.74rem; color: var(--ub-ink); min-width: 6.5rem; }
.ub-grid .ub-name { position: sticky; inset-inline-start: 0; z-index: 1; background: var(--paper); min-width: 9rem; max-width: 14rem; text-transform: none; letter-spacing: 0; font-size: 0.84rem; color: var(--ink); box-shadow: 1px 0 0 var(--line); }
.ub-en, .ub-ar { display: block; font-weight: 600; }
.ub-ar { font-weight: 500; color: var(--muted); }
.ub-ar-h { font-size: 0.9em; color: var(--muted); font-weight: 500; }
.ub-meta { display: block; font-weight: 400; color: var(--muted); font-size: 0.72rem; text-transform: none; letter-spacing: 0; }
.ub-cell input.ub-v { width: 4.4rem; font: inherit; padding: 0.15rem 0.25rem; border: 1px solid var(--ub-line); border-radius: 4px; }
.ub-cell select.ub-k { display: block; width: 4.4rem; margin-top: 0.15rem; font: inherit; font-size: 0.72rem; border: 1px solid var(--ub-line); border-radius: 4px; background: var(--paper); }
.ub-cell input:focus, .ub-cell select:focus { outline: 2px solid var(--brand); outline-offset: 1px; }
.ub-cell.ub-late { background: var(--ub-late-bg); }
.ub-cell.ub-late::after { content: "late"; display: block; font-size: 0.65rem; color: var(--ub-late); }
.ub-cell.ub-outof input.ub-v { border-style: dashed; }
.ub-cell.ub-invalid, .ub-cell [aria-invalid="true"] { background: var(--ub-invalid-bg); border-color: var(--ub-invalid); }
.ub-cell.ub-stale { background: var(--ub-stale-bg); box-shadow: inset 0 0 0 2px var(--ub-stale); }
.ub-cell.ub-typed, .ub-cell.is-dirty { background: var(--ub-typed-bg); }
.ub-err { display: block; color: var(--ub-invalid); font-size: 0.75rem; font-weight: 500; }
/* The teacher's grid is one line a row: the number box and a narrow status
   menu side by side, the name on one line. Scoped to the grid's form, so the
   fold and the admin breakdown grid keep their own shape. Tab still goes number
   box to number box (the script takes the menus out of the tab order). A chosen
   status gives the empty number box's room to the menu, so its word shows; the
   column keeps its width. "late" is a corner tag, not a second line. A long
   name is cut with an ellipsis below 1200px and read in full in its title and
   in every control's label. */
.ub-form .ub-grid thead th { min-width: 0; }
.ub-form .ub-grid thead th.ub-col { min-width: 6rem; }
.ub-form .ub-grid tbody th, .ub-form .ub-grid tbody td { vertical-align: middle; padding-block: 0.2rem; }
.ub-form .ub-grid tbody th.ub-name { white-space: nowrap; }
.ub-form .ub-grid .ub-name a { display: inline-block; max-width: 13.2rem; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; }
.ub-form .ub-grid .ub-name .note { display: block; white-space: normal; font-size: 0.72rem; }
.ub-form .ub-grid td.ub-cell { position: relative; white-space: nowrap; }
.ub-form .ub-grid .ub-cell input.ub-v { width: 3.1rem; padding-block: 0.1rem; vertical-align: middle; -moz-appearance: textfield; appearance: textfield; }
.ub-form .ub-grid .ub-cell input.ub-v::-webkit-inner-spin-button,
.ub-form .ub-grid .ub-cell input.ub-v::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.ub-form .ub-grid .ub-cell select.ub-k { display: inline-block; width: 2.1rem; margin: 0 0 0 0.15rem; padding-block: 0.1rem; vertical-align: middle; }
.ub-form .ub-grid .ub-cell:has(select.ub-k option:checked:not([value=""])) input.ub-v { width: 1rem; }
.ub-form .ub-grid .ub-cell:has(select.ub-k option:checked:not([value=""])) select.ub-k { width: 4.2rem; }
.ub-form .ub-grid .ub-cell.ub-late::after { position: absolute; top: 0; inset-inline-end: 0.25rem; padding: 0 0.15rem; font-size: 0.56rem; line-height: 1.1; background: var(--ub-late-bg); border-radius: 0 0 3px 3px; }
@media (min-width: 1200px) {
  .ub-form .ub-grid .ub-name { max-width: 20rem; }
  .ub-form .ub-grid .ub-name a { max-width: 19.2rem; }
}
.ub-total, .ub-letter { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ub-letter { font-weight: 700; color: var(--ub-ink); }
.ub-status { color: var(--muted); font-size: 0.8rem; }
.ub-scaled { color: var(--ub-ink); margin-inline-start: 0.1rem; }
.ub-savebar { position: sticky; bottom: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.6rem; margin-top: 0.8rem; padding: 0.55rem 0.8rem; background: var(--paper); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06); }
.ub-savebar button { font: inherit; padding: 0.4rem 1.1rem; border: 0; border-radius: 6px; background: var(--brand); color: #fff; cursor: pointer; }
.ub-savebar button:hover { background: var(--brand-deep); }
.ub-savebar button:disabled { background: var(--ub-wash); color: var(--muted); border: 1px dashed var(--ub-line); cursor: not-allowed; }
.ub-saved { font-size: 0.85rem; color: var(--ub-ink); font-weight: 600; }
.ub-linklike { font: inherit; background: none; border: 0; padding: 0; color: var(--brand); text-decoration: underline; cursor: pointer; }
.ub-one-list { margin: 0; }
.ub-one-row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.4rem 1rem; padding: 0.4rem 0; border-bottom: 1px solid var(--line); }
.ub-one-row dt { flex: 1 1 14rem; }
.ub-one-row dd { margin: 0; }
.ub-one-total { font-size: 0.95rem; }
.ub-copy th[colspan] { text-transform: none; letter-spacing: 0; color: var(--ub-ink); }
.ub-student-table td { white-space: nowrap; }
@media (max-width: 640px) {
  .ub-grid .ub-name { min-width: 7rem; }
  .ub-cell input.ub-v, .ub-cell select.ub-k { width: 3.8rem; }
  .ub-form .ub-grid .ub-name a { max-width: 8rem; }
  .ub-form .ub-grid .ub-cell input.ub-v { width: 2.8rem; }
  .ub-form .ub-grid .ub-cell select.ub-k { width: 1.9rem; }
  .ub-form .ub-grid .ub-cell:has(select.ub-k option:checked:not([value=""])) select.ub-k { width: 3.7rem; }
}
@media print {
  .ub-savebar, .ub-live, .ub-paste-note { display: none; }
}
/* Unit Grading Book — admin ---------------------------------------------
   The coordinators' pages at /unit-grading-book/ (plan §H). Scoped under
   .ub-admin; the state chips wear their own --ub-* colours and never a band's
   or a cmp-* token (DR-581): a breakdown's state is not a judgement about a
   child. A disabled button is styled as disabled and prints its reason beside
   it — an unstyled :disabled reads as a broken button (the inspection lesson). */
.ub-admin {
  --ub-published: #1F5F8B;
  --ub-published-bg: rgba(31, 95, 139, 0.10);
  --ub-draft: #6A4C93;
  --ub-draft-bg: rgba(106, 76, 147, 0.10);
  --ub-unproven: #8A5A00;
  --ub-unproven-bg: rgba(138, 90, 0, 0.10);
  --ub-late: #8A5A00;
  --ub-quiet: var(--muted);
}
.ub-admin .ub-head h1 { margin-bottom: 0.25rem; }
.ub-admin .ub-bar { display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; align-items: center; justify-content: space-between; margin: 0.5rem 0 1rem; }
.ub-admin .ub-tabs { display: flex; flex-wrap: wrap; gap: 0.25rem; }
.ub-admin .ub-tabs a, .ub-admin .ub-units a {
  padding: 0.3rem 0.65rem; border: 1px solid var(--line); border-radius: 999px;
  background: var(--paper); color: var(--brand-deep); text-decoration: none; font-size: 0.85rem;
}
.ub-admin .ub-tabs a[aria-current="page"], .ub-admin .ub-units a[aria-current="page"] {
  background: var(--brand); border-color: var(--brand); color: #fff;
}
.ub-admin .ub-units { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.5rem; }
.ub-admin .ub-year { display: flex; gap: 0.4rem; align-items: flex-end; font-size: 0.8rem; color: var(--muted); }
.ub-admin .ub-year select, .ub-admin .ub-year button { font: inherit; padding: 0.25rem 0.5rem; }
.ub-admin .ub-table td, .ub-admin .ub-table th { vertical-align: top; }
.ub-admin .ub-grid td { min-width: 7rem; }
.ub-admin .ub-chip {
  display: inline-block; padding: 0.1rem 0.5rem; border-radius: 999px; font-size: 0.78rem;
  border: 1px solid var(--line); background: var(--wash); color: var(--ub-quiet);
  text-decoration: none; margin: 0.1rem 0.15rem 0.1rem 0; line-height: 1.35;
}
.ub-admin .ub-chip-published { color: var(--ub-published); background: var(--ub-published-bg); border-color: rgba(31, 95, 139, 0.35); }
.ub-admin .ub-chip-draft { color: var(--ub-draft); background: var(--ub-draft-bg); border-color: rgba(106, 76, 147, 0.35); }
.ub-admin .ub-chip-unproven { color: var(--ub-unproven); background: var(--ub-unproven-bg); border-color: rgba(138, 90, 0, 0.35); border-style: dashed; }
.ub-admin .ub-chip-superseded, .ub-admin .ub-chip-discarded { color: var(--ub-quiet); text-decoration: line-through; }
.ub-admin .ub-chip-late { color: var(--ub-late); background: var(--ub-unproven-bg); }
.ub-admin .ub-state-complete, .ub-admin .ub-state-complete_late { color: var(--ub-published); background: var(--ub-published-bg); }
.ub-admin .ub-state-in_progress { color: var(--ub-draft); background: var(--ub-draft-bg); }
.ub-admin .ub-state-nothing_entered { color: var(--ub-unproven); background: var(--ub-unproven-bg); }
.ub-admin .ub-empty { color: var(--muted); font-style: italic; }
.ub-admin .ub-drift, .ub-admin .ub-warn, .ub-admin .ub-choose { color: var(--ub-late); }
.ub-admin .ub-warn { border-inline-start: 3px solid var(--ub-late); padding: 0.4rem 0.7rem; background: var(--ub-unproven-bg); }
.ub-admin .ub-form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 0.6rem 1rem; align-items: end; }
.ub-admin .ub-form-grid label, .ub-admin .ub-notes { display: flex; flex-direction: column; gap: 0.2rem; font-size: 0.8rem; color: var(--muted); }
.ub-admin input[type="text"], .ub-admin input[type="number"], .ub-admin input[type="date"],
.ub-admin input[type="search"], .ub-admin select, .ub-admin textarea {
  font: inherit; padding: 0.3rem 0.45rem; border: 1px solid var(--line); border-radius: 4px; background: var(--paper); color: var(--ink);
}
.ub-admin .ub-narrow { width: 5.5rem; }
.ub-admin .ub-ticks { grid-column: 1 / -1; border: 1px dashed var(--line); border-radius: 8px; padding: 0.4rem 0.75rem; display: flex; flex-wrap: wrap; gap: 0.4rem 1.2rem; }
.ub-admin .ub-ticks legend { font-size: 0.75rem; color: var(--muted); }
.ub-admin .ub-ticks label { flex-direction: row; align-items: center; gap: 0.35rem; color: var(--ink); font-size: 0.85rem; }
.ub-admin .ub-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: center; margin: 0.6rem 0; }
.ub-admin .ub-inline { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: center; margin: 0.5rem 0; }
.ub-admin .ub-inline label { display: flex; gap: 0.35rem; align-items: center; font-size: 0.85rem; }
.ub-admin .ub-btn {
  font: inherit; padding: 0.4rem 0.9rem; border: 1px solid var(--brand); border-radius: 4px;
  background: var(--brand); color: #fff; cursor: pointer;
}
.ub-admin .ub-btn:hover { background: var(--brand-deep); }
.ub-admin .ub-btn-quiet { background: var(--paper); color: var(--brand-deep); }
.ub-admin .ub-btn-quiet:hover { background: var(--wash); }
.ub-admin .ub-btn:disabled, .ub-admin button:disabled, .ub-admin select:disabled {
  background: var(--wash); color: var(--muted); border-color: var(--line); border-style: dashed; cursor: not-allowed;
}
.ub-admin .ub-why { color: var(--ub-late); font-size: 0.85rem; }
.ub-admin .ub-proof { font-weight: 600; color: var(--ub-unproven); }
.ub-admin .ub-proof.is-proven { color: var(--ub-published); }
.ub-admin .ub-impact { margin: 0.4rem 0 0.8rem; padding-inline-start: 1.2rem; }
.ub-admin .ub-sentence { font-weight: 600; color: var(--brand-deep); }
.ub-admin .ub-fold { margin: 0.4rem 0; }
.ub-admin .ub-fold > summary { cursor: pointer; color: var(--brand-deep); }
.ub-admin .ub-recipient { display: flex; gap: 0.35rem; align-items: baseline; font-size: 0.88rem; }
.ub-admin .ub-recipient.is-derived { font-weight: 600; }
.ub-admin .ub-sticky { position: sticky; bottom: 0; background: var(--bg); padding: 0.5rem 0; border-top: 1px solid var(--line); }
.ub-admin .ub-delivery-captured, .ub-admin .ub-delivery-pending { color: var(--ub-draft); }
.ub-admin .ub-delivery-sent { color: var(--ub-published); }
.ub-admin .ub-delivery-failed, .ub-admin .ub-delivery-skipped { color: var(--ub-late); }
.ub-admin .ub-term-unit { display: flex; gap: 0.6rem; align-items: center; }
.ub-admin .ub-term-unit.is-taken { opacity: 0.6; }
.ub-admin .ub-areas li { margin: 0.25rem 0; }
.ub-admin .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.82rem; }
.ub-admin .ub-narrow-table { max-width: 22rem; }
.ub-admin .ub-pages { display: flex; gap: 1rem; margin: 0.75rem 0; }
.ub-admin .ub-readonly { border-inline-start: 3px solid var(--ub-published); padding: 0.4rem 0.7rem; background: var(--ub-published-bg); }
.ub-admin .ub-reason { margin: 0.3rem 0 0; font-size: 0.85rem; }
.ub-admin .ub-moves { margin: 0.25rem 0 0; padding-inline-start: 1.1rem; font-size: 0.85rem; }
.ub-admin .ub-subject-use { display: inline-flex; margin: 0.15rem 0 0; }
.ub-admin .ub-subject-use button { font: inherit; font-size: 0.75rem; padding: 0.05rem 0.45rem; border: 1px solid var(--line); border-radius: 4px; background: var(--paper); color: var(--brand-deep); cursor: pointer; }
.ub-admin .ub-subject-use button:hover { background: var(--wash); }
@media (max-width: 640px) {
  .ub-admin .ub-bar { flex-direction: column; align-items: flex-start; }
  .ub-admin .ub-form-grid { grid-template-columns: 1fr; }
}
/* ── Unit Grading Book — analysis (plan §K, 2026-10-09) ─────────────────────
   The Analysis tab /analysis/?view=unit_book. A unit total and its letter are
   marks, never a MAP band (DR-559), so nothing here borrows a band token
   (--wae/--wwe/--wte, --mark-*) or a cmp-* role: the book's states and its
   chart wear their own --ub-* roles, ink on a wash. "Complete" is not green and
   "nothing entered" is not red — a state is a fact about a room, and a grid of
   green is a league table (rule 12j's stance). Scoped to .is-unit-book, except
   the chart's .plot roles, which follow the NAFS rule: rooted at .plot so a
   fold fetched into the page draws the same. */
.is-unit-book {
  --ub-ink: var(--ink);
  --ub-wash: var(--wash);
  --ub-rule: var(--line);
  --ub-quiet: var(--muted);
  --ub-box: #5B6B8C;
  --ub-median: var(--ink);
}
.is-unit-book .ub-section { margin: 0 0 1.75rem; }
.is-unit-book .ub-section > h2 { margin: 1rem 0 0.4rem; }
.is-unit-book .ub-sub { margin: 1rem 0 0.35rem; font-size: 1rem; color: var(--brand-deep); }
.is-unit-book .ub-reach { max-width: 84ch; }
.is-unit-book .ub-unit-line { margin: 0 0 0.4rem; color: var(--ub-quiet); }
.is-unit-book .ub-units { flex-wrap: wrap; align-items: baseline; gap: 0.4rem 1rem; }
.is-unit-book .ub-unit-hidden { color: var(--ub-quiet); }
.is-unit-book .ub-table td.num,
.is-unit-book .ub-table th.num { text-align: end; white-space: nowrap; font-variant-numeric: tabular-nums; }
.is-unit-book .ub-table thead th { vertical-align: bottom; }
.is-unit-book .ub-state {
  display: inline-block; padding: 0.05rem 0.5rem; border: 1px solid var(--ub-rule);
  border-radius: 999px; background: var(--ub-wash); color: var(--ub-ink);
  font-size: 0.8rem; font-weight: 500; white-space: nowrap;
}
.is-unit-book .ub-state-nothing_entered { border-style: dashed; }
.is-unit-book .ub-state-complete_late { border-color: var(--ub-quiet); }
.is-unit-book .ub-bd { display: block; font-size: 0.82rem; color: var(--ub-ink); white-space: nowrap; }
.is-unit-book .ub-bd-none { color: var(--ub-quiet); }
.is-unit-book .ub-bd-unproven { font-weight: 600; }
.is-unit-book .ub-pair {
  display: inline-block; margin: 0 0.25rem 0.15rem 0; padding: 0 0.45rem;
  border: 1px solid var(--ub-rule); border-radius: 999px; background: var(--ub-wash);
  font-size: 0.8rem; white-space: nowrap;
}
.is-unit-book .ub-week-nav { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; align-items: baseline; margin: 0 0 0.5rem; }
.is-unit-book .ub-fold { margin: 0.6rem 0 0.25rem; }
.is-unit-book .ub-totals-chart { max-width: 48rem; }
.is-unit-book .ub-no-room li, .is-unit-book .ub-names li { padding: 0.2rem 0; }
.plot .bar-ub-box { fill: #5B6B8C; fill-opacity: 0.35; stroke: #5B6B8C; stroke-width: 1; }
.plot .cut-ub-median { stroke: var(--ink); stroke-width: 2.5; }
.plot .cut-ub-letter { stroke: var(--muted); stroke-dasharray: 2 3; }
