/* ClearPath Tutoring. One stylesheet, four surfaces, no build step, no framework.
 *
 * `DESIGN.md` is the reasoning. This file is the implementation and it holds to
 * one rule: nothing here is a literal. Every colour, size, space and radius comes
 * from a token defined once in :root, because the sheet this replaced had
 * thirty-one distinct colours and nine different values for "a small gap", which
 * is what happens without one.
 *
 * There is no inline style anywhere in any page, because the Content Security
 * Policy is "self and nothing else". That is not a style preference: this product
 * renders text a child typed on every single turn.
 *
 * The palette and the two typefaces are School Systems Lab's own, the ones its
 * site and the ClearPath marks already carry: ink, white paper, the school ink
 * blue and the highlighter. A second ClearPath look would be the defect.
 *
 * Every colour pair is measured, and `test/contrast.test.ts` reads THIS FILE and
 * fails if an edit breaks a ratio. Changing a value below without running the
 * suite does not quietly ship: it goes red.
 */

/* The two faces, self-hosted (fonts/OFL.txt): the same files the School
 * Systems Lab site serves, so the site and the software read as one thing. Both
 * are variable, one file each, Latin with every accent the seven languages
 * use. `swap`, so words show at once in the system face and the page never
 * waits on a font. */
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Schibsted Grotesk";
  src: url("/fonts/schibsted-grotesk-latin.woff2") format("woff2");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ---------------------------------------------------------------- colour */
  --ink: #121826;         /* body text                              17.73 / 16.38 */
  --muted: #566074;       /* secondary text                   6.32 / 5.84 / 5.42 */
  --page: #f4f6f9;        /* the page ground, cool gray */
  --card: #ffffff;        /* paper */
  --soft: #e9edfd;        /* the tutor's turn, soft fills, the row under the pointer */
  --line: #e3e7ee;        /* a divider between rows. NOT a control edge */

  --brand: #2a44d6;       /* school ink blue: the primary button, the child's own turn */
  --brand-ink: #ffffff;   /* anything written on --brand                     7.25 */
  --brand-press: #1d31a6; /* the primary button under the pointer           10.28 */
  --accent: #2a44d6;      /* links, the active state            7.25 / 6.70 / 6.21 */

  --night: #121826;       /* the staff band, the tour's backdrop */
  --night-ink: #ffffff;   /* anything written on --night                    17.73 */
  --night-muted: #aab4c8; /* secondary text on --night                       8.50 */

  /* The highlighter. A ground behind --mark-ink and a rule under a heading.
   * It is never text: 1.26:1 on paper. */
  --mark: #ffe45c;
  --mark-ink: #121826;    /*                                                13.93 */

  /* The visible EDGE OF A CONTROL, which is a different job from a divider and
   * so a different token. WCAG 2.1 AA 1.4.11 wants 3:1 for the boundary of
   * anything you can operate; --line is 1.24:1 against paper, which is a good
   * hairline and an invisible text box.  3.95 card / 3.65 page / 3.39 soft */
  --edge: #75809a;

  /* Two rings, and that is not decoration. No single colour clears 3:1 against
   * every ground this product puts a control on: the grounds run from --card to
   * the night band and the blue of a child's own turn. So the inner ring is the
   * deep blue, which carries the light grounds, and the halo is the
   * highlighter, which carries the band and the blue. They contrast with each
   * other at 9.39:1 so the indicator reads as a shape, and --offset keeps the
   * ring on the page ground rather than against the control's own 1px border. */
  --focus: #192a8d;       /* inner       11.95 card / 11.04 page; 9.39 from the halo */
  --focus-halo: #ffe45c;  /* outer       13.93 on the night band, 5.70 on --brand */

  --warn-bg: #fff4d6; --warn-line: #9a6b0f; --warn-ink: #73500c;   /* 4.68 / 6.65 */
  --stop-bg: #fdecea; --stop-line: #c2412f; --stop-ink: #8f2d20;   /* 5.13 / 7.16 */
  --done-bg: #e2f5ea; --done-ink: #14532d;                         /*        8.02 */

  --band: var(--night);
  --band-ink: var(--night-ink);
  --band-muted: var(--night-muted);

  /* ------------------------------------------------------------------ type */
  --display: "Schibsted Grotesk", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --ui: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  --t1: 0.75rem;   /* table heads, tags, ids */
  --t2: 0.875rem;  /* secondary and hint text */
  --t3: 0.95rem;   /* table cells */
  --t4: 1rem;      /* body. The density dial moves this one and the rest follow */
  --t5: 1.2rem;    /* h2 */
  --t6: 1.3rem;    /* h1 in the band */
  --t7: 1.9rem;    /* what a child is working on */
  --t8: clamp(2.1rem, 1.3rem + 3.2vw, 3.4rem);   /* the start screen's question */
  --leading: 1.6;
  --tight: -0.022em;   /* the display face's tracking, the site's own */

  /* --------------------------------------------------------------- spacing */
  --s1: 0.25rem; --s2: 0.5rem; --s3: 0.75rem; --s4: 1rem;
  --s5: 1.5rem;  --s6: 2rem;   --s7: 3rem;

  /* Three radii, on purpose: controls, cards, pills. Everything at one radius
   * is the tell. */
  --r-ctl: 10px; --r-card: 16px; --r-pill: 999px;

  /* Two depths. --lift is a card resting on the page; --lift-hi is the one
   * thing on a screen that is being used: the start card, the box a child
   * writes in while it has focus, a button under the pointer. */
  --lift: 0 1px 2px rgba(18, 24, 38, 0.05), 0 10px 28px -14px rgba(18, 24, 38, 0.16);
  --lift-hi: 0 2px 4px rgba(18, 24, 38, 0.06), 0 26px 56px -22px rgba(42, 68, 214, 0.34);
  --glow: 0 0 0 4px rgba(42, 68, 214, 0.16);

  /* Motion. --fast is a state change under the pointer; --rise is something
   * arriving. Everything stops under prefers-reduced-motion. */
  --fast: 140ms;
  --rise: 360ms;
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --offset: 3px;

  /* The two knobs the density dial owns. It moves spacing and type size and
   * NOTHING else: contrast and target size are the floor, not a preference. */
  --gap: var(--s4);
  --pad: var(--s4);
  --font: var(--t4);
}

:root[data-density="spacious"]    { --gap: var(--s5); --pad: var(--s5); --font: 1.15rem; --leading: 1.7; }
:root[data-density="comfortable"] { --gap: var(--s4); --pad: var(--s4); --font: 1.05rem; --leading: 1.65; }
:root[data-density="dense"]       { --gap: var(--s3); --pad: var(--s3); --font: 0.98rem; --leading: 1.5; }

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e8ecf6;                                       /* 14.66 / 16.01 */
    --muted: #9ea9bf;                                     /* 7.33 / 8.01 / 6.20 */
    --page: #0b1020; --card: #131a2c;
    --soft: #1c2748;                                      /* 12.39 with --ink */
    --line: #252f47;
    --brand: #4c66f0;                                     /* 4.70 with its ink, 3.69 on the card */
    --brand-press: #3a54e4;
    --accent: #a3b4ff;                                    /* 8.69 / 9.49 / 7.35 */
    --edge: #7682a0;                                      /* 4.51 / 4.93 / 3.82 */
    --focus: #ffe45c; --focus-halo: #192a8d;              /* swapped, same construction */
    --warn-bg: #33280f; --warn-line: #d3a23f; --warn-ink: #f3dca6;
    --stop-bg: #3a1a15; --stop-line: #e8806c; --stop-ink: #f7c9be;
    --done-bg: #14301f; --done-ink: #9fe0bd;
    --lift: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 28px -14px rgba(0, 0, 0, 0.6);
    --lift-hi: 0 2px 4px rgba(0, 0, 0, 0.4), 0 26px 56px -22px rgba(76, 102, 240, 0.5);
    --glow: 0 0 0 4px rgba(163, 180, 255, 0.22);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font: var(--font) / var(--leading) var(--ui);
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* A link is the colour the palette names for links. With no rule here every
 * link was the browser's own blue, which on the dark page is 1.86:1: the
 * support address on every page and the helpline numbers a child reaches
 * before signing in. The navigation band keeps its own colours below. */
a { color: var(--accent); text-underline-offset: 0.18em; }

/* A line of code may break anywhere. The spreadsheet's header line is one
 * word to a browser, and at 320 pixels it pushed the whole console sideways. */
code { overflow-wrap: anywhere; }

/* ------------------------------------------------------------------- band */
/* The single strongest "which product is this" signal, and the answer to the
 * first review question. Staff surfaces carry the product name because a staff
 * member is looking at the product; the student surface carries the tutor's name
 * because a child is meeting a tutor, not evaluating a purchase. */

header.bar {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3) clamp(var(--s4), 4vw, var(--s6));
  background: var(--band);
  color: var(--band-ink);
  border-bottom: 1px solid var(--band);
}

