/*
  styles.css — MoleBridge.

  THE PALETTE IS THE `Instrument` FAMILY, taken verbatim from the hub's
  palettes/families.json rather than derived here. Those values have been
  measured against every hard floor in PALETTES.md in both modes; picking
  colours by eye and hoping is how a build stamp ends up at 2.54:1 while its
  gate reads 4.79 and passes.

  The roles, and what each is for:
    --page / --page-alt     the ground the app sits on
    --surface-1/2/3         the fill ladder, monotonically away from the page
    --rail                  every 1px edge that has to be seen
    --hairline              separators that need not be
    --text-1/2/3            primary, secondary, tertiary — strictly ordered
    --accent                the focus ring and the one loud thing

  §7b: the build stamp is dimmed with --text-3, a COLOUR TOKEN. Never opacity —
  an opacity is invisible to a contrast gate, and the stamp is the one element
  whose entire job is being readable in a screenshot nobody composed.
*/

:root {
  color-scheme: dark light;

  --page: #1a1a1a;
  --page-alt: #232323;
  --surface-1: #3a3a3a;
  --surface-2: #414141;
  --surface-3: #484848;
  --rail: rgba(255, 255, 255, 0.62);
  --hairline: rgba(255, 255, 255, 0.17);
  --text-1: #eaeaea;
  --text-2: #d2d2d2;
  --text-3: #c1c1c1;

  --step: 0.5rem;
  --radius: 0.6rem;
  /* Every pressable thing clears this. A finger on a board at the front of a
     room is not a mouse pointer, and 44px is the floor rather than the target. */
  --touch: 3rem;
  --measure: 42rem;
}

/*
  THE LIGHT MODE IS DECLARED ONCE, HERE. `theme.js` resolves the reader's
  preference — auto, light or dark — into `data-theme` before first paint, so
  this stylesheet never has to ask the operating system anything and the values
  are not written twice.
*/
:root[data-theme="light"] {
  --page: #c4bcab;
  --page-alt: #ccc4b3;
  --surface-1: #eee6d6;
  --surface-2: #f4ecdd;
  --surface-3: #faf3e5;
  --rail: rgba(39, 33, 25, 0.68);
  --hairline: rgba(39, 33, 25, 0.22);
  --text-1: #272119;
  --text-2: #363026;
  --text-3: #4a4436;
}

/*
  ...except once, and only for a reader with JavaScript off, where `data-theme`
  is never set and nothing above fires. This block is held character-identical
  to the one above by `tools/tokens-check.mjs`; PALETTES.md is explicit that a
  must-change-together pair which nothing checks is a drift factory.
*/
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
  --page: #c4bcab;
  --page-alt: #ccc4b3;
  --surface-1: #eee6d6;
  --surface-2: #f4ecdd;
  --surface-3: #faf3e5;
  --rail: rgba(39, 33, 25, 0.68);
  --hairline: rgba(39, 33, 25, 0.22);
  --text-1: #272119;
  --text-2: #363026;
  --text-3: #4a4436;
  }
}

/*
  ---- the accent, which is the axis a person means by "colour" ----

  PALETTES.md's four families vary chrome WARMTH rather than hue, so the
  neutrals above are the Instrument family in every theme and only these change.
  Each is a real palette in palettes/molebridge.json and `npm run palette`
  checks all six against every hard floor. `moss` is the default and is set by
  theme.js, so a reader who has chosen nothing still gets a complete palette.

  `--on-accent` is text ON the accent fill and is its own token: in light mode
  the page is a warm tan, and tan lettering on a dark green button is a pairing
  nobody chose — it just falls out of borrowing a token for a job it was not for.
*/
[data-palette="moss"] {
  --accent: #9fd8b0;
  --accent-soft: rgba(159, 216, 176, 0.15);
  --on-accent: #0e2a1a;
}
[data-palette="moss"][data-theme="light"] {
  --accent: #14532d;
  --accent-soft: rgba(23, 83, 45, 0.12);
  --on-accent: #f7f3ea;
}
[data-palette="harbour"] {
  --accent: #9fc2f5;
  --accent-soft: rgba(159, 194, 245, 0.15);
  --on-accent: #10233d;
}
[data-palette="harbour"][data-theme="light"] {
  --accent: #17458f;
  --accent-soft: rgba(23, 69, 143, 0.12);
  --on-accent: #f7f3ea;
}
[data-palette="clay"] {
  --accent: #eab896;
  --accent-soft: rgba(234, 184, 150, 0.15);
  --on-accent: #3a1f0a;
}
[data-palette="clay"][data-theme="light"] {
  --accent: #6d3410;
  --accent-soft: rgba(109, 52, 16, 0.12);
  --on-accent: #f7f3ea;
}

