/* 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 is ClearPath's own, taken from the storefront stylesheet and the
 * product icon system rather than invented. 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.
 */

:root {
  /* ---------------------------------------------------------------- colour */
  --deep: #153d3b;        /* the brand band, the primary button, the child's own turn */
  --deep-ink: #fffdf8;    /* anything written on --deep                         11.72 */
  --deep-muted: #a8c4bc;  /* secondary text on --deep                            6.40 */
  --green: #206b5f;       /* the wordmark, links, the active state         6.20 / 5.55 */
  --mint: #dcebe3;        /* the tutor's turn, soft fills, the student band          */
  --page: #f4f0e7;        /* ClearPath cream */
  --card: #fffdf8;        /* ClearPath paper */
  --ink: #183331;         /*                                             13.27 / 11.86 */
  --muted: #5c6967;       /*                                               5.63 / 5.03 */
  --line: #d9ded7;        /* a divider between rows. NOT a control edge */

  /* 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.26:1 against paper, which is a good
   * hairline and an invisible text box.  4.42 card / 3.95 page / 3.64 mint */
  --edge: #5f7d75;

  /* 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 deep band, and the luminance window satisfying both is about 0.235 to
   * 0.30. Five candidates in it were measured and each failed on two grounds or
   * more. So one ring carries the light grounds and the other carries the band,
   * they contrast with each other at 8.99: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: #0d3f57;       /* inner       11.07 card / 9.90 page / 9.13 mint */
  --focus-halo: #cfeaf7;  /* outer       9.51 on the deep band */

  --warn-bg: #fbf1de; --warn-line: #9c7017; --warn-ink: #7a5410;   /* 4.35 / 6.04 */
  --stop-bg: #fbeae6; --stop-line: #c0503a; --stop-ink: #8c3a26;   /* 4.64 / 6.55 */
  --done-bg: #dcebe3; --done-ink: #14532d;

  /* Fill and border only. 3.13:1 as text, which fails 1.4.3, so --stop-ink is
   * the text partner. The storefront carries the same split. */
  --coral: #dd715c;

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

  /* ------------------------------------------------------------------ type */
  --display: Georgia, "Iowan Old Style", "Times New Roman", serif;
  --ui: 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.85rem;   /* secondary and hint text */
  --t3: 0.95rem;   /* table cells */
  --t4: 1rem;      /* body. The density dial moves this one and the rest follow */
  --t5: 1.15rem;   /* h2 */
  --t6: 1.4rem;    /* h1 in the band */
  --t7: 1.75rem;   /* the student's own heading, the only display-scale type */
  --leading: 1.6;

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

  /* Three radii, on purpose. 4px on controls is the storefront's own value and
   * reads institutional; cards are softer; tags are pills. Everything at one
   * radius is the tell. */
  --r-ctl: 4px; --r-card: 10px; --r-pill: 999px;

  --lift: 0 1px 0 rgba(21, 61, 59, 0.06), 0 6px 20px rgba(21, 61, 59, 0.07);
  --fast: 120ms;
  --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.2rem;  --leading: 1.75; }
: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 {
    --deep: #153d3b; --deep-ink: #fffdf8; --deep-muted: #a8c4bc;
    --green: #6fc0ac;                                     /* 7.34 */
    --mint: #1c322e;                                      /* 11.58 with --ink */
    --page: #0f1c1a; --card: #162624;
    --ink: #e6efea;                                       /* 14.90 / 13.39 */
    --muted: #9fb3ad;                                     /* 7.13 */
    --line: #2a3d39;
    --edge: #7f9e96;                                      /* 5.41 / 6.02 */
    --focus: #cfeaf7; --focus-halo: #0d3f57;              /* swapped, same construction */
    --warn-bg: #3a2d14; --warn-line: #c99a3f; --warn-ink: #f0d9a8;
    --stop-bg: #3a1c16; --stop-line: #e08472; --stop-ink: #f5c8bd;
    --done-bg: #1c322e; --done-ink: #9fe0bd;
    --lift: 0 1px 0 rgba(0, 0, 0, 0.4), 0 6px 20px rgba(0, 0, 0, 0.35);
  }
}

* { 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%;
}

/* A link is the green 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(--green); }

/* 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);
  box-shadow: var(--lift);
}

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

/* The ClearPath family mark: the stepped path itself, drawn in the band's own
 * ink. The icon system specifies a cream mark on a deep field; the band IS the
 * deep field, so the field is not drawn twice. */
.brandmark { flex: 0 0 auto; width: 1.5em; height: 1.5em; }