header.bar h1 {
  font: 700 var(--t6) / 1.2 var(--display);
  letter-spacing: var(--tight);
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-width: 0;
}

/* The ClearPath family mark, the blue tile with its highlighter rule. */
.brandmark { flex: 0 0 auto; width: 1.6em; height: 1.6em; }

/* The school's own crest on the student page, and its preview on the console.
 * Height-bound, width free, so a wide banner and a square badge both sit in
 * the band without pushing it about. */
.schoollogo { flex: 0 0 auto; height: 1.75em; width: auto; max-width: 8em; object-fit: contain; }

/* The console's preview of the student band: same type, same shape, the saved
 * colour and ink painted on by showBrand. It defaults to the standard paper so
 * a school that set nothing previews exactly what its students get. */
.brandstrip { display: flex; align-items: center; gap: var(--s3);
              background: var(--card); color: var(--ink);
              font: 700 var(--t4)/1.2 var(--display); letter-spacing: var(--tight);
              padding: var(--s3) var(--s4); border: 1px solid var(--line);
              border-radius: var(--r-ctl); margin: var(--s3) 0 0; max-width: 24rem; }
.brandmark path { fill: none; stroke: currentColor; stroke-width: 3.4;
                  stroke-linecap: round; stroke-linejoin: round; }
.brandmark circle { fill: currentColor; }

/* The heading takes the slack, so whatever follows it (a role, a sign-out
 * button, both, neither) sits right without a rule per combination. */
header.bar h1 { margin-right: auto; }
header.bar .who { color: var(--band-muted); font-size: var(--t2); }

/* Print-only paper provenance under the header. The screen never shows it: a
 * signed-in page already says who and where. The print block turns it on. */
.printline { display: none; }

/* The in-product help pointer (P-AVA9). Outside <main>, the same reason
 * #offline is: it is true of the whole page, not one card. It sits in main's
 * column, same width and padding, so its left edge is the cards' left edge. At
 * the header's padding it hung in the top corner of the student page, a long
 * way from the one card a child is looking at. `body > p` outranks `p.hint`. */
body > p.pagehelp { max-width: 44rem; margin-left: auto; margin-right: auto; margin-bottom: 0;
                    padding: var(--s3) clamp(var(--s4), 4vw, var(--s6)) 0; }
body:has(> main.wide) > p.pagehelp,
body:has(.studyrow > .workspace:not(.hidden)) > p.pagehelp { max-width: 76rem; }

/* The student page's row under the band, in main's column like the help
 * pointer: on the left the line that says the tutor is an AI, on the right
 * the page's own two switches. They wrap onto two rows on a phone. */
.pagetop { max-width: 44rem; margin: 0 auto; padding: var(--s4) clamp(var(--s4), 4vw, var(--s6)) 0;
           display: flex; flex-wrap: wrap; align-items: center; gap: var(--s1) var(--s4); }
body:has(.studyrow > .workspace:not(.hidden)) > .pagetop { max-width: 76rem; }
.pagetop p { margin: 0; }
.pagetop .pagetools { margin-left: auto; }
/* "Ava is an AI tutor." A child is told so before anything else on the page,
 * as plain words in a tag of their own with the brand's dot beside them, not
 * as grey small print. */
.aitag { display: inline-flex; align-items: center; gap: var(--s2);
         padding: var(--s1) var(--s3); border-radius: var(--r-pill);
         background: var(--card); border: 1px solid var(--line); box-shadow: var(--lift);
         color: var(--ink); font: 600 var(--t2) / 1.5 var(--ui); }
.aitag::before { content: "" / ""; flex: 0 0 auto; width: 0.55rem; height: 0.55rem; border-radius: 50%;
                 background: var(--brand);
                 box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 22%, transparent); }

/* Three staff surfaces that never linked to each other. An administrator on the
 * console had to type a URL to reach the queues. aria-current marks the page you
 * are on, and it is marked with a rule and a weight change as well as colour,
 * because colour alone is the single most common failure in school dashboards. */
nav.pages { display: flex; gap: var(--s4); flex-wrap: wrap; }
nav.pages a { color: var(--band-muted); text-decoration: none; font-size: var(--t3);
              font-weight: 600; padding: var(--s1) 0;
              border-bottom: 2px solid transparent; }
nav.pages a:hover { color: var(--band-ink); }
nav.pages a[aria-current="page"] { color: var(--band-ink); font-weight: 700;
                                   border-bottom-color: var(--mark); }

/* --------------------------------------------------------- small screens */
/* Slice 48 shipped tested at 1200 to 1440 and said so out loud. Driven at 375
 * on 2026-09-01: the header crushed its four pieces into one row until the
 * wordmark painted its glyphs over the navigation links. The bar wraps
 * instead: the wordmark, the navigation on a row of its own, then the session
 * controls, and nothing overlaps. (The tables needed nothing: every one already
 * sits in a .scroll wrapper, and this pass first blamed them after probing
 * the card instead of the wrapper. Check the probe before the write-up.)
 *
 * The session controls come after the navigation because they do in the page.
 * With only the navigation moved below them, Tab went through it and then back
 * up to Sign out on the row above (driven at 320, 375 and 700 pixels, P-AVA61
 * pull request 3). */
@media (max-width: 48rem) {
  header.bar { flex-wrap: wrap; }
  header.bar h1 { white-space: nowrap; }
  /* The operator console's title is the long one. Kept on one line, it pushed
   * the whole page sideways at 320 and 375 (P-AVA51, driven 2026-10-14). */
  body[data-surface="operator"] header.bar h1 { white-space: normal; }
  nav.pages { order: 4; flex-basis: 100%; }
  header.bar .who, header.bar #signout { order: 5; }
  header.bar .who { margin-left: auto; }
  /* Inline after its sentence on a wide screen; on a phone the wrap put the
   * button in the middle of the sentence, reading as part of it. */
  #finish { display: block; margin-top: var(--s3); }
}

/* A child gets the light end of the family: same mark, same type, paper
 * ground. A dark institutional bar over a seven year old's tutoring session is
 * the "feeling surveilled" failure the audience table warns about. */
body[data-surface="student"] {
  --band: var(--card);
  --band-ink: var(--ink);
  --band-muted: var(--muted);
}
body[data-surface="student"] header.bar { border-bottom-color: var(--line); }
/* The night band sits on a page nearly its own colour in dark mode, so it
 * keeps a hairline there. */
@media (prefers-color-scheme: dark) {
  header.bar { border-bottom-color: var(--line); }
}
/* ----------------------------------------------------------------- layout */

main { max-width: 44rem; margin: 0 auto; padding: var(--s5) clamp(var(--s4), 4vw, var(--s6)) var(--s7); }
main.wide { max-width: 76rem; }

h2 { font: 700 var(--t5) / 1.3 var(--display); letter-spacing: var(--tight); margin: 0 0 var(--s2); text-wrap: balance; }
h3 { font: 700 var(--t4) / 1.3 var(--display); letter-spacing: var(--tight); }
/* What a child chose to work on is the heading of their own session, and the
 * one thing on the screen the highlighter marks: their own words, swiped the
 * way a marker goes over a line in a notebook. "Working on" sits above it as a
 * small label. The swipe is a ground with its own ink, the same in both
 * colour schemes. */
h2:has(.subject) { font: 600 var(--t2) / 1.3 var(--ui); letter-spacing: 0; color: var(--muted); }
h2:has(.subject) > span:first-child { display: block; margin-bottom: var(--s1); }
h2 .subject { font: 700 var(--t7) / 1.35 var(--display); letter-spacing: var(--tight);
              color: var(--mark-ink); background: var(--mark);
              padding: 0.04em 0.3em; border-radius: 0.2em;
              -webkit-box-decoration-break: clone; box-decoration-break: clone; }
/* No topic drawn yet (a reload, before the lesson answers): no empty swipe. */
h2 .subject:empty { display: none; }
#chatCard > h2 { margin-bottom: var(--s4); padding-bottom: var(--s4); border-bottom: 1px solid var(--line); }
p.hint { color: var(--muted); font-size: var(--t2); margin: 0 0 var(--gap); max-width: 60ch; }

section.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: var(--pad);
  margin-bottom: var(--gap);
  box-shadow: var(--lift);
}

/* The cards on a working surface are a stack, and that is a decision made by
 * looking at one rather than by taste.
 *
 * The first attempt was `repeat(auto-fit, minmax(26rem, 1fr))`, on the argument
 * that a registrar should not pay a scroll per panel. On the real console seven
 * of the eight panels carry a table and take the full width back, so the eighth
 * sat alone in column one and left a 973px hole of empty page beside it. A grid
 * that produces one column and a void is worse than the stack it replaced.
 *
 * THE CLASS IS ON THE WRAPPER AND NOT ON `main`. Every one of these pages holds
 * its cards inside one container (#queues, #console, #report) that the sign-in
 * card sits outside of. The grid was on `main` first, which looked right in the
 * stylesheet and did nothing at all on the page: `main` had two columns and one
 * child. Only opening it found that.
 *
 * Density comes from the type scale, the spacing scale and a sticky table head,
 * not from columns. `.hidden` still wins, because it is display: none
 * !important. */