/*
  ---- reading preferences ----

  Set on <html> by theme.js before the first paint, for the same reason the
  theme is: applied afterwards they are a visible reflow, and text jumping size
  on load is worst for the reader who needed the setting.

  SIZE IS A ROOT FONT SIZE, not a zoom. Everything in this stylesheet is in
  `rem`, so one value moves the type, the touch targets and the spacing
  together — a student who needs bigger text needs bigger buttons too, and a
  transform would have scaled the layout off the side of the screen.
*/
:root[data-text="large"] { font-size: 118%; }
:root[data-text="largest"] { font-size: 136%; }

/*
  Spacing rather than a different typeface. A dyslexia-friendly FONT cannot ship
  here — no third-party runtime dependencies, and the app has to work offline —
  but the spacing is most of what the research supports anyway, and it costs
  three declarations.
*/
:root[data-spacing="roomy"] body {
  line-height: 1.9;
  letter-spacing: 0.045em;
  word-spacing: 0.14em;
}
:root[data-spacing="roomy"] p,
:root[data-spacing="roomy"] li { margin-bottom: 0.9em; }
/* NEVER THE CODE OR THE EQUATIONS. A completion code is transcribed character
   by character and a formula's subscripts are part of its meaning; loosening
   either makes them harder to read, not easier. */
:root[data-spacing="roomy"] .code,
:root[data-spacing="roomy"] .equation,
:root[data-spacing="roomy"] .worked { letter-spacing: normal; word-spacing: normal; }

/*
  One thing at a time. The step being worked stays; the equation card and the
  step rail go. The app is step-gated already, so this is what it looked like
  underneath all along.

  THE PROGRESS LINE USED TO GO WITH THEM, and that was wrong. It and the rail
  were the only two things saying which step this is, so hiding both left the
  student who asked for one step at a time with no way to tell the fourth box
  from the second. It now names the step as well as the problem and stays in
  both modes.
*/
:root[data-focus="on"] .problem-card,
:root[data-focus="on"] .rail { display: none; }

* { box-sizing: border-box; }

/*
  `hidden` HAS TO WIN. A `display` rule on a class beats the hidden attribute's
  user-agent style, so `.update-strip { display: flex }` showed the stale-app
  strip on every load — the one element whose whole point is that it appears
  only when there is genuinely a new version waiting. Caught by looking at the
  screen; nothing else would have.
*/
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--page);
  color: var(--text-1);
  font: 1.125rem/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  padding-bottom: 4rem;
}

/* A focus ring on everything, from one rule, so nothing can be missed. */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

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

/*
  The skip link is a KEYBOARD route and is kept as one. It is not the only route
  to anything — every screen's first control is reachable by tabbing forward —
  because a control revealed only by focus is unreachable by finger, and an
  affordance nobody can reach is worse than a missing one.
*/
.skip {
  position: absolute;
  left: var(--step); top: -4rem;
  z-index: 10;
  padding: 0.75rem 1rem;
  background: var(--surface-2);
  color: var(--text-1);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
  text-decoration: none;
  transition: top 120ms ease;
}
.skip:focus { top: var(--step); }

/* ---------------- chrome ---------------- */

.chrome {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step);
  padding: 0.75rem 1rem;
  background: var(--page-alt);
  border-bottom: 1px solid var(--hairline);
}

.chrome-left { display: flex; align-items: baseline; gap: 0.6rem; }

.brand { font-weight: 650; letter-spacing: 0.01em; }

/* §7b: always on screen, quiet, and selectable so it can be pasted. */
.stamp {
  color: var(--text-3);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  user-select: text;
}

.icon-button {
  min-width: var(--touch);
  min-height: var(--touch);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.4rem;
  background: transparent;
  color: var(--text-1);
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
}
.icon-button:hover { background: var(--surface-1); border-color: var(--hairline); }