/* 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 mint so
 * a school that set nothing previews exactly what its students get. */
.brandstrip { display: flex; align-items: center; gap: var(--s3);
              background: var(--mint); color: var(--ink);
              font: 600 var(--t4)/1.2 var(--display);
              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. Horizontal padding
 * matches the header rather than main's, since it sits between the two. */
.pagehelp { padding: var(--s2) clamp(var(--s4), 4vw, var(--s6)) 0; }

/* 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(--coral); }

/* --------------------------------------------------------- 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; }
  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 soft end of the family: same mark, same type, mint 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(--mint);
  --band-ink: var(--ink);
  --band-muted: var(--muted);
}
body[data-surface="student"] 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: 600 var(--t5) / 1.3 var(--ui); margin: 0 0 var(--s2); }
/* The one typographic moment on the student's page. What a child chose to work
 * on is the heading of their own session, so it is set in the display face
 * rather than being another line of interface text. */
h2 .subject { display: block; font: 400 var(--t7) / 1.25 var(--display);
              color: var(--ink); margin-top: 0.1rem; }
h2:has(.subject) { color: var(--muted); font-weight: 500; }
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);
}

/* 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%; }
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;
}
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(--deep);
  color: var(--deep-ink);
  border-color: var(--deep);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--fast) ease;
}
button:hover { background: var(--green); border-color: var(--green); }
button.quiet { background: var(--card); color: var(--ink); border-color: var(--edge); }
button.quiet:hover { background: var(--mint); border-color: var(--edge); }
/* --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(--deep); border-color: var(--deep); }

/* Dark mode inverts the primary: --deep is nearly the page colour there, so a
 * deep button on a dark card is a rectangle nobody can find. */
@media (prefers-color-scheme: dark) {
  button { background: var(--green); border-color: var(--green); color: var(--page); }
  button:hover { background: var(--mint); border-color: var(--green); color: var(--ink); }
  button[disabled]:hover { background: var(--green); border-color: var(--green); }
}

: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; }

/* 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; }
/* 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. Its button in a lesson, clear of the card or the lesson below it. */
#childHelpOpen { margin-bottom: var(--gap); }
/* 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); }
.toolbar > p { margin: 0 auto 0 0; }
.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); }
/* 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(--pad);
  border-radius: var(--r-card);
  border: 1px solid transparent;
  background: var(--mint);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-width: 88%;
}

.turn.me { background: var(--deep); color: var(--deep-ink); align-self: flex-end; }
.turn .said { display: block; font: 700 var(--t1) / 1.4 var(--ui);
              text-transform: uppercase; letter-spacing: 0.06em;
              opacity: 0.72; margin-bottom: var(--s1); }
.turn.thinking { opacity: 0.7; font-style: italic; }
.turn .withpicture { display: block; font-size: var(--t2); opacity: 0.75; 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. */
#composer { display: flex; gap: var(--s3); align-items: flex-end; }
#composer textarea { flex: 1 1 auto; min-width: 0; }

.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); border-radius: var(--r-ctl);
        padding: var(--s1) var(--s3); cursor: pointer; font-size: var(--t3);
        min-height: 24px; background: var(--card); }
.pick:hover { background: var(--mint); }
.sr-only:focus-visible + .pick { outline: 3px solid var(--focus); outline-offset: var(--offset);
                                 box-shadow: 0 0 0 9px var(--focus-halo); }
#attachPreview { max-height: 3rem; max-width: 8rem; border: 1px solid var(--line);
                 border-radius: var(--r-ctl); }

/* ----------------------------------------------------------------- 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: 600 var(--t5) / 1.3 var(--ui); padding-bottom: var(--s2); }
th, td { text-align: left; padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line);
         vertical-align: top; }
thead th { font: 700 var(--t1) / 1.4 var(--ui); text-transform: uppercase;
           letter-spacing: 0.06em; color: var(--muted);
           border-bottom: 2px 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(--mint); }
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. */
tr.resolved { opacity: 0.62; }
tr.never td:first-child strong { text-decoration: underline wavy 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); }
td .none { color: var(--muted); }

.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(--green); color: var(--done-ink); }

.count { color: var(--muted); font-size: var(--t2); font-weight: 400;
         font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------- 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(--mint);
  color: var(--ink);
  border-color: var(--edge);
  border-left-color: var(--green);
}

/* 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; --mint: #ffffff; }
  body { background: #ffffff; font-size: 10.5pt; }
  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 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 {
    border-color: CanvasText;
  }
}