.panels { display: flex; flex-direction: column; gap: var(--gap); }
.panels > section.card { margin-bottom: 0; }

/* ------------------------------------------------------------- primitives */

label { display: block; font-weight: 600; font-size: var(--t2); margin-bottom: var(--s1); }

input, select, textarea, button {
  font: inherit;
  color: inherit;
  border-radius: var(--r-ctl);
  border: 1px solid var(--edge);
  padding: var(--s2) var(--s3);
  background: var(--card);
  max-width: 100%;
}

input, select, textarea { width: 100%; transition: border-color var(--fast) ease, box-shadow var(--fast) ease; }
input:hover, select:hover, textarea:hover { border-color: var(--accent); }
textarea { resize: vertical; min-height: 3.2rem; }

/* A checkbox is not a text field. Without this the rule above stretches it
 * the full width of its card, padded and bordered like a text box, with the
 * native glyph lost somewhere inside (P-AVA41, ACCESSIBILITY.md). 1.5rem is
 * var(--s5), which is also the 24px WCAG 2.5.8 target-size floor; it does not
 * move with the density dial, the same rule that pins button to 44px. */
input[type="checkbox"] {
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  margin: 0 var(--s2) 0 0;
  flex: 0 0 auto;
}
/* A tick and a choice in the brand's own blue, where the browser draws them. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--brand); }
select { appearance: none; padding-right: var(--s6);
         background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                           linear-gradient(135deg, currentColor 50%, transparent 50%);
         background-position: right 1.15rem center, right 0.85rem center;
         background-size: 0.32rem 0.32rem, 0.32rem 0.32rem;
         background-repeat: no-repeat; }

/* 44px is the smallest thing a seven year old reliably hits, and it is the WCAG
 * 2.2 target size line as well. It does not move with the density dial. */
button {
  min-height: 44px;
  padding: var(--s2) var(--s5);
  background: var(--brand);
  color: var(--brand-ink);
  border-color: var(--brand);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--fast) ease, border-color var(--fast) ease,
              transform var(--fast) var(--ease), box-shadow var(--fast) ease;
}
/* A button answers the pointer: it lifts a pixel, and settles when pressed.
 * The lift's shadow never takes the place of the focus ring's halo, which is
 * a shadow too: on the night band the halo is the ring that shows. And the
 * movement itself is only for someone who has not asked for less of it.
 * The edge stays the brand's: the pressed fill alone is under 3:1 on a dark
 * card, and the edge is what keeps the button's shape. */
button:hover { background: var(--brand-press); border-color: var(--brand); }
button:hover:not(:focus-visible) { box-shadow: var(--lift); }
button:active:not(:focus-visible) { box-shadow: none; }
@media (prefers-reduced-motion: no-preference) {
  button:hover { transform: translateY(-1px); }
  button:active { transform: translateY(0) scale(0.985); }
  button[disabled]:hover, button.helplink:hover, button.flashcard:hover { transform: none; }
  ul.alerts li.alert:hover { transform: translateY(-1px); }
  #open button[type="submit"]:hover::after { transform: translateX(0.2em); }
}
button.quiet { background: var(--card); color: var(--ink); border-color: var(--edge); }
button.quiet:hover { background: var(--soft); border-color: var(--accent); }
/* --card, not a fixed near-white: on dark mode's lighter red that was 2.68:1,
 * on the button that erases a child. */
button.danger { background: var(--stop-line); border-color: var(--stop-line); color: var(--card); }
button.danger:hover { background: var(--stop-ink); border-color: var(--stop-ink); }
button[disabled] { opacity: 0.55; cursor: not-allowed; }
button[disabled]:hover { background: var(--brand); border-color: var(--brand); box-shadow: none; }
button.quiet[disabled]:hover { background: var(--card); border-color: var(--edge); }

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: var(--offset);
  box-shadow: 0 0 0 calc(3px + var(--offset) + 3px) var(--focus-halo);
}

.row { display: flex; gap: var(--s3); flex-wrap: wrap; align-items: flex-end; }
.row > * { flex: 1 1 12rem; }
.row > button { flex: 0 0 auto; }
/* A field holding a picker starts at its longest choice's width, so "Teacher,
 * sees only their own students" or "Use the school's country" shows whole, and
 * the field takes its own line when the row has no room. min-width 0 keeps a
 * phone narrower than the longest choice from scrolling sideways. */
.row > div:has(> select) { flex: 1 1 max-content; min-width: 0; }
/* A picker in a table row starts at 16rem, so a student's "Use the school's
 * country" shows whole and the staff table still fits a desktop panel. Sized
 * to its content instead, the staff role picker (choices that are whole
 * sentences) pushed that table into a sideways scroll at 1440 wide. */
td select { min-width: 16rem; }
/* A button in a table row says its action on one line ("Move up a grade"),
 * never broken across two; the table scrolls inside its panel if it must. */
td button { white-space: nowrap; }
/* What a child is working on is the long answer and the class is one word, so
 * the form reads down: the class, then the topic box at full width, then
 * Start. Side by side, the example was cut off in every language but English. */
#open .row > #courseField { flex: 1 1 100%; min-width: 0; }
#open #courseField select { max-width: 20rem; }
#open .row > div:has(> #subject) { flex: 1 1 100%; }

/* P-AVA61. The sign-in card's language switcher, on one line and as wide as
 * its longest name rather than the card. */
.langpick { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; margin: 0 0 var(--s3); }
.langpick label { margin: 0; }
.langpick select { width: auto; }
/* 0120. A checkbox with its label beside it rather than under it, and a
 * weekday's hours as one unboxed group on the console. */
.checkline { display: flex; align-items: center; margin: 0; }
.checkline label { margin: 0; }
fieldset.hoursday { border: 0; padding: 0; margin: 0 0 var(--s3); min-width: 0; }
fieldset.hoursday legend { font-weight: 600; padding: 0; }
/* P-AVA72. Listen sits on its own line under the reply it reads, and so does
 * a graph's button, which sits among the reply's words. */
.turn > button, .turn > .words > button { display: flex; margin-top: var(--s2); }
/* The student page's help panel: each line, then the number large enough to
 * read across a room and to tap. */
.helplines { list-style: none; padding: 0; margin: var(--s3) 0; }
.helplines li { margin: 0 0 var(--s3); }
.helplines a { font-weight: 700; font-size: var(--t5); white-space: nowrap; }
/* P-AVA64. The small link that opens it, at the foot of the page. A <button>
 * for the keyboard and the screen reader, drawn as quiet link text, and still
 * the 44px every button keeps: small to look at, not small to hit. */
button.helplink { background: none; border: 0; padding: 0 var(--s1); width: auto;
  display: inline-flex; align-items: center;
  color: var(--muted); font-size: var(--t2); font-weight: 400; text-decoration: underline;
  cursor: pointer; margin: var(--s4) 0 var(--gap); }
button.helplink:hover { background: none; color: var(--accent); }
button.helplink:hover:not(:focus-visible) { box-shadow: none; }
/* An intro line and the action that goes with it, on one row. These were a
 * <button> sitting inside the <p>, which put it on the baseline of whichever
 * line the text happened to wrap onto. */
.toolbar { display: flex; gap: var(--s4); flex-wrap: wrap; align-items: center;
           margin-bottom: var(--s4); }
/* The words take the room and wrap inside it; the action keeps to the right
 * on their row. */
.toolbar > p { margin: 0; flex: 1 1 16rem; }
.toolbar > button { flex: 0 0 auto; margin-left: auto; }
.toolbar .count { margin-left: auto; }

/* P-AVA61. The username sign-in sits under the provider buttons and the paste
 * fields, set apart by its own heading. */
#passwordSignIn, #passwordChange { margin-top: var(--s5); }
/* Signing in is one narrow card in the middle of the page, on every surface:
 * the fields one under another at full width, and the button the width of the
 * fields, so there is one obvious thing to do next. */
#signinCard { max-width: 30rem; margin-left: auto; margin-right: auto;
              padding: clamp(var(--s5), 5vw, var(--s6)); box-shadow: var(--lift-hi); }
#signinCard h2 { font-size: var(--t7); letter-spacing: -0.03em; margin-bottom: var(--s3); }
#signinCard .row { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
#signinCard .row > * { flex: 0 0 auto; }
#signinCard .row > button, #signinCard form > button { width: 100%; min-height: 3rem; }
#signinCard input { min-height: 3rem; }
/* The one time a temporary password is shown. Label beside value, and the
 * value large and in the monospace face, because it is read aloud or copied
 * onto paper letter by letter and an l that looks like a 1 costs a reset. */