/*
  The chrome's icons. STROKED IN currentColor so they inherit the button's own
  text token — nothing here declares a colour, which is what keeps every pixel
  in the app inside the role system the theme swap depends on.

  `round` caps are what make the three keypad dots and the (i)'s tittle read as
  dots at all: they are zero-length lines, so a butt cap would draw nothing.
*/
/* The one icon drawn as solid cells rather than strokes. */
.glyph-cells rect { fill: currentColor; stroke: none; }

.glyph {
  width: 1.375rem;
  height: 1.375rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---------------- the stale-app strip (§7h) ---------------- */

.update-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  background: var(--accent-soft);
  border-bottom: 1px solid var(--rail);
  color: var(--text-1);
}

/*
  The way back to a problem in progress. Deliberately the same shape as the
  stale-version strip: both are the app saying "there is something you should
  know about" from outside whatever screen you are on, and a second visual
  language for that would make the two read as different kinds of thing.
*/
.resume-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  background: var(--accent-soft);
  border-bottom: 1px solid var(--rail);
  color: var(--text-1);
}

/* ---------------- layout ---------------- */

main { padding: 1.25rem 1rem 0; }

.screen {
  max-width: var(--measure);
  margin: 0 auto;
}
.screen[hidden] { display: none; }

h1 { font-size: 1.75rem; line-height: 1.25; margin: 0 0 0.5rem; }
h2 { font-size: 1.25rem; line-height: 1.3; margin: 1.75rem 0 0.5rem; }
h3 { font-size: 1.0625rem; margin: 1.25rem 0 0.4rem; }
p { margin: 0 0 0.9rem; }
ul, ol { margin: 0 0 0.9rem; padding-left: 1.4rem; }
li { margin: 0.3rem 0; }

.lede { color: var(--text-2); }
.hint { color: var(--text-3); font-size: 0.9375rem; }
.error { color: var(--text-1); background: var(--accent-soft); border-left: 4px solid var(--rail); padding: 0.75rem 1rem; border-radius: var(--radius); }

.card {
  background: var(--surface-1);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 1rem 1.1rem;
  margin: 0 0 1.25rem;
}

/* ---------------- the problem ---------------- */

.progress { color: var(--text-2); font-size: 0.9375rem; margin-bottom: 0.5rem; }

.equation {
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  margin-bottom: 0.6rem;
  overflow-x: auto;
}

.prompt { margin-bottom: 0.4rem; }

/* ---------------- the step rail ---------------- */

.rail {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  list-style: none;
  padding: 0;
  margin: 0 0 1.25rem;
}

.rail-step {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem 0.7rem;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  font-size: 0.875rem;
  color: var(--text-3);
  background: var(--page-alt);
}
.rail-done { color: var(--text-2); }
.rail-here {
  color: var(--text-1);
  border-color: var(--rail);
  background: var(--accent-soft);
  font-weight: 600;
}
.rail-tick { font-weight: 700; }
/*
  WHAT THEY PUT IN THE STEP, beside its name. The chain is the whole of
  stoichiometry: the molar mass feeds the moles and the moles feed the ratio,
  and the app used to ask for each one and then take it off the screen.

  `--text-1` rather than the pill's muted `--text-3`: this is a number the
  student is meant to READ and carry into the box in front of them, not a label
  saying which step they are on. Tabular figures so a column of digits does not
  wobble, and it wraps rather than truncating — a truncated intermediate is
  worse than none, because it looks like a number you could use.
*/
/*
  WHAT YOU HAVE SO FAR — the same numbers as the rail, for the student who has
  turned the rail off. Exactly one of the two is on screen at any time, decided
  here rather than in script, off the attribute `theme.js` writes before the
  first paint.

  `hidden` in the markup and un-hidden by JS when there is something to show, so
  it never appears as an empty promise at the first step; this rule then decides
  whether it may show at all. Both conditions have to hold, which is why the
  attribute selector uses `display` rather than removing the element.
*/
.so-far { display: none; }
:root[data-focus="on"] .so-far:not([hidden]) { display: block; margin: 0 0 1.25rem; }
.so-far > summary {
  /* A summary is a control a finger has to hit, and `min-height` on the default
     `display: list-item` is honoured — but the padding is what makes the whole
     row a target rather than just the words. */
  min-height: var(--touch);
  display: flex;
  align-items: center;
  padding: 0.4rem 0.2rem;
  font-weight: 600;
  color: var(--text-1);
  cursor: pointer;
}
.so-far-list {
  margin: 0.25rem 0 0;
  padding: 0 0 0 1.25rem;
  color: var(--text-1);
}
.so-far-list li { margin: 0 0 0.35rem; }
.so-far-name { color: var(--text-2); }
.so-far-value { font-weight: 600; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }

.rail-value {
  color: var(--text-1);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* ---------------- entering an answer ---------------- */

.stage-card { background: var(--surface-2); }
.stage-prompt { font-size: 1.1875rem; margin: 0 0 0.75rem; }

.field { margin: 0 0 1.5rem; border: 0; padding: 0; }
.field > label, .field > legend { display: block; font-weight: 600; margin-bottom: 0.25rem; padding: 0; }

input[type="text"] {
  width: 100%;
  min-height: var(--touch);
  padding: 0.6rem 0.75rem;
  font: inherit;
  font-size: 1.1875rem;
  color: var(--text-1);
  background: var(--surface-3);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
}

.answer { font-variant-numeric: tabular-nums; }

.coefficients {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.coefficient { display: flex; flex-direction: column; gap: 0.2rem; min-width: 6rem; flex: 1 1 6rem; }
.coefficient label { font-weight: 600; font-size: 0.9375rem; }
.coefficient input { text-align: center; }

.choices { display: flex; flex-wrap: wrap; gap: 0.6rem; }

.choice {
  /*
    DISPLAY FIRST, AND IT IS LOAD-BEARING. `min-height` does nothing at all to a
    non-replaced INLINE element, and a <label> is inline — so this floor only
    ever held where a .choice happened to be a flex item of `.choices`, and was
    silently ignored anywhere else. The report panel's symptom list was that
    anywhere else, and measured 42px against a 44px floor.
  */
  display: flex;
  align-items: center;
  min-height: var(--touch);
  padding: 0.6rem 1.1rem;
  font: inherit;
  color: var(--text-1);
  background: var(--surface-3);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
  cursor: pointer;
}
.choice-on { background: var(--accent-soft); font-weight: 650; box-shadow: inset 0 0 0 2px var(--rail); }

/*
  DISPLAY IS PART OF WHAT THESE CLASSES PROMISE, and leaving it out made them
  mean two different things. `min-height` is inert on an inline box. A <button>
  is inline-BLOCK by default so the floor applied; an <a> is inline, so the same
  class on an anchor produced a 36px control against a 44px floor — the class
  said 2.75rem and the anchor was having none of it.

  This is the `.choice` defect a second time, in a second place: that one was a
  42px row from `min-height` on an inline <label>. Setting display here is what
  makes "this is a button" true of the element rather than true of buttons.
*/
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch);
  padding: 0.7rem 1.3rem;
  font: inherit;
  font-weight: 600;
  color: var(--on-accent);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  cursor: pointer;
}
.button:hover { filter: brightness(1.06); }
.button-big { display: flex; width: 100%; font-size: 1.1875rem; min-height: 3.5rem; }
/*
  A COMPLETE BUTTON, not a size modifier — and it was a size modifier for
  fifteen releases. Every `class="button-small"` in the app carries that class
  ALONE, so none of the `.button` rule above applied to any of them and they
  were painted by the browser's own default button styling: #6b6b6b with white
  text in dark, #efefef with black in light, in both cases a cold grey against
  a warm palette and completely unmoved by which theme was chosen.

  It passed every gate. Contrast was fine — the UA colours are legible — and
  nothing else was looking. What found it was reverse-mapping each rendered
  colour to the role token it came from: 144 measurements resolved to no token
  at all, which is the shape of a colour arriving from outside the theme.

  These are SECONDARY buttons — Back, Copy it, Check, Look up a mistake — so
  they are painted as one rather than given the accent fill of the primary.
*/
.button-small {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 2.75rem, not 2.5. A 40px control is under the 44px floor a finger needs,
     and "small" is about how loud it looks rather than how hard it is to hit. */
  min-height: 2.75rem;
  padding: 0.5rem 0.9rem;
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--text-1);
  background: var(--surface-2);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
  cursor: pointer;
}
.button-small:hover { background: var(--surface-3); }
.button-quiet { color: var(--text-1); background: transparent; border-color: var(--rail); }