.issued dl { display: grid; grid-template-columns: max-content 1fr; gap: var(--s2) var(--s4);
             margin: var(--s4) 0; }
.issued dt { color: var(--muted); }
/* The address may break anywhere, because it has nowhere else to. */
.issued dd { margin: 0; overflow-wrap: anywhere; }
.issued dd.id { font-family: var(--mono); font-size: var(--t4); letter-spacing: 0.08em; }
/* On a phone, label above value. Side by side, the address ran 270 pixels past
 * a 320 pixel screen, and the column left beside "Temporary password" would
 * have split the password itself across two lines. */
@media (max-width: 30rem) {
  .issued dl { grid-template-columns: 1fr; row-gap: 0; }
  .issued dd { margin-bottom: var(--s3); }
}
/* A username asked for inside a table row. */
form.inline { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }

.hidden { display: none !important; }

/* Held back until drawn. The four pages ship in English, knowing no school
 * and no sign-in door, and ava.js redraws them a moment later in the visitor's
 * language, with the right buttons or the signed-in page. Found on staging,
 * 2026-10-01, from Pakistan to us-east-1: that moment was one to five seconds
 * of English and of paste fields that read like an older site. So the pages
 * ship as `<html class="drawing">`, everything but the band waits, and ava.js
 * lifts the class once it has drawn (pageDrawn). If the script never runs, the
 * page is let out after four seconds anyway, as shipped: the delay, not the
 * duration, so the reduced-motion rule below leaves it alone. */
html.drawing body > :not(header) { visibility: hidden; animation: ava-drawn 0s 4s forwards; }
@keyframes ava-drawn {
  to { visibility: visible; }
}

/* ------------------------------------------------------------------- chat */

/* The log scrolls, not the page. While the page was the scrolling element,
 * showing the newest message pushed the box a child types into below the fold:
 * they read the answer and then had to hunt for where to reply. */
#log {
  display: flex;
  flex-direction: column;
  gap: var(--gap);
  margin-bottom: var(--gap);
  max-height: 55vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.turn {
  padding: var(--s3) var(--pad);
  border-radius: var(--r-card);
  border: 1px solid transparent;
  background: var(--soft);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-width: 88%;
}

.turn.me { background: var(--brand); color: var(--brand-ink); align-self: flex-end; }
/* A link inside the child's own turn is drawn in the turn's ink: the link blue
 * on the brand blue is the same colour twice. */
.turn.me a { color: inherit; }
/* Who said it, in the display face and the person's own capitals ("Ava",
 * "You"). Tiny tracked capitals over every bubble is the stock chat template. */
.turn .said { display: block; font: 700 var(--t2) / 1.3 var(--display); letter-spacing: var(--tight);
              margin-bottom: var(--s1); }
.turn.thinking { opacity: 0.7; font-style: italic; }
/* The words sit in a place of their own (WCAG 3.1.2), and the dots after them
 * stay on their line, as they did beside the bare words. */
.turn.thinking .words { display: inline; }
/* Small, not faded: on the child's own blue a faded line falls under 4.5:1. */
.turn .withpicture { display: block; font-size: var(--t2); margin-top: var(--s1); }

#transcriptLog { display: flex; flex-direction: column; gap: var(--s3); margin-top: var(--s3); }

/* The textarea IS the flex item. It used to sit inside a wrapper carrying its
 * label, which made the wrapper the flex item with no rule telling it to grow:
 * the box squashed to two words wide the first time anybody looked at it. */
/* The box a child writes in and its Send are one object: a single framed field
 * with Send inside it, the way every chat a child already uses looks. The frame
 * carries the focus ring while the box has focus, so the ring still wraps the
 * thing being typed into. */
#composer { display: flex; gap: var(--s2); align-items: flex-end;
            border: 1px solid var(--edge); border-radius: var(--r-card);
            background: var(--card); padding: var(--s2);
            transition: border-color var(--fast) ease, box-shadow var(--fast) ease; }
#composer:hover { border-color: var(--accent); }
/* Being written in, it is the one thing on the screen in use: it lifts. */
#composer:focus-within { border-color: var(--accent); box-shadow: var(--lift-hi); }
#composer textarea:hover { border-color: transparent; }
#composer textarea { flex: 1 1 auto; min-width: 0; border: 0; background: transparent;
                     padding: var(--s2); min-height: 3.6rem; max-height: 12rem;
                     resize: vertical; overflow-y: auto; }
/* The ring moves to the frame only where :has() can put it there; elsewhere
 * the box keeps its own. The grip goes only where the box grows by itself. */
@supports selector(:has(*)) {
  #composer textarea:focus-visible { outline: none; box-shadow: none; }
  #composer:has(textarea:focus-visible) { outline: 3px solid var(--focus); outline-offset: var(--offset);
                                          box-shadow: 0 0 0 calc(3px + var(--offset) + 3px) var(--focus-halo); }
}
@supports (field-sizing: content) {
  #composer textarea { resize: none; field-sizing: content; }
}
#composer button[type="submit"] { border-radius: var(--r-ctl); }
/* The line under the box ("your teachers can read your chats") sat flush
 * against its bottom edge. */
#teachersCanRead { margin-top: var(--s2); }
/* On a phone the box beside Send was a third of the width and cut its own
 * placeholder off mid-word. The box takes the line, three lines tall so a
 * longer language's placeholder still shows whole; Send goes under it, right. */
@media (max-width: 30rem) {
  #composer { flex-wrap: wrap; }
  #composer textarea { flex-basis: 100%; min-height: 5.2rem; }
  #composer button { margin-left: auto; }
}

/* The tutor's mark: her first letter in the display face, on the brand blue,
 * beside each of her turns. It gives the conversation a someone without a
 * mascot. Decorative to a screen reader, which already hears her name. The
 * measured pair, --brand-ink on --brand, with a soft light from the top left
 * that stops short of the letter. */
body[data-surface="student"] #log .turn[data-initial] { position: relative; margin-left: 2.9rem; }
body[data-surface="student"] #log .turn[data-initial]::before {
  content: attr(data-initial) / "";
  position: absolute; left: -2.9rem; top: 0;
  width: 2.2rem; height: 2.2rem; border-radius: var(--r-pill);
  background: radial-gradient(circle at 28% 22%, rgba(255, 255, 255, 0.3), transparent 46%) var(--brand);
  color: var(--brand-ink);
  box-shadow: 0 0 0 4px var(--soft);
  font: 700 1.05rem / 2.2rem var(--display); text-align: center; }

/* ---------------------------------------------------------- the start screen */
/* The page's first words to a child, at the size of a poster: the tutor's
 * mark, the question, a marker stroke under it, then the one box to fill in.
 * This is the screen a school sees first in a demo, so it is the one card
 * that floats. */
body[data-surface="student"] #openCard { padding: clamp(var(--s5), 4vw, var(--s6)); box-shadow: var(--lift-hi); }
#openCard h2 { font: 700 var(--t8) / 1.08 var(--display); letter-spacing: -0.03em; margin: 0; }
/* The marker stroke: the highlighter, drawn once under the question as it
 * arrives. A rule, not a ground, so no words sit on it. */
#openCard h2::after { content: "" / ""; display: block; width: 4.5rem; height: 0.5rem;
                      margin: var(--s4) 0 var(--s4); border-radius: var(--r-pill);
                      background: var(--mark); transform-origin: left center;
                      animation: ava-stroke 520ms var(--ease) 160ms backwards; }
@keyframes ava-stroke { from { transform: scaleX(0); } }
#openCard #cap { font-size: var(--t3); }
#open label { font-size: var(--t3); }
/* The box and Start are the same height, and big enough to be the point. */
#open input, #open select, #open button[type="submit"] { min-height: 3.25rem; }
/* The example keeps the body size, so the longest language's still fits. */
#open #subject { padding-left: var(--s4); }
#open button[type="submit"] { padding-left: var(--s6); padding-right: var(--s5); font-size: 1.05em; }
/* Start goes somewhere, and says so: an arrow after its word, which leans in
 * under the pointer. Decorative; the word is the name. */
#open button[type="submit"]::after { content: "\2192" / ""; display: inline-block; margin-left: var(--s2);
                                     transition: transform var(--fast) var(--ease); }

/* The tutor's mark at full size, on the start card and above the sign-in: the
 * name's first letter, drawn from the attribute applyTutorName keeps. It
 * breathes: two rings that open and close slowly, the page's sign that
 * somebody is here. Decorative; a screen reader hears the name in the band. */