/*
  The work screen's head: how far through, and the way out. They share a row so
  the exit is beside the progress rather than hunted for at the bottom of a
  scrolling page.
*/
.work-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step);
  flex-wrap: wrap;
}
.work-head .progress { margin: 0; }
/* The armed state is a WORD CHANGE, not a colour change: "Leave — you will not
   get a code" says what the second tap does, and a red button only says that
   something is dangerous. */
.work-leave-armed { background: var(--accent-soft); font-weight: 700; }

/* The question and the way to hear it, on one line. */
.stage-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--step); flex-wrap: wrap; }
.stage-head .stage-prompt { margin: 0; flex: 1 1 14rem; }
.speak { flex: 0 0 auto; }

/* Focus lands here after a rejection, so it needs a ring like anything else
   that takes focus — a container with tabindex="-1" gets none by default. */
.feedback:focus-visible { outline: 3px solid var(--accent); outline-offset: 4px; border-radius: var(--radius); }

/*
  ---- what a code says ----

  A description list, because that is what it is: a thing the code carries, and
  what it says. Not a table — this is read on a phone, where columns are lost
  silently while the prose around them looks fine.
*/
.readout { margin: var(--step) 0 0; }
.readout dt {
  margin-top: calc(var(--step) * 1.25);
  color: var(--text-2);
  font-size: 0.9375rem;
}
.readout dd {
  margin: calc(var(--step) * 0.25) 0 0;
  color: var(--text-1);
  font-weight: 650;
}
.readout-not { margin: var(--step) 0 0; padding-left: 1.25rem; color: var(--text-2); }
.readout-not li { margin-bottom: calc(var(--step) * 0.5); }

/*
  A native select, which the APP deliberately never uses — its picker is radios,
  because a select on a ViewBoard opens a list nobody at the back can read. That
  argument is about a screen projected to a room. The teacher's own planning page
  is never projected, and a compact control there beats nine radio buttons.

  It still clears the touch floor, because she might be doing this on a phone —
  and unstyled it was 19px, which the accessibility gate caught the moment the
  warm-up builder existed.
*/
select {
  min-height: var(--touch);
  width: 100%;
  max-width: 22rem;
  padding: 0.5rem 0.75rem;
  font: inherit;
  color: var(--text-1);
  background: var(--surface-2);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
}

/* A thing the app noticed across several answers, not about one of them. */
.noticed {
  margin: var(--step) 0;
  padding: 0.9rem 1.1rem;
  background: var(--surface-1);
  border-left: 3px solid var(--rail);
  border-radius: var(--radius);
  color: var(--text-1);
}

/* ---------------- feedback ---------------- */

.feedback { max-width: var(--measure); margin: 0 auto 2rem; }
.feedback:empty { margin: 0 auto; }

.note { font-weight: 650; margin-bottom: 0.4rem; }
.note-good { color: var(--text-1); }
.note-wrong { color: var(--text-1); }

.why {
  background: var(--surface-1);
  border-left: 4px solid var(--rail);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  margin-bottom: 1rem;
}

.remediation {
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  margin-bottom: 1rem;
}
.remediation h3 { margin-top: 0; }
.remediation ol { margin-bottom: 0.6rem; }
.check { font-weight: 600; margin-bottom: 0; }

/* ---------------- the code ---------------- */

.code-card { background: var(--surface-2); text-align: center; }

.code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: clamp(1.25rem, 6vw, 2rem);
  font-weight: 600;
  letter-spacing: 0.06em;
  user-select: all;
  margin: 0.25rem 0 1rem;
  overflow-wrap: anywhere;
}

.summary { list-style: none; padding: 0; }
.summary li { padding: 0.4rem 0; border-bottom: 1px solid var(--hairline); }
.summary li:last-child { border-bottom: 0; }

/* ---------------- the ⓘ panel ---------------- */