.orb::after { content: attr(data-tutor-initial) / ""; }
.orb { position: relative; isolation: isolate; display: grid; place-items: center;
       width: 4.25rem; height: 4.25rem; margin: var(--s3) 0 var(--s6) var(--s3);
       border-radius: 50%; color: var(--brand-ink);
       background: radial-gradient(circle at 28% 22%, rgba(255, 255, 255, 0.3), transparent 46%) var(--brand);
       font: 700 1.9rem / 1 var(--display); user-select: none;
       box-shadow: 0 0 0 0.45rem color-mix(in srgb, var(--brand) 14%, transparent),
                   0 0 0 1rem color-mix(in srgb, var(--brand) 6%, transparent);
       animation: ava-orb 5s ease-in-out infinite; }
@keyframes ava-orb {
  50% { box-shadow: 0 0 0 0.7rem color-mix(in srgb, var(--brand) 10%, transparent),
                    0 0 0 1.5rem color-mix(in srgb, var(--brand) 3%, transparent); }
}
/* And a light that turns behind it: the blue running into the highlighter and
 * back, blurred to a glow. Behind the mark, so the letter's own ground is
 * never anything but --brand. */
.orb::before { content: "" / ""; position: absolute; inset: -0.4rem; z-index: -1; border-radius: 50%;
               background: conic-gradient(from 0deg, var(--brand), var(--accent) 30%, var(--mark) 52%, var(--brand) 78%, var(--brand));
               filter: blur(0.55rem); opacity: 0.75;
               animation: ava-turn 9s linear infinite; }
@keyframes ava-turn { to { transform: rotate(1turn); } }

/* ------------------------------------------------------- the page behind it */
/* The student page's ground: two soft lights, the blue from the top left and
 * the highlighter from the top right, over a field of ruled dots that fades
 * out down the page, like the top of a sheet of graph paper. It sits behind
 * everything, moves slowly, and is still for anyone who asked for less
 * motion. Paper and forced colours get the plain ground.
 *
 * Words sit on this ground too (the row under the band, the line at the foot
 * of the page), so it is measured: each light at its strongest still leaves
 * the muted ink and a link 4.5:1, and the dots are drawn toward the paper,
 * white in light and black in dark, never toward the ink, so a dot under a
 * letter only adds to its contrast. */
body[data-surface="student"]::before {
  content: "" / ""; position: fixed; inset: -12vmax; z-index: -1; pointer-events: none;
  background:
    radial-gradient(42vmax 30vmax at 18% 14%, color-mix(in srgb, var(--brand) 15%, transparent), transparent 72%),
    radial-gradient(34vmax 24vmax at 84% 12%, color-mix(in srgb, var(--mark) 34%, transparent), transparent 72%);
  animation: ava-drift 26s ease-in-out infinite alternate; }
@keyframes ava-drift { to { transform: translate3d(3vmax, 2vmax, 0) scale(1.06); } }
body[data-surface="student"]::after {
  content: "" / ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: radial-gradient(circle, color-mix(in srgb, #ffffff 90%, transparent) 1.25px, transparent 1.75px) 0 0 / 1.5rem 1.5rem;
  mask-image: linear-gradient(to bottom, #000, transparent 62%); }
@media (prefers-color-scheme: dark) {
  body[data-surface="student"]::after {
    background: radial-gradient(circle, color-mix(in srgb, #000000 70%, transparent) 1.25px, transparent 1.75px) 0 0 / 1.5rem 1.5rem; }
  body[data-surface="student"]::before {
    background:
      radial-gradient(42vmax 30vmax at 18% 14%, color-mix(in srgb, var(--brand) 30%, transparent), transparent 72%),
      radial-gradient(34vmax 24vmax at 84% 12%, color-mix(in srgb, var(--mark) 9%, transparent), transparent 72%); }
}

/* ------------------------------------------------------------ what arrives */
/* A card that is shown, and a turn that is said, rise into place once. The
 * same move for both, short enough to finish before a child starts reading.
 * Nothing is held once it has arrived: a transform kept on a card would make
 * the card the frame its tooltips are placed in, not the window. */
@keyframes ava-rise { from { opacity: 0; transform: translateY(0.6rem); } }
section.card, .workspace { animation: ava-rise var(--rise) var(--ease); }
#log .turn { animation: ava-rise var(--rise) var(--ease); }

/* The three tools beside the conversation (a picture, the writing space, the
 * study tools) were three different shapes and sizes. Now one: the same
 * height, edge, weight and a line icon each, so they read as a set. The icon
 * is a mask on the text colour, so it follows dark mode and forced colours. */
.pick, #writeOpen, #studyToggle { display: inline-flex; align-items: center; gap: var(--s2);
  min-height: 44px; padding: var(--s1) var(--s4); font-size: var(--t3); font-weight: 600;
  border-radius: var(--r-ctl); transition: background var(--fast) ease, border-color var(--fast) ease; }
.pick::before, #writeOpen::before, #studyToggle::before {
  content: "" / ""; flex: 0 0 auto; width: 1.15em; height: 1.15em;
  background: currentColor; mask: var(--icon) center / contain no-repeat; }
.pick { --icon: url("/icons/picture.svg"); }
#writeOpen { --icon: url("/icons/pencil.svg"); }
#studyToggle { --icon: url("/icons/cards.svg"); }

.attachrow { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
             margin-bottom: var(--s2); }
/* A label standing in for the file input, which is hidden by .sr-only rather
 * than display:none so it keeps its place in the tab order. */
.pick { border: 1px solid var(--edge); cursor: pointer; background: var(--card); }
.pick:hover { background: var(--soft); border-color: var(--accent); }
.sr-only:focus-visible + .pick { outline: 3px solid var(--focus); outline-offset: var(--offset);
                                 box-shadow: 0 0 0 9px var(--focus-halo); }
/* The pictures waiting to go, in order, each with its own Remove. They wrap
 * rather than push a narrow screen sideways. */
.thumbs { display: flex; flex-wrap: wrap; gap: var(--s2); list-style: none; margin: 0; padding: 0; }
.thumbs li { display: flex; align-items: center; gap: var(--s2); }
.thumbs img { max-height: 3rem; max-width: 6rem; border: 1px solid var(--line); border-radius: var(--r-ctl); }

/* The study tools, a small menu beside the box, and the panel a set is drawn
 * on. The card is a button, so it keeps every button's 44px and focus ring;
 * flipping changes its words and its ground, and nothing moves. */
.studytools { margin-bottom: var(--s2); }
.studymenu { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
.studymenu p.hint { flex: 1 1 100%; margin: 0; }
.studypanel { border: 1px solid var(--edge); border-radius: var(--r-card); padding: var(--pad);
              margin-bottom: var(--gap); background: var(--card); }
.studypanel h3 { margin-top: 0; }
#studyClose { margin-top: var(--s3); }
.studymoves { display: flex; flex-wrap: wrap; gap: var(--s2); margin-bottom: var(--s3); }
button.flashcard { display: block; width: 100%; min-height: 8rem; margin-bottom: var(--s2);
                   background: var(--soft); color: var(--ink); border-color: var(--edge);
                   font-weight: 400; text-align: center; overflow-wrap: anywhere; }
button.flashcard:hover { background: var(--soft); border-color: var(--accent); }
button.flashcard.flipped { background: var(--card); }
.flashcard .side { display: block; font-size: var(--t2); color: var(--muted); margin-bottom: var(--s1); }
.flashcard .face { display: block; font-size: var(--t5); }
.quizq { font-size: var(--t4); font-weight: 600; }

/* ------------------------------------------------------- B1, the new look */
/* Her 8 Sep spec (SCOPE-OF-WORK 1.1b): bubbles, chips, help where the screen is
 * empty, a tour, logos. Every color below is a token pair already measured in
 * test/contrast.test.ts; nothing new is invented here. */

/* The conversation as bubbles. The tutor speaks from the left on the soft
 * blue, the child from the right in the brand blue, each with one squarer
 * corner on the side it speaks from. Staff transcripts use the same turns. */
.turn { align-self: flex-start; border-bottom-left-radius: 4px; max-width: min(88%, 38rem); }
.turn.me { border-bottom-left-radius: var(--r-card); border-bottom-right-radius: 4px; }
/* A place under each reply for its own buttons (another change adds Listen). */
.turn .actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); white-space: normal; }
.turn .actions:empty { display: none; }

/* While a reply is being checked and written: the words say it, and three dots
 * breathe beside them. Not streamed word by word: every reply is safety-checked
 * before a child sees any of it. The dots are a background, so a screen reader
 * hears only the words, and they stop for anyone who asked for less motion. */
#log .turn.thinking { animation: none; }
.turn.thinking::after { content: ""; display: inline-block; vertical-align: middle;
  width: 1.6em; height: 0.55em; margin-left: var(--s2);
  background: radial-gradient(circle, currentColor 0.16em, transparent 0.19em) 0 50% / 0.55em 0.55em repeat-x;
  animation: ava-breathe 1.4s ease-in-out infinite; }
@keyframes ava-breathe { 50% { opacity: 0.3; } }
/* Her mark breathes with the dots while she thinks: the same loop. */
body[data-surface="student"] #log .turn.thinking[data-initial]::before {
  animation: ava-breathe 1.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .turn.thinking::after { animation: none; }
  body[data-surface="student"] #log .turn.thinking[data-initial]::before { animation: none; }
}

/* Help where the conversation will be, gone once there is one. */
#log:not(:empty) + #logEmpty { display: none; }
.empty.startHelp { text-align: left; margin-bottom: var(--gap); }
.startHelp p { margin: var(--s1) 0 0; }
.startHelp ul { margin: var(--s2) 0 0; padding-left: var(--s5); }

/* "Show me around", in the help line under the band rather than at a card's foot. */
.pagehelp button.helplink { margin: 0; }

/* The line under an empty sentence that says what to do next. Shown only while
 * the sentence it follows is. */
.emptyhelp { display: none; color: var(--muted); font-size: var(--t2); text-align: center;
             margin: var(--s2) auto var(--gap); max-width: 60ch; }
.empty:not(.hidden) + .emptyhelp { display: block; }

/* Quick-help chips above the box: real buttons, pill shaped, the full 44px. */
.chiprow { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; margin-bottom: var(--s2); }
.chips { display: flex; flex-wrap: wrap; gap: var(--s2); }
/* Filled soft blue, so a suggestion that sends words reads as a different kind
 * of button from the tools that open something; the edge stays, as on
 * anything operable. */
button.chip { background: var(--soft); color: var(--ink); border-color: var(--edge);
              border-radius: var(--r-pill); padding: var(--s1) var(--s4); font-size: var(--t3); }
button.chip:hover { background: var(--card); border-color: var(--accent); }
button.chip[disabled]:hover { background: var(--soft); border-color: var(--edge); }
/* A status line takes no room while empty, and stays in the accessibility tree:
 * one hidden while empty comes back holding its sentence, and is often not read. */
#chipSaid:empty { margin: 0; }

/* The spec's two columns: the chat, and the panel beside it once something
 * fills it. Wraps to one column on a phone. */
.studyrow { display: flex; flex-wrap: wrap; gap: var(--gap); align-items: flex-start; }
.studyrow > #chatCard { flex: 1 1 26rem; min-width: 0; }
.studyrow > .workspace { flex: 1 1 20rem; min-width: 0; }
main:has(.studyrow > .workspace:not(.hidden)) { max-width: 76rem; }

/* Tooltips. look.js opens one beside its control on hover and on focus, and
 * places it; Escape closes it. Ink and card swapped, the same measured pair. */
.tip { display: none; }
.tip.open { display: block; position: fixed; z-index: 10;
            max-width: min(18rem, calc(100vw - 2 * var(--s4)));
            padding: var(--s2) var(--s3); border-radius: var(--r-ctl);
            background: var(--ink); color: var(--card);
            font: 400 var(--t2) / 1.45 var(--ui); text-align: left; white-space: normal; }

/* The first-run tour, in the browser's own modal dialog. */
dialog.tour { width: min(30rem, calc(100vw - 2 * var(--s4))); padding: var(--s5);
              background: var(--card); color: var(--ink);
              border: 1px solid var(--edge); border-radius: var(--r-card); box-shadow: var(--lift-hi); }
dialog.tour[open] { animation: ava-rise var(--rise) var(--ease); }
dialog.tour::backdrop { background: var(--night); opacity: 0.55; }
.tour h2 { margin: var(--s1) 0 var(--s3); }
.tour p { margin: 0 0 var(--s4); }
.tourbuttons { display: flex; flex-wrap: wrap; gap: var(--s3); }
.tourbuttons #tourClose { margin-left: auto; }

/* The console's section menu: a strip of links on a phone, a column that stays
 * in view beside the cards on a wide screen. The current one is marked by
 * weight and an underline as well as color. */
.sectionmenu ol { list-style: none; margin: 0; padding: 0 0 var(--s1); display: flex; gap: var(--s2);
                  overflow-x: auto; position: relative; }
.sectionmenu a { display: flex; align-items: center; min-height: 44px; padding: 0 var(--s3);
                 white-space: nowrap; border: 1px solid var(--edge); border-radius: var(--r-pill);
                 background: var(--card); color: var(--ink); text-decoration: none; font-size: var(--t3); }
.sectionmenu a:hover { background: var(--soft); }
.sectionmenu a[aria-current="true"] { background: var(--soft); border-color: var(--accent); font-weight: 700;
                                       text-decoration: underline 2px; text-underline-offset: 0.3em; }
@media (min-width: 64rem) {
  #console { position: relative; padding-left: 14rem; }
  .sectionmenu { position: absolute; top: 0; bottom: 0; left: 0; width: 12.5rem; }
  .sectionmenu ol { position: sticky; top: var(--s4); flex-direction: column; overflow: visible; padding: 0; }
  .sectionmenu a { white-space: normal; border-radius: var(--r-ctl); border-color: transparent;
                   background: none; padding: var(--s1) var(--s3); }
  .sectionmenu a[aria-current="true"] { border-color: var(--accent); background: var(--soft); }
}

/* The logos. The staff surfaces' band is always the night ink, so their
 * lockup is the one drawn for a dark ground; the foot of every page carries
 * School Systems Lab, in the version for the page's own ground. */
.lockup { display: block; height: 1.6em; width: auto; max-width: 100%; }
footer.madeby { display: flex; flex-wrap: wrap; gap: var(--s4); align-items: center; justify-content: center;
                padding: var(--s5) var(--s4) var(--s6); border-top: 1px solid var(--line); }
footer.madeby img { display: block; height: 1.5rem; width: auto; }

/* ----------------------------------------------------------------- tables */
/* The table is the primary component on every staff surface. Records live in
 * tables; cards are for summaries. */

.scroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: var(--t3); }
caption { text-align: left; font: 700 var(--t5) / 1.3 var(--display); letter-spacing: var(--tight); padding-bottom: var(--s2); }
th, td { text-align: left; padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line);
         vertical-align: top; }
/* A column's name in the words' own case. Spaced capitals over every column is
 * the stock dashboard. */
thead th { font: 600 var(--t2) / 1.4 var(--ui); color: var(--muted);
           border-bottom: 1px solid var(--edge);
           position: sticky; top: 0; background: var(--card); z-index: 1; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--soft); }
/* Every figure in a table lines up, the way a gradebook's do. */
table { font-variant-numeric: tabular-nums; }
td.id { font-family: var(--mono); font-size: var(--t1); color: var(--muted); }

/* A name cell is two lines, the way every real gradebook draws one. These were
 * a <strong> and a <small> side by side with nothing between them, so a roster
 * read "Jack Whitcombe-ElleryGrade 8" on the page and to a screen reader. */
td strong + small { display: block; color: var(--muted); font-size: var(--t1);
                    font-weight: 400; margin-top: 0.1rem; }
.turn.me td strong + small, td.me strong + small { color: inherit; }
td.num { font-variant-numeric: tabular-nums; text-align: right; }

/* Ported from the school's own intervention dashboard. A resolved alert stays in
 * the log rather than disappearing, so a mis-click is visible; it just stops
 * shouting. A student who never started is the row that matters most here: a
 * bar in the watch colour down the row's edge. It was a wavy underline under the
 * name, which every reader knows as a spelling mistake. The row's own "never
 * started" says it in words; the bar is only where the eye lands. */
tr.resolved { opacity: 0.62; }
tr.never td:first-child { box-shadow: inset 3px 0 0 var(--warn-line); }

/* P-AVA62. A cell holding a short list on the staff dashboard: each concept,
 * mistake or alert on its own line, with when it was seen and the standards it
 * was seen against underneath, small and muted. A cell with nothing in it says
 * so in the muted ink rather than sitting blank. */
ul.facts { list-style: none; margin: 0; padding: 0; }
ul.facts li + li { margin-top: var(--s2); }
ul.facts small { display: block; color: var(--muted); font-size: var(--t1); }
ul.facts small.codes { font-family: var(--mono); }
/* What to try next, under an academic alert's summary (0146). */
.nextstep:not(:empty) { margin-top: var(--s2); }
.nextstep p { margin: var(--s1) 0; }
.nextstep small { display: block; color: var(--muted); font-size: var(--t1); }
td .none { color: var(--muted); }

/* Needs your attention (staff.drawAttention), the staff page's first card.
 * Each open alert is a block of its own with its kind down its left edge, so
 * safety and learning read apart before a word is read, and the group heading
 * and the tag say it in words for everyone who cannot see the color. The way
 * in, Open the message, is the one solid button; the rest are quiet. */
#attentionCard h3 { font: 700 var(--t3) / 1.3 var(--display); letter-spacing: var(--tight); margin: var(--s4) 0 var(--s2); }
#attentionSafety h3 { color: var(--stop-ink); }
#attentionLearning h3 { color: var(--warn-ink); }
#attentionCard p.hint.checked { font-size: var(--t1); margin-top: calc(-1 * var(--s2)); }
ul.alerts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s3); }
ul.alerts li.alert { border: 1px solid var(--line); border-left: 4px solid var(--warn-line);
                     border-radius: var(--r-card); padding: var(--s3) var(--pad); background: var(--card);
                     transition: box-shadow var(--fast) ease, transform var(--fast) var(--ease); }