.info {
  width: min(46rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  padding: 0;
  color: var(--text-1);
  background: var(--surface-1);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
}
.info::backdrop { background: rgba(0, 0, 0, 0.6); }

.info-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step);
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--hairline);
  position: sticky;
  top: 0;
  background: var(--surface-1);
}
.info-head h2 { margin: 0; font-size: 1.25rem; }

.info-body { padding: 1rem 1.25rem 1.5rem; overflow-y: auto; }
.info-section { margin-bottom: 1.5rem; }

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

/* ---------------- what changed, and the diagnostic ---------------- */

.release { margin-bottom: 1.25rem; }
.release h4 { margin: 0 0 0.3rem; font-size: 1rem; }

.diagnostic {
  margin-top: 1rem;
  padding: 0.85rem 1rem;
  background: var(--page-alt);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  color: var(--text-2);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.8125rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-x: auto;
  user-select: all;
}

/* ---------------- the teacher's decoder ---------------- */

textarea {
  width: 100%;
  padding: 0.6rem 0.75rem;
  font: inherit;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.9375rem;
  color: var(--text-1);
  background: var(--surface-3);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
  resize: vertical;
}

.results-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--step);
  flex-wrap: wrap;
}
.results-head h2 { margin-top: 0; }

.histogram { list-style: none; padding: 0; margin: 0 0 1rem; }

.histogram-row {
  display: grid;
  grid-template-columns: minmax(7rem, 10rem) 1fr auto;
  align-items: center;
  gap: 0.75rem;
  padding: 0.3rem 0;
}
.histogram-name { font-size: 0.9375rem; }
.histogram-track {
  height: 1.1rem;
  background: var(--page-alt);
  border: 1px solid var(--hairline);
  border-radius: 4px;
  overflow: hidden;
}
.bar {
  display: block;
  height: 100%;
  width: var(--fill, 0%);
  background: var(--accent);
}
.histogram-count { font-variant-numeric: tabular-nums; font-size: 0.9375rem; color: var(--text-2); }

.trouble { padding-left: 1.4rem; }
.trouble li { margin: 0.45rem 0; }

.student { background: var(--surface-2); margin-bottom: 0.75rem; }
.student h3 { margin-top: 0; }

/*
  On a narrow screen the histogram stops being a row and becomes a stack. A
  three-column grid at 380px wide puts two characters per line in the middle
  column, which is a chart nobody can read.
*/
@media (max-width: 30rem) {
  .histogram-row { grid-template-columns: 1fr auto; }
  .histogram-track { grid-column: 1 / -1; }
}

/* ---------------- print ---------------- */