ul.alerts li.alert:hover { box-shadow: var(--lift); }
ul.alerts li.alert.harm { border-left-color: var(--stop-line); }
.alerthead { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s1) var(--s3); margin: 0; }
.alerthead small { color: var(--muted); font-size: var(--t2); }
.alerthead .ago { margin-left: auto; }
li.alert .summary { margin: var(--s2) 0 var(--s1); }
li.alert > small { display: block; color: var(--muted); font-size: var(--t2); }
li.alert .actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s3); }

/* The message an alert came from, opened from either list, outlined in the
 * alert's color and named by the tag at its top, which wraps in a bubble. */
.turn.landed { border-width: 2px; border-color: var(--warn-line); }
.turn.landed.harm { border-color: var(--stop-line); }
.turn.landed > .tag { display: table; white-space: normal; margin-bottom: var(--s2); }

.tag { display: inline-block; padding: 0 var(--s2); border-radius: var(--r-pill);
       font: 700 var(--t1) / 1.7 var(--ui); border: 1px solid var(--edge);
       white-space: nowrap; }
.tag.harm { background: var(--stop-bg); border-color: var(--stop-line); color: var(--stop-ink); }
.tag.watch { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn-ink); }
.tag.done { background: var(--done-bg); border-color: var(--done-ink); color: var(--done-ink); }

.count { color: var(--muted); font: 500 var(--t2) / 1.4 var(--ui); letter-spacing: 0;
         font-variant-numeric: tabular-nums; }
/* Beside a card's heading it is a small pill: the number that answers the
 * question before it is asked. */
h2 > .count, caption > .count { display: inline-block; vertical-align: 0.15em; margin-left: var(--s1);
                                padding: 0 var(--s2); border-radius: var(--r-pill);
                                background: var(--soft); color: var(--ink); }

/* -------------------------------------------------------- the seven states */

/* 1. Empty. The first day and a filter that matched nothing are different
 *    sentences with different ways out, so they are different classes. */
.empty { color: var(--muted); padding: var(--s5) var(--s3); text-align: center;
         border: 1px dashed var(--edge); border-radius: var(--r-card);
         background: var(--page); }
.empty strong { display: block; color: var(--ink); font-size: var(--t4); margin-bottom: var(--s1); }
.empty.filtered { border-style: solid; }

/* 2. Loading, and 6. Partial. Built 2026-09-01: every panel loads through
 *    panel() in ava.js, which gives the card one note about itself. While the
 *    fetch is out the note says "Loading." and the card is aria-busy; when the
 *    fetch fails the failure lands here, on the card, in the failure ink the
 *    page-level box uses, because one dead panel is not a dead page. No
 *    skeletons and no animation: this product has none, on purpose. */
.cardnote { color: var(--muted); font-size: var(--t2); margin: var(--s3) 0 0; }
.cardnote.fail { color: var(--stop-ink); background: var(--stop-bg);
                 border: 1px solid var(--stop-line); border-left-width: 4px;
                 border-radius: var(--r-ctl); padding: var(--s2) var(--s3); }

/* 3. Error. #notice is the page-level failure. */
#notice { padding: var(--s3) var(--pad); border-radius: var(--r-ctl);
          border: 1px solid var(--stop-line); border-left-width: 4px;
          background: var(--stop-bg); color: var(--stop-ink); margin-bottom: var(--gap); }

/* 4. Permission denied. In a school this is normal, not a failure, so it is not
 *    drawn in the failure colours. A teacher who cannot open the console has not
 *    broken anything. */
/* An id beats a class, so this has to out-specify #notice deliberately rather
 * than by luck. Same box, same live region, not the failure colours. */
#notice.denied {
  background: var(--soft);
  color: var(--ink);
  border-color: var(--edge);
  border-left-color: var(--accent);
}

/* 7. Offline. */
.offline { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap;
           padding: var(--s3) var(--pad); background: var(--warn-bg);
           color: var(--warn-ink); border-bottom: 1px solid var(--warn-line); }

/* The stay-signed-in prompt. Warm rather than alarming: it is a question, not a
 * failure, and a child seeing it has done nothing wrong. */
#staying { display: flex; gap: var(--gap); align-items: center; flex-wrap: wrap;
           background: var(--warn-bg); color: var(--warn-ink);
           border: 1px solid var(--warn-line); border-left-width: 4px;
           border-radius: var(--r-card); padding: var(--pad); margin: 0 0 var(--gap); }
#staying p { margin: 0; flex: 1 1 14rem; }

.sr-only { 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) {
  *, *::before, *::after { animation-duration: 0.01ms !important;
                           animation-iteration-count: 1 !important;
                           transition-duration: 0.01ms !important; }
}

/* ------------------------------------------------------------------ print */
/* /report is the page a head teacher prints for a board meeting, so paper is a
 * real surface here and not a CSS nicety. Ink is expensive, the band is not
 * information, and a URL a reader cannot click is noise. */
@media print {
  :root { --page: #ffffff; --card: #ffffff; --ink: #000000; --muted: #333333;
          --line: #999999; --soft: #ffffff; --lift: none; --lift-hi: none; }
  body { background: #ffffff; font-size: 10.5pt; }
  body[data-surface="student"]::before, body[data-surface="student"]::after { display: none; }
  h2 .subject { background: none; padding: 0; }
  header.bar { background: #ffffff; color: #000000; box-shadow: none;
               border-bottom: 2px solid #000000; padding-left: 0; padding-right: 0; }
  header.bar button, .toolbar, #composer, .attachrow, button { display: none !important; }
  /* A mailto link cannot be clicked on paper either, and the printline above
   * already says whose numbers these are. */
  .pagehelp { display: none; }
  /* Navigation cannot be clicked on paper and "Signed in as admin" is about the
   * session, not the school. Both printed as ghost text until 2026-09-01, and
   * with them gone the wordmark has the room to stay on one line. */
  header.bar .pages, header.bar .who { display: none; }
  header.bar h1 { white-space: nowrap; }
  /* The header lockup is drawn for a dark ground and paper is white, so the
   * printed page carries the one drawn for a light ground. */
  header.bar img.lockup { content: url('/clearpath-tutoring-logo.svg'); }
  /* The pickers stay: they carry the chosen window and the chosen report's
   * name. What goes is the control chrome, because a dropdown arrow on paper
   * promises an interaction paper cannot offer. */
  select { appearance: none; border: 0; background: none; padding: 0; font-weight: 600; }
  /* Whose numbers, and when they left the building. Filled by applyPrintLine
   * in ava.js; on screen the session already says both, so it stays hidden. */
  .printline { display: block; font-size: 9pt; color: #333333; margin: 0.15cm 0 0; }
  main, main.wide { max-width: none; padding: 0; display: block; }
  section.card { border: 0; border-top: 1px solid #999999; border-radius: 0;
                 padding: 0.4cm 0; margin: 0; break-inside: avoid; }
  table { font-size: 9.5pt; }
  thead { display: table-header-group; }   /* the head repeats on every page */
  tr { break-inside: avoid; }
  thead th { position: static; }
  .tag { border: 1px solid #000000; }
  #log { max-height: none; overflow: visible; }
  .turn { border: 1px solid #999999; max-width: 100%; }
}

/* The microphone, while it is listening.
 *
 * COLOUR IS NOT THE ONLY SIGNAL. The button's own text changes from Talk to
 * Stop, and the line beside it says "Listening", so a child who cannot tell the
 * two states apart by colour is told twice in words. WCAG 2.2 AA 1.4.1 Use of
 * Colour. The ring is drawn with the same token the rest of the interface uses
 * for a live thing, so it is legible in both colour schemes without a second
 * definition. */
#talk.recording {
  box-shadow: 0 0 0 2px var(--band, currentColor);
  font-weight: 600;
}

/* The spoken answer sits under the composer and never grows the card. */
#spoken {
  display: block;
  width: 100%;
  max-width: 22rem;
  margin-top: 0.5rem;
}

/* -------------------------------------------- forced colors (Windows High Contrast Mode) */
/* A lot of school estates turn `forced-colors` on centrally through group
 * policy (ACCESSIBILITY.md section 3). The platform replaces every author
 * colour with a system keyword the person chose in their own theme, and it
 * does this to every rendered colour whether this stylesheet has an opinion
 * or not. What it does NOT do is invent a replacement for box-shadow,
 * text-shadow or background-image: those simply compute to nothing, because
 * there is no system-colour stand-in for a shadow or a picture.
 *
 * Two concrete things break on exactly that gap. The focus ring's outer halo
 * is a box-shadow, gone the moment forced colors turns on, which is why the
 * ring was built as two rings rather than a glow: the inner ring is a plain
 * `outline`, and an outline survives. It is pinned to `Highlight` here, the
 * keyword the platform itself uses for "the thing with focus", rather than
 * left on the `--focus` token, so the ring is the colour the person picked
 * for exactly this job. And `select` turns its native arrow off with
 * `appearance: none` and draws its own with a `background-image` gradient
 * (see the rule near the top of the file): forced colors drops that image
 * and does not put the native one back, so without help a forced-colors
 * user gets a dropdown with no visible way to tell it is one.
 * `appearance: auto` hands the arrow back to the platform.
 *
 * Everything else below, the input and textarea and button edges, the file
 * picker label standing in for a hidden input, the tag pills, the empty
 * state, and the notice and prompt boxes, gets an explicit `ButtonBorder` or
 * `CanvasText` rather than trusting the browser's own forced-colors
 * heuristic for a plain element with a border. That heuristic is very
 * likely already fine on its own, but "very likely" is not evidence, and
 * this file could not be opened in Windows High Contrast Mode to check
 * (ACCESSIBILITY.md section 3). A system-colour keyword is the one thing
 * provably still there afterwards, and `test/contrast.test.ts` recomputes
 * every rule below from this file the same way it recomputes every hex
 * ratio above it.
 *
 * The tag colours (harm, watch, done) collapse into one another here, on
 * purpose and not as a gap: forced colors removing custom colour coding is
 * the platform doing its job, and every one of these tags already carries
 * the word ("harm disclosure", "resolved") for exactly this reason, tested
 * under "Colour as the only signal" in ACCESSIBILITY.md section 1. */
@media (forced-colors: active) {
  input, select, textarea, button, button.quiet, button.danger, .pick {
    border-color: ButtonBorder;
  }

  select {
    appearance: auto;
    background-image: none;
  }

  :focus-visible {
    outline-color: Highlight;
    box-shadow: none;
  }
  .sr-only:focus-visible + .pick {
    outline-color: Highlight;
    box-shadow: none;
  }

  .tag, .tag.harm, .tag.watch, .tag.done, .empty, #notice, #notice.denied,
  .cardnote.fail, #staying, .offline, ul.alerts li.alert {
    border-color: CanvasText;
  }
}

/* B1. The tooltip, the tour and the menu links stand out by their fill.
 * Forced colors paints the fill the page's own Canvas, so they get an edge in
 * the system ink. A block of its own after the one above,
 * which test/contrast.test.ts reads as the controls' policy. */
/* Forced colours repaint a background with a background colour, which would
 * blank a mask-drawn icon. The icons take the button text's system colour. */
@media (forced-colors: active) {
  .pick::before, #writeOpen::before, #studyToggle::before { forced-color-adjust: none; background: ButtonText; }
}
@media (forced-colors: active) {
  .tip.open, dialog.tour, .sectionmenu a { border: 1px solid CanvasText; }
}
/* The page's lights and dots are pictures, which forced colours drops anyway;
 * said here so nothing is left half drawn. The swipe behind what a child is
 * working on becomes the platform's own marked text. */
@media (forced-colors: active) {
  body[data-surface="student"]::before, body[data-surface="student"]::after { display: none; }
  h2 .subject { background: Mark; color: MarkText; }
  #openCard h2::after { background: Highlight; }
  .orb { border: 1px solid CanvasText; }
}

/* ------------------------------------------ math, and the graph beside the chat */
/* A reply's math is drawn by KaTeX (math.js), whose own stylesheet sets its
 * faces; this only fits it into a bubble. A display line too wide for a phone
 * scrolls inside its bubble rather than widening the page. */
.turn .math.display { overflow-x: auto; overflow-y: hidden; margin: var(--s2) 0; }
.turn .katex-display { margin: 0; }
.graphbutton { display: block; margin-top: var(--s2); }
.graphsaid { margin: var(--s2) 0; }

/* The panel (graph.js). It is the study row's workspace, so .studyrow lays it
 * beside the chat when there is room and under it on a phone, and only while
 * a graph is open. Each curve has its own line as well as its own
 * colour, and the legend's keys match them, so colour is never the only cue. */
#graphPanel, #writePanel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: var(--pad);
  margin-bottom: var(--gap);
}
#chatCard.hidden + #graphPanel, #chatCard.hidden ~ #writePanel { display: none; }
.graphhead, .workhead { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }
.graphhead h2, .workhead h2 { margin: 0; }
.graphboard { position: relative; overflow: hidden; width: 100%; aspect-ratio: 4 / 3; margin: var(--s3) 0; }
.graphkeys { list-style: none; margin: 0 0 var(--s2); padding: 0; }
.graphkeys li { display: flex; align-items: center; gap: var(--s2); }
.graphkeys .key { flex: 0 0 2rem; border-top: 3px solid var(--accent); }
.graphkeys li:nth-child(2) .key { border-top: 3px dashed var(--stop-line); }
.graphkeys li:nth-child(3) .key { border-top: 3px dotted var(--warn-line); }
#graphPanel p.hint { margin: 0; }

/* P-AVA84. The writing space (writing.js): the workspace's text editor, tall
 * enough for a page of writing, with its count, what it says, and the button
 * and the shortcut that send it. */
.workhead { flex-wrap: wrap; }
#writePanel label { display: block; margin-top: var(--s3); }
#writePanel #draft { min-height: 16rem; margin-top: var(--s1); }
#writePanel p.hint { margin: var(--s2) 0 0; }
/* Level with Study tools beside it, which keeps this margin for its menu. */
#writeOpen { margin-bottom: var(--s2); }
#writePanel #draftSaid:empty { margin: 0; }
.draftfoot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3); margin-top: var(--s3); }
.draftfoot .hint { margin: 0; }

/* The keyboard shortcuts, closed until asked for. The summary is a control,
 * so it gets a control's 44px; each key looks like a key. */
details.shortcuts { margin-top: var(--s2); }
details.shortcuts summary { cursor: pointer; min-height: 44px; display: list-item; padding: var(--s2) 0; }
dl#shortcutList { display: grid; grid-template-columns: max-content 1fr; gap: var(--s2) var(--s3); margin: 0; }
dl#shortcutList dd { margin: 0; }
@media (max-width: 30rem) {
  dl#shortcutList { grid-template-columns: 1fr; row-gap: var(--s1); }
  dl#shortcutList dd { margin-bottom: var(--s2); }
}
kbd { font: inherit; font-size: 0.9em; white-space: nowrap; padding: 0 0.35em; border: 1px solid var(--edge);
      border-bottom-width: 2px; border-radius: 4px; background: var(--card); color: var(--ink); }

/* The crisis lines under the safety reply, one touch each: a target well over
   24 by 24 (WCAG 2.5.8), and underlined like every other link. */
.actions a.crisisline { display: inline-block; padding: var(--s2) var(--s3); margin: var(--s1) var(--s2) 0 0;
  border: 1px solid var(--edge); border-radius: var(--r-pill); }

/* Easier-to-read text (look.js): a plain sans serif with the letter, word and
 * line spacing of WCAG 1.4.12, set on the controls a child writes in too, since
 * they do not inherit it, and the turns of the conversation 2em apart. Maths and
 * code keep the spacing KaTeX measures them with. */
:root[data-readable="on"] { --ui: Verdana, Tahoma, Arial, sans-serif; --display: Verdana, Tahoma, Arial, sans-serif; }
:root[data-readable="on"] :is(body, button, input, select, textarea) { line-height: 1.8; }
/* The spacing is set on each element, not inherited: inherited, it arrives as
 * the body's pixels, which on a heading twice the size is half of 0.12em. And
 * over any rule, because a heading sets a tight tracking of its own, some by
 * id, in the very words a reader meets first. Maths and code, and everything
 * inside them, keep the spacing they were measured with. */
:root[data-readable="on"] :is(body, body *):not(:is(.katex, math, code, pre, kbd), :is(.katex, math, code, pre, kbd) *) {
  letter-spacing: 0.12em !important; word-spacing: 0.16em !important; }
:root[data-readable="on"] :is(.katex, math, code, pre, kbd) { letter-spacing: normal !important; word-spacing: normal !important; }
:root[data-readable="on"] #log { gap: 2em; }
/* The switch shows it is on as well as saying so, and stands apart from Show me around. */
button.helplink[aria-pressed="true"] { font-weight: 700; color: var(--ink); }
.pagehelp button.helplink + button.helplink { margin-left: var(--s4); }

/* Under a reply that taught from the school's material: which document (0155). */
.turn small.source { display: block; margin-top: var(--s1); color: var(--muted); font-size: var(--t1); }
/* Read-along (ava.js follow): the word being said, marked by underline as well as colour. */
::highlight(ava-word) { background-color: Mark; color: MarkText; text-decoration: underline; }