@media print {
  /*
    A printed class list is read on a fixed-width page, at a desk, on paper. The
    reasons the screen avoids a grid — a tablet, a narrow viewport, a reader who
    might be holding it one-handed — none of them apply here, and a printed
    column of cards would run to six pages.
  */
  :root {
    --page: #fff;
    --page-alt: #fff;
    --surface-1: #fff;
    --surface-2: #fff;
    --surface-3: #fff;
    --text-1: #000;
    --text-2: #222;
    --text-3: #333;
    --rail: #000;
    --hairline: #999;
    --accent: #000;
    --accent-soft: #eee;
  }

  .chrome, .skip, #decode-form, #print, .update-strip, noscript { display: none !important; }
  body { padding: 0; font-size: 10.5pt; }
  main { padding: 0; }
  .screen { max-width: none; }
  .card { border: 1px solid var(--hairline); break-inside: avoid; }

  .student {
    display: inline-block;
    width: 48%;
    vertical-align: top;
    margin: 0 1% 0.5rem 0;
  }

  h1 { font-size: 16pt; }
  h2 { font-size: 13pt; page-break-after: avoid; }
  h3 { font-size: 11pt; page-break-after: avoid; }

  /* The bars survive: a printer that drops backgrounds would print an empty
     chart, so the fill is outlined as well as filled. */
  .histogram-track { border: 1px solid #000; }
  .bar { background: #000; }

  a[href]::after { content: ""; }
}

/*
  ---- a stacked radio group (the appearance picker, the symptom list) ----

  Radios rather than a select: one tap per option, and a native select on a
  board at the front of a room opens a list nobody at the back can read. The
  label is the target, so it clears --touch by containing the control rather
  than by being sized next to one.

  SCOPED TO `.theme-axis`, NOT TO THE PICKER. Every rule here used to be a
  descendant of `.theme-picker`, which read as tidy and meant that the second
  fieldset to use this markup — the report panel's symptom list — got the class
  name, none of the styling, and rows two pixels under the touch floor. The
  shape is general; the picker was only the first one to want it.
*/
.theme-axis {
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: var(--step) calc(var(--step) * 1.5) calc(var(--step) * 1.5);
  margin: 0 0 var(--step);
}
.theme-axis legend {
  padding: 0 calc(var(--step) * 0.5);
  color: var(--text-2);
  font-size: 0.9rem;
}
.theme-axis .choice {
  display: flex;
  align-items: center;
  gap: calc(var(--step) * 1.25);
  min-height: var(--touch);
  padding: 0 calc(var(--step) * 1.25);
  border-radius: var(--radius);
  cursor: pointer;
}
.theme-axis .choice:hover { background: var(--accent-soft); }
.theme-axis input[type="radio"] {
  width: 1.25rem;
  height: 1.25rem;
  accent-color: var(--accent);
  flex: 0 0 auto;
}
.theme-axis input[type="radio"]:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

/*
  ---- the first-run dialog ----

  THE ACTION BAR IS PINNED, and that is the whole point of the change. As a
  full-height screen this content pushed its own "Get started" below the fold on
  a real device, so the one control that mattered was the one thing nobody could
  see. Here the body scrolls inside the panel and the button stays put at every
  height.

  `100dvh` rather than `100vh`: on a phone browser `vh` is the height WITHOUT
  the address bar, so a panel sized against it is taller than the visible area
  and puts the bar back off screen — which is the same defect wearing a
  different hat.
*/
.welcome-dialog {
  display: none;
  max-height: min(calc(100dvh - 2rem), 52rem);
  flex-direction: column;
}
.welcome-dialog[open] { display: flex; }
.welcome-dialog .info-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.welcome-actions {
  flex: 0 0 auto;
  padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom));
  border-top: 1px solid var(--hairline);
  background: var(--surface-1);
}
.welcome-actions .button { width: 100%; }

/*
  ---- the three doors ----

  Big targets, stacked, each one saying what it is FOR rather than just naming
  itself. A menu of bare nouns makes a student guess which one produces the
  thing their teacher asked for, and guessing wrong in an assignment costs them
  a code they then cannot produce.
*/
.doors { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--step); }
.door {
  width: 100%;
  min-height: var(--touch);
  display: block;
  text-align: left;
  padding: calc(var(--step) * 1.75);
  color: var(--text-1);
  background: var(--surface-1);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
}
.door:hover { background: var(--surface-2); }
.door:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.door-name { display: block; font-size: 1.35rem; font-weight: 650; }
.door-what { display: block; margin-top: calc(var(--step) * 0.5); color: var(--text-2); }

/*
  The revealed answer. --accent-soft rather than a colour of its own: this is
  the app telling you something you asked for, which is the same voice as the
  algebra help, and a second highlight colour would imply a second meaning.
*/
.reveal {
  margin-top: var(--step);
  padding: 0.75rem 1rem;
  color: var(--text-1);
  background: var(--accent-soft);
  border-left: 4px solid var(--rail);
  border-radius: var(--radius);
}

/*
  ---- the lesson list and one lesson ----

  EVERY ROW IS A LIVE CONTROL, including the ones not yet finished and the ones
  already done. Nothing is locked here, and a greyed-out row would promise
  otherwise — the state is said in words instead, which a screen reader can also
  read and which does not depend on noticing a colour.
*/
.lessons { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--step); }
.lesson-row {
  width: 100%;
  min-height: var(--touch);
  display: block;
  text-align: left;
  padding: calc(var(--step) * 1.5);
  color: var(--text-1);
  background: var(--surface-1);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
}
.lesson-row:hover { background: var(--surface-2); }
.lesson-row:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.lesson-name { display: block; font-size: 1.15rem; font-weight: 650; }
.lesson-promise { display: block; margin-top: calc(var(--step) * 0.4); color: var(--text-2); }
.lesson-state { display: block; margin-top: calc(var(--step) * 0.5); color: var(--text-3); font-size: 0.9rem; }
.lesson-done { background: var(--accent-soft); }

/*
  ---- the periodic table ----

  A GRID THAT SCROLLS SIDEWAYS RATHER THAN SHRINKING. Eighteen columns on a
  phone means a cell about 18px wide, which is unreadable and untappable — and
  the touch floor is not negotiable just because a periodic table is wide. The
  cells stay a fixed, tappable size and the container scrolls, which is what a
  paper table on a desk does too.
*/
.info-wide { max-width: min(96vw, 76rem); }
.table-scroll { overflow-x: auto; padding-bottom: var(--step); }
.table-grid {
  display: grid;
  grid-template-columns: repeat(18, 3.25rem);
  grid-auto-rows: 3.25rem;
  gap: 2px;
  /* The f-block sits below a deliberate empty row; this keeps that gap from
     collapsing when the grid is only as tall as its content. */
  min-width: max-content;
}
.table-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2px;
  font: inherit;
  line-height: 1.1;
  color: var(--text-1);
  background: var(--surface-1);
  border: 1px solid var(--rail);
  border-radius: 4px;
  cursor: pointer;
  overflow: hidden;
}
.table-cell:hover { background: var(--surface-3); }
.table-z { font-size: 0.5625rem; color: var(--text-2); }
.table-symbol { font-size: 1rem; font-weight: 650; }
.table-mass { font-size: 0.5rem; color: var(--text-2); font-variant-numeric: tabular-nums; }
.table-s { background: var(--surface-2); }
.table-f { background: var(--accent-soft); }
.table-weight { font-variant-numeric: tabular-nums; }

/*
  ---- the calculator ----

  Twenty keys, each clearing the touch floor, in a grid that keeps the same
  shape on a phone and on a board at the front of a room.
*/
.calc-keys {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: calc(var(--step) * 0.75);
  margin: var(--step) 0;
}
.calc-key {
  min-height: var(--touch);
  display: flex;
  align-items: center;
  justify-content: center;
  font: inherit;
  font-size: 1.25rem;
  color: var(--text-1);
  background: var(--surface-2);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
  cursor: pointer;
}
.calc-key:hover { background: var(--surface-3); }
.calc-out {
  min-height: var(--touch);
  display: flex;
  align-items: center;
  margin: var(--step) 0 0;
  padding: 0 calc(var(--step) * 1.25);
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}
.calc-value { font-size: 1.375rem; font-weight: 650; color: var(--text-1); background: var(--accent-soft); border-radius: var(--radius); }
.calc-error { color: var(--text-2); }
#calc-entry { width: 100%; }

/*
  ---- the reference ----

  The same row shape as a lesson, because it is the same act: a list of things
  to open. A second visual language for a second list would say the two are
  different kinds of thing, and they are not.
*/
.reference { list-style: none; margin: 0; padding: 0; display: grid; gap: calc(var(--step) * 0.75); }
.reference-row {
  width: 100%;
  min-height: var(--touch);
  display: flex;
  align-items: center;
  text-align: left;
  padding: calc(var(--step) * 1.25);
  color: var(--text-1);
  background: var(--surface-1);
  border: 1px solid var(--rail);
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
}
.reference-row:hover { background: var(--surface-2); }
.reference-lessons { list-style: none; margin: var(--step) 0 0; padding: 0; display: grid; gap: var(--step); }

/* The route out of a diagnosis, so it reads as an offer rather than as the
   next thing to press instead of answering. */
.explain { margin-top: var(--step); }

/* Worked lines align on purpose — the units sit under each other, and that
   alignment is part of the explanation rather than decoration. */
.worked {
  margin: var(--step) 0;
  padding: 0.75rem 1rem;
  overflow-x: auto;
  color: var(--text-1);
  background: var(--surface-2);
  border-radius: var(--radius);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.95rem;
  line-height: 1.6;
}

.drills { list-style: decimal; margin: 0; padding-left: 1.5rem; display: grid; gap: calc(var(--step) * 1.5); }
.drill input { width: 100%; min-height: var(--touch); }
.drill-verdict {
  margin-top: calc(var(--step) * 0.75);
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  border-left: 4px solid var(--rail);
  background: var(--accent-soft);
  color: var(--text-1);
}
