/*
 * The design system — CLAUDE.md §8, translated. Every colour, size, radius, space and weight
 * in the app comes from a token declared here; a page that hard-codes a hex or a px is a page
 * that drifts, and the next change to any value below will miss it.
 *
 * Read in two layers. The PALETTE layer names what §8 names, so a value can be checked against
 * the design by eye. The ROLE layer says what a value is FOR: a ground, a border, an action,
 * a link. Where a role exists, rules use it and not the palette entry underneath — and for the
 * ACCENT that is absolute and enforced, because it is the one §8 rations. There is exactly one
 * --action token, and scripts/design-tokens-acceptance.js fails the build if a rule reaches past
 * it for raw terracotta, so "add another accent" is a visible edit to this file rather than a hex
 * pasted into a screen. Structural colours that are their own meaning — forest for headings, sage
 * for an eyebrow, mint for the selection — are named straight, because a role token that only
 * ever aliases one palette entry for one use is indirection, not a system. The BUTTON LADDER is
 * the counter-case and the reason that test is "one use" rather than "one hex": --primary is
 * --forest and nothing else today, but it is a rung that four classes and every future screen
 * pick by intent, so the alias is what lets the ladder move without hunting for buttons among
 * everything else forest does. scripts/button-ladder-acceptance.js fails the build if a .btn
 * rule reaches past those roles.
 *
 * Three things here are rules, not preferences:
 *
 * 1. THE FONTS ARE OURS. Both faces are self-hosted below. Our users are minors (§1), and a
 *    webfont request hands their IP address to a third party on every page load — so there is
 *    no link, no @import and no CDN to fonts.googleapis.com or fonts.gstatic.com, ever. The
 *    files were fetched once at authoring time and committed, licences beside them.
 * 2. ONE THEME, DECLARED. `color-scheme: light` is the half that gets forgotten: without it a
 *    browser in dark mode restyles form controls, scrollbars and the canvas behind the page, so
 *    inputs go dark under light-styled labels and a warm parchment design goes muddy. There is
 *    deliberately NO `prefers-color-scheme` branch — a dark theme for this palette is a product
 *    decision and a full pass, not a media query bolted on.
 * 3. NO SHADOWS. Cards are separated by a hairline and by ground colour. The design uses no
 *    drop shadow anywhere, and one shadow added locally re-reads every card on the page.
 */

/*
 * Both faces are variable and latin-subset. `font-weight: 100 900` describes the file's real
 * wght axis rather than a wish — each was checked against its own fvar table, not assumed.
 * Fraunces italic is a SEPARATE FILE and a genuine italic (italicAngle -16°), which is why
 * §8's tagline may be set in it: a browser asked to slant the roman face synthesises one, and
 * a synthesised Fraunces italic is a different letterform from the drawn one.
 *
 * Both also carry an `opsz` axis, left on the `font-optical-sizing: auto` default so display
 * sizes get the display drawing and 9px eyebrows get the small-size drawing without any page
 * having to ask.
 *
 * `unicode-range` is the latin block these files actually contain. Declaring it keeps a page
 * with no latin text from downloading them at all.
 */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('fonts/fraunces-latin-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url('fonts/fraunces-italic-latin-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('fonts/inter-latin-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  color-scheme: light;

  /* ---------------------------------------------------------------- palette (§8) ----
     Warm parchment ground under a deep forest, sage and mint doing the structural work,
     exactly one warm accent for action. Nothing here is pure white except cards — a white
     page reads as a different product. */

  --forest: #173B39;
  --forest-deep: #0F2B29;
  --paper: #F4EFE4;
  --surface: #FFFFFF;
  --ink: #2B2B27;

  --sage: #3E7A6D;
  --mint: #BFE0D3;
  --mint-wash: #F1F8F4;
  --mint-mid: #CFE8DD;

  /* The muted ladder is green-tinted all the way down, never neutral grey. Only --muted-dim
     clears 4.5:1 on paper (5.44:1); --muted is 3.85:1 and --muted-faint is 2.16:1, so those
     two are for hairlines, rules and decoration — not for text a reader has to get through. */
  --muted: #6B7C74;
  --muted-dim: #55645D;
  --muted-faint: #9AA8A1;

  --terracotta: #C97854;
  --terracotta-dk: #B96844;
  --terracotta-tint: #F2E4DA;
  --warm-chip: #FFF6EE;
  --warm-chip-mid: #E8D5C7;
  --warm-chip-deep: #E8C4AE;

  --line-colour: rgba(23, 59, 57, 0.10);

  /* ------------------------------------------------------------------- roles ----
     What each value is FOR — a ground, a border, an action, a link. Where one of these exists,
     rules use it rather than the palette entry underneath. */

  --bg-page: var(--paper);
  --bg-card: var(--surface);
  --bg-quiet: var(--mint-wash);        /* the SIT FIRST card, kind chips */
  --bg-quiet-mid: var(--mint-mid);
  --bg-warm: var(--warm-chip);         /* the warm chip, paired with its own border */
  --border-warm: var(--warm-chip-mid);
  --bg-rail: var(--forest);

  --text-colour: var(--ink);
  --text-muted-colour: var(--muted-dim);
  --text-on-forest: var(--surface);
  --text-on-forest-dim: var(--mint);

  --link: var(--sage);
  --link-hover: var(--forest);

  /* ---------------------------------------------------- the button ladder (§8) ----
     The design draws FOUR button treatments, and for a while this file shipped two — which
     made .btn terracotta and terracotta the default primary, so every screen anyone built came
     out shouting. The rungs below are named for the JOB a button does, never for the colour it
     happens to be, so the next screen picks by intent: a ground is chosen by asking "what does
     this button claim?", not by asking "what colour goes here?".

     Rung by rung, with the label contrast on its own ground measured rather than estimated:

       --primary          forest ground, white label     12.20:1   the ordinary primary
       --primary-on-forest  mint ground, forest label     8.61:1   the same, where the ground
                                                                   under it is the forest rail
                                                                   or a forest card
       --quiet-label      outlined, forest label         12.20:1   secondary
       --action           terracotta ground, white label  3.33:1   the ONE action the screen
                                                                   exists for — at most one

     The ladder is scope, not importance: a forest primary is the primary OF ITS CARD, and the
     terracotta one is the primary of the whole screen. Two terracotta buttons on a page is not
     twice the emphasis, it is none. */

  --primary: var(--forest);
  --primary-hover: var(--forest-deep);
  --text-on-primary: var(--surface);

  --primary-on-forest: var(--mint);
  --primary-on-forest-hover: var(--mint-mid);
  --text-on-primary-on-forest: var(--forest);

  /* The outlined rung carries its label in forest, not in sage. Sage is the LINK colour and it
     was doing this job by inheritance rather than by decision: at 4.35:1 on --paper it misses
     AA for a 15px semibold label, which is not large text by any reading of the rule. Forest is
     what the design draws here and it measures 12.20:1 on the surface. */
  --quiet-label: var(--forest);
  --quiet-label-hover: var(--forest-deep);
  --quiet-hover-bg: var(--bg-quiet);

  /* THE action colour, and the only one. In the whole dashboard it appears on the primary
     button, the eyebrow above the thing you are meant to do today, and the numerals counting
     the four tracks — that scarcity is what makes "Begin" findable on a page with forty other
     things on it. A second accent halves the value of the first, so if something new needs
     emphasis, take it from sage or mint before reaching here.
     Contrast, measured: --text-on-action on --action is 3.33:1. That clears AA for large text
     and UI components but not for a small label, and it is DELIBERATE — #29 asked whether to
     deviate from the design for contrast and Stephen ruled that the design stands. What keeps
     that ruling defensible is the ration itself: 3.33:1 is one label on one button per screen,
     not the ground under every primary in the product. scripts/button-ladder-acceptance.js is
     what turns that from a sentence in §8 into something the build enforces. */
  --action: var(--terracotta);
  --action-hover: var(--terracotta-dk);
  --action-quiet: var(--terracotta-tint);
  --text-on-action: var(--surface);

  --focus-ring: var(--sage);
  --focus-ring-on-forest: var(--mint);

  /* One hairline, everywhere. On mint surfaces it becomes the mint edge instead. Cards are
     separated by these and by ground colour — never by shadow. */
  --line: var(--line-colour);
  --hairline: 1px solid var(--line-colour);
  --hairline-mint: 1px solid var(--mint);

  /* ---------------------------------------------------------------- type ----
     §8's scale. Fraunces is display only: page titles, section headings, card numerals, the
     italic tagline. Inter is everything else. The mono face is for machine metadata — the
     `flow: g9.on-time / grade: 9 / pace: standard` block — and nothing a member has to read. */

  --font-display: 'Fraunces', Georgia, serif;
  --font-ui: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --text-title: 34px;
  --text-wordmark: 25px;
  --text-section: 23px;
  --text-card: 19px;
  --text-subhead: 18px;
  --text-tagline: 17px;

  /* --text is the PROSE FLOOR, and 15px rather than §8's "13px interface default" on purpose.
     §8's scale describes the densest interface chrome in the design; §8's rules that outrank
     the pixels say running prose a student actually reads sits at 14–15px, and a parent reading
     the same screen is often in their forties or fifties. So: prose starts at --text and grows.
     --text-ui and below are chrome — labels, chips, counts, meta — and per §8 none of them may
     ever be the ONLY carrier of a status, a count or an instruction. */
  --text: 15px;
  --text-body: 14px;
  --text-ui: 13px;
  --text-support: 12px;
  --text-meta: 11px;
  --text-eyebrow: 10px;
  --text-micro: 9px;

  --leading-prose: 1.6;
  --leading-prose-loose: 1.65;
  --leading-tight: 1.25;
  --leading-flat: 1;

  /* Eyebrows are 9–10px, and the tracking is what makes them legible at that size. Setting one
     without it produces mush. */
  --track-eyebrow: 0.12em;
  --track-eyebrow-wide: 0.30em;
  /* The kind chip on a curriculum unit row is drawn at 9px / 0.14em (#12) — inside §8's
     0.12–0.30em band, and its own value rather than a rounding of the eyebrow's. */
  --track-chip: 0.14em;

  /* 400 / 500 / 600. There is no bold-bold in this design. */
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;

  /* -------------------------------------------------------------- shape ----
     Radii by role, so "which corner radius" is answered by what the thing is. */

  --radius-chip: 4px;
  --radius-control: 8px;
  --radius-panel: 12px;
  --radius-panel-lg: 14px;
  --radius-card: 16px;
  --radius-badge: 20px;
  --radius-pill: 999px;
  --radius-avatar: 50%;

  /* Spacing runs on a loose 2px grid. Named by value: the value IS the vocabulary here, and a
     numbered ladder would only add a lookup between the design and the rule. */
  --space-6: 6px;
  --space-8: 8px;
  --space-10: 10px;
  --space-12: 12px;
  --space-14: 14px;
  --space-16: 16px;
  --space-18: 18px;
  --space-20: 20px;
  --space-22: 22px;
  --space-24: 24px;
  --space-28: 28px;

  /* 44px minimum on interactive targets, in BOTH dimensions, whatever the visual box looks
     like. The design's chips are visually small; their hit areas are not. */
  --tap-min: 44px;

  /* The shell: a fixed dark rail against the paper content column. */
  --rail-width: 236px;
  --rail-padding: 26px var(--space-16) 22px;

  /* The feedback panel (#148): a column over the page she is already on, not a page of its own.
     Wider than the rail because it holds an editable issue body, and capped so it never becomes
     the page — at 860px and below it is the page, and the media query below says so. */
  --panel-width: 380px;

  /* Inside the rail the page's hairline is invisible — --line is forest at 10% ON forest. So
     the rail gets its own edge and its own hover/current grounds, all derived from --mint with
     color-mix rather than pasted as new hexes: one mint moves and the whole rail moves with it.
     Measured against --forest: the current-item ground is 1.8:1, which is a TINT and not a
     signal. That is why .rail__item[aria-current] also carries a solid mint edge bar and a
     heavier weight — §8 forbids meaning carried by colour alone, and a 1.8:1 wash is barely
     colour at all. */
  --line-rail: color-mix(in srgb, var(--mint) 20%, transparent);
  --hairline-rail: 1px solid var(--line-rail);
  --bg-rail-hover: color-mix(in srgb, var(--mint) 10%, transparent);
  --bg-rail-current: color-mix(in srgb, var(--mint) 22%, transparent);

  /* The mint pill badge: --mint ground, --forest text, 8.6:1. Named as a pair so a screen
     cannot take the ground without the ink that was measured against it. */
  --bg-badge: var(--mint);
  --text-on-badge: var(--forest);

  /* A disabled control on the rail still has to be READ — the parent-view toggle ships
     unwired (#19) and its state is the whole point of it being visible. 4.93:1 on --forest. */
  --text-on-forest-off: var(--muted-faint);
}

* { box-sizing: border-box }

/* `hidden` is how every script in this app shows and hides a thing, and the attribute's UA
   default is only `display: none` — which any rule that sets `display` silently beats. The
   avatar did exactly that and shipped as an empty mint disc next to "Not signed in". */
[hidden] { display: none !important }

body {
  margin: 0;
  background: var(--bg-page);
  color: var(--text-colour);
  font: var(--weight-regular) var(--text)/var(--leading-prose) var(--font-ui);
  -webkit-text-size-adjust: 100%;
}

::selection { background: var(--mint); color: var(--forest) }

/* The one layout token here, kept because both existing pages already consume it. The shell
   proper — the 236px rail against the content column — belongs to the page that builds it. */
.wrap { max-width: 720px; margin: 0 auto; padding: var(--space-28) var(--space-20) }

/* Display type runs at 1–1.25 and prose at 1.6–1.65. Nothing in between reads as either. */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: var(--weight-semibold);
  color: var(--forest);
  line-height: var(--leading-tight);
  margin: 0 0 var(--space-12);
}
h1 { font-size: var(--text-title) }
h2 { font-size: var(--text-section); margin-top: var(--space-28) }
h3 { font-size: var(--text-card); font-weight: var(--weight-medium) }

p { margin: 0 0 var(--space-16); max-width: 68ch }

a { color: var(--link); text-decoration-thickness: 1px; text-underline-offset: 2px }
a:hover { color: var(--link-hover) }

.muted { color: var(--text-muted-colour) }

/* The wordmark: Fraunces over a mint EDUCATION at eyebrow size. Both halves are present at
   full size somewhere on the page, so the 9px line is never carrying the name on its own. */
.wordmark {
  font-family: var(--font-display);
  font-size: var(--text-wordmark);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-flat);
  color: var(--text-on-forest);
}
.wordmark span {
  display: block;
  margin-top: var(--space-6);
  font-family: var(--font-ui);
  font-size: var(--text-micro);
  font-weight: var(--weight-medium);
  letter-spacing: var(--track-eyebrow-wide);
  text-transform: uppercase;
  color: var(--text-on-forest-dim);
}

/* An eyebrow is DECORATION over information that is also present at full size nearby (§8).
   Never the only place a status, a count or an instruction appears. */
.eyebrow {
  font-size: var(--text-eyebrow);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--sage);
}
.eyebrow--action { color: var(--action) }

/* Fraunces has a drawn italic and this is what it is for. */
.tagline {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text-tagline);
  font-weight: var(--weight-regular);
  line-height: var(--leading-prose);
  color: var(--sage);
}

/* Machine metadata only — never a member-facing sentence. */
.meta {
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  color: var(--text-muted-colour);
}

.card {
  background: var(--bg-card);
  border: var(--hairline);
  border-radius: var(--radius-card);
  padding: var(--space-20);
  margin: 0 0 var(--space-16);
}
.card--quiet { background: var(--bg-quiet); border: var(--hairline-mint) }

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-10);
  border: var(--hairline);
  border-radius: var(--radius-chip);
  background: var(--bg-quiet);
  color: var(--forest);
  font-size: var(--text-support);
  font-weight: var(--weight-medium);
  line-height: var(--leading-flat);
}
.chip--warm { background: var(--bg-warm); border-color: var(--border-warm) }
.badge { border-radius: var(--radius-badge); padding: var(--space-6) var(--space-12) }

/* A chip that DOES something keeps its small visual box and gets a full 44px hit area from
   this overlay, rather than growing to look like a button. Without it the design's chips are
   ~26px tall and get missed on touch.
   The overlay extends past the visual box, so a row of chips needs at least 44px of pitch —
   pack two rows closer than that and the upper chip's hit area eats the lower one's taps. */
a.chip, button.chip, .chip[role='button'] { position: relative; cursor: pointer }
a.chip::after, button.chip::after, .chip[role='button']::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: var(--tap-min);
  min-width: 100%;
  height: var(--tap-min);
}

/* .btn is SHAPE ONLY — geometry, type and the 44px hit area — and it deliberately declares no
   ground and no label colour. It used to declare terracotta, and that one line is the whole of
   what issue #45 was about: a base that grounds itself in the accent makes `class="btn"` the
   easy thing to type and the loudest thing on the page, so the accent stopped being rationed by
   anything except how often someone remembered §8.
   A .btn with no rung is therefore a BUILD FAILURE (scripts/button-ladder-acceptance.js), not a
   visual default. It still renders legibly — forest ink on whatever ground it sits over — so a
   half-written local page is readable while you work, but it does not ship. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding: 0 var(--space-20);
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  font: var(--weight-semibold) var(--text)/var(--leading-flat) var(--font-ui);
  text-decoration: none;
  cursor: pointer;
}

/* RUNG 1 — the ordinary primary. The action a card or a section is for: "Watch now" inside the
   lesson card, "Save grade" under the grade form. Reach for this one by default. */
.btn--primary { background: var(--primary); color: var(--text-on-primary) }
.btn--primary:hover { background: var(--primary-hover); color: var(--text-on-primary) }

/* RUNG 2 — the same primary where the ground under it is forest: the rail, or a forest card
   like the live-session block the design puts "Save my seat" on. Rung 1 on forest would be a
   dark button on a dark card, which is why this rung exists rather than being a hover state. */
.btn--on-forest { background: var(--primary-on-forest); color: var(--text-on-primary-on-forest) }
.btn--on-forest:hover {
  background: var(--primary-on-forest-hover);
  color: var(--text-on-primary-on-forest);
}

/* RUNG 3 — secondary: outlined, no ground of its own, so it takes the surface beneath it.
   "Open track", "Preview what's coming". Emphasis here comes from the hairline and the label,
   never from a second accent. */
.btn--quiet {
  background: transparent;
  color: var(--quiet-label);
  border-color: var(--line);
}
.btn--quiet:hover { background: var(--quiet-hover-bg); color: var(--quiet-label-hover) }

/* RUNG 4 — the one action the screen exists for, and the name is the rule: at most one per
   screen, which scripts/button-ladder-acceptance.js counts. "Begin". If a second thing on the
   page needs emphasis it is rung 1, not a second one of these — see the ladder note in :root. */
.btn--only { background: var(--action); color: var(--text-on-action) }
.btn--only:hover { background: var(--action-hover); color: var(--text-on-action) }

/* The landing page's two actions side by side: the one the page exists for and the way in
   for a returning member (#53). A flex row rather than inline whitespace so the two 44px boxes
   keep a real gap, and it wraps so a narrow screen stacks them instead of squeezing either
   below --tap-min. Layout only — each button's ground comes from its rung. */
.landing__actions { display: flex; flex-wrap: wrap; gap: var(--space-12) }

/* Never rely on the browser default here: a focus ring removed by a reset and not replaced is
   a keyboard user locked out of the page. Sage is invisible against the forest rail, so
   anything sitting on forest re-colours the ring to mint rather than losing it. */
:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px }
.on-forest :focus-visible { outline-color: var(--focus-ring-on-forest) }

/* ==========================================================================================
   THE SHELL — the frame every surface renders inside (§8, issue #10)

   A 236px fixed rail in --forest against the --paper content column. Three things here are
   rules rather than styling, and each has a guard in scripts/app-shell-acceptance.js:

   1. EVERY RAIL ITEM IS 44px IN BOTH DIMENSIONS. The design's nav rows and chips are visually
      smaller than that; the hit area is not. Where the visual box has to stay small the extra
      comes from padding, never from shrinking --tap-min.
   2. THE RAIL COLLAPSES INTO A LABELLED CONTROL, NOT A HAMBURGER. Below the breakpoint the
      rail becomes a top bar and the nav goes behind a button that says the word "Menu" and
      carries a count of what is behind it — §8: label the control, and nothing disappears
      without a visible count and a visible way back. The bars glyph is decoration beside the
      word, never instead of it.
   3. NOTHING TICKS. The next-drop block is rendered once, in whole days. There is no timer
      anywhere in the shell — §1's constraint, not a style preference.

   The breakpoint below is the one number here that is not a token, and cannot be: a media
   query cannot read a custom property. It is the width at which 236px of rail stops leaving a
   usable content column. */

.shell {
  min-height: 100vh;
}

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

.shell__rail {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
  width: var(--rail-width);
  padding: var(--rail-padding);
  overflow: hidden;
  background: var(--bg-rail);
  color: var(--text-on-forest);
}

/* The rail is the one dark surface in the app, so the sage focus ring disappears on it. The
   .on-forest class in common.css re-colours the ring to mint; it is on the rail element and
   must stay there. Removing it does not look like anything — it only breaks keyboard use. */
.shell__rail .eyebrow { color: var(--text-on-forest-dim) }

/* The wordmark is a link home, so it carries the 44px floor like every other target — it
   cleared it only incidentally, on the height of two lines of type, which a copy change or a
   scale tweak takes away without anything noticing. In the narrow top bar it is also a flex
   item beside the Menu button, so the width floor is the half that matters there. */
.rail__wordmark {
  display: block;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  text-decoration: none;
  padding-block: var(--space-6);
}
.rail__wordmark:hover { color: var(--text-on-forest) }

/* --------------------------------------------------------------------------- the nav --- */

.rail__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* A row that is 44px tall because --tap-min says so, not because the text happens to fill it.
   `display: flex` with min-height is deliberate over a fixed height: the label wraps at narrow
   widths and the row grows rather than clipping. */
.rail__item {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  min-height: var(--tap-min);
  padding: var(--space-8) var(--space-12);
  border-left: 3px solid transparent;
  border-radius: var(--radius-control);
  color: var(--text-on-forest);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  text-decoration: none;
}
a.rail__item:hover { background: var(--bg-rail-hover); color: var(--text-on-forest) }

/* Current page: a ground tint AND a solid mint edge AND a heavier weight. The tint alone is
   1.8:1 against the rail — decoration, not a signal. aria-current carries it for assistive
   tech; the edge bar carries it for everyone else. */
.rail__item[aria-current] {
  background: var(--bg-rail-current);
  border-left-color: var(--mint);
  font-weight: var(--weight-semibold);
}

/* A section that has no page yet is LISTED, not omitted — §8: nothing disappears without a
   visible way back, and an absent row reads as "my things are gone". It is not a link, so it
   is not focusable and cannot be a dead end; the pill beside it says why. */
.rail__item--pending { color: var(--text-on-forest-dim); font-weight: var(--weight-regular) }

/* A door that does something HERE rather than going somewhere (#148). It is a button, so it
   needs the resets a button brings with it — the rail's rows are otherwise links — and it is
   full-width and left-aligned so the row is indistinguishable from the ones above it. The
   pending row's dimming is deliberately NOT inherited: this door works. */
.rail__item--action {
  width: 100%;
  margin: 0;
  border-top: 0;
  border-right: 0;
  border-bottom: 0;
  background: none;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.rail__item--action:hover { background: var(--bg-rail-hover); color: var(--text-on-forest) }

.rail__label { flex: 1 1 auto }

.rail__badge {
  flex: 0 0 auto;
  min-width: var(--space-22);
  padding: var(--space-6) var(--space-10);
  border-radius: var(--radius-badge);
  background: var(--bg-badge);
  color: var(--text-on-badge);
  font-size: var(--text-meta);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-flat);
  text-align: center;
}
.rail__badge--quiet {
  background: transparent;
  border: var(--hairline-rail);
  color: var(--text-on-forest-dim);
  font-weight: var(--weight-medium);
}

/* The empty state is a first-class case, not an afterthought: with nothing seeded the badges
   are all absent, and an absence needs a sentence or it reads as a broken rail. */
.rail__note {
  margin: var(--space-10) 0 0;
  padding-inline: var(--space-12);
  color: var(--text-on-forest-dim);
  font-size: var(--text-support);
  line-height: var(--leading-prose);
  max-width: none;
}

/* The Staff section's list (#116). `.rail__block > *` zeroes the margins inside a block, which
   is right for the lines of prose the other blocks hold and wrong for a nav list that needs to
   clear its own eyebrow — so the list, and only the list, gets it back. Everything else the
   section paints is the rail's existing vocabulary: .rail__item for the 44px rows, the pending
   treatment for a door whose screen is not built, .rail__block-note for the sentence. */
.rail__block .rail__list { margin-top: var(--space-6) }

/* ------------------------------------------------------- next drop, account, parent --- */

.rail__block {
  padding-top: var(--space-18);
  border-top: var(--hairline-rail);
}
.rail__block > * { margin: 0 }
.rail__block-title {
  margin-top: var(--space-8);
  font-size: var(--text);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  color: var(--text-on-forest);
  max-width: none;
}
/* The date line is the information; the 10px eyebrow above it is decoration over information
   that is also present at full size (§8). So this never drops into the eyebrow tier. */
.rail__block-value {
  margin-top: var(--space-6);
  font-size: var(--text-body);
  color: var(--text-on-forest-dim);
  max-width: none;
}
.rail__block-note {
  margin-top: var(--space-10);
  font-size: var(--text-support);
  line-height: var(--leading-prose);
  color: var(--text-on-forest-dim);
  max-width: none;
}
.rail__block-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--text-on-forest-dim);
  font-size: var(--text-body);
}
.rail__block-link:hover { color: var(--text-on-forest) }

/* THE WAY OUT (#117). §8's rung 3 is the JOB — a secondary control, not the action the screen
   exists for — and rung 3's treatment is an outlined forest label, which on the forest rail
   would be a label the colour of its own ground. So it takes the rail's own quiet treatment,
   the one the signed-out block's Sign in link already uses: same colour, same 44px floor,
   sharing .rail__block-link rather than restating it. What is left here is the chrome a
   <button> brings and a link does not. It is a button and not a link because it POSTs. */
.rail__signout {
  min-height: var(--tap-min);
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--text-body);
  text-align: left;
  cursor: pointer;
}
/* In flight. The disabled colour is the one the rail already declares for a control that is
   visible and not currently usable, and scripts/app-shell-acceptance.js measures it on forest. */
.rail__signout[disabled] { color: var(--text-on-forest-off); cursor: default }

.rail__identity {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  margin-top: var(--space-10);
}
.rail__identity .rail__block-title { margin-top: 0 }
.rail__avatar {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: var(--space-28);
  height: var(--space-28);
  border-radius: var(--radius-avatar);
  background: var(--bg-badge);
  color: var(--text-on-badge);
  font-size: var(--text-support);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-flat);
}

/* The parent-view control ships VISIBLE AND UNWIRED on purpose. What guardian view renders is
   #19 and depends on the parent projection in #14; a switch that flips and changes nothing
   would be worse than one that says it is not ready. Its state is spelled out in words next to
   it — a greyed pixel is not a state anyone can read. */
.rail__switch {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  width: 100%;
  min-height: var(--tap-min);
  margin-top: var(--space-10);
  padding: var(--space-8) var(--space-10);
  border: var(--hairline-rail);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-on-forest-off);
  font: var(--weight-medium) var(--text-body)/var(--leading-tight) var(--font-ui);
  text-align: left;
}
.rail__switch-track {
  flex: 0 0 auto;
  width: var(--space-28);
  height: var(--space-16);
  border: var(--hairline-rail);
  border-radius: var(--radius-pill);
}
.rail__switch-knob {
  display: block;
  width: var(--space-10);
  height: var(--space-10);
  margin: var(--space-6) 0 0 var(--space-6);
  border-radius: var(--radius-avatar);
  background: var(--text-on-forest-off);
}
/* Label and state stack rather than competing for one line: "Not available yet" is the point
   of this control existing right now, so it does not get squeezed to fit beside the name. */
.rail__switch-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.rail__switch-state {
  font-size: var(--text-meta);
  font-weight: var(--weight-regular);
}

/* #19 WIRED THE CONTROL. It is a LINK to /app/parent.html now, not a switch that changes
   nothing, so the base rule above — which still carries the geometry and the not-yet ink — is
   no longer the whole story. This modifier is what a wired one looks like: a full-strength
   label and the knob over to the right, so the difference between "not ready" and "open it" is
   a shape and a word and not a shade of grey (§8: never encode meaning in colour alone). */
.rail__switch--open { color: var(--text-on-forest); border-color: var(--mint) }
.rail__switch--open .rail__switch-track { border-color: var(--mint) }
.rail__switch--open .rail__switch-knob { margin-left: var(--space-14); background: var(--mint) }

/* ------------------------------------------------------------------ the mobile toggle --- */

/* Hidden on desktop because the nav is already open there — never because the control is
   optional. Below the breakpoint it is the only way back to the nav, so it is a real button
   with a real word in it. */
.rail__toggle { display: none }

/* The rail's lower half: everything the Menu button reveals at narrow widths. Grouped so the
   collapse is one rule rather than six, and so the wordmark and the toggle never collapse. */
.shell__drawer {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-20);
}

/* The whole rail is taller than a 800px-high laptop, so something has to scroll — and WHICH
   part scrolls is a content decision, not a layout one. The nav and the next drop scroll; the
   account block and the parent-view rule stay pinned to the foot, because the sentence about
   what a parent can and cannot see is the privacy contract between two people using this
   account, and a contract nobody scrolls to is a contract nobody read.
   min-height: 0 is what lets a flex child actually shrink below its content — without it the
   rail grows instead and the foot is pushed off the bottom, which is the bug this replaced. */
.rail__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* Whichever of the two actually scrolls — .rail__scroll in the rail, the whole drawer in the
   narrow top bar — carries this. A default light scrollbar track down a forest surface reads
   as a rendering fault, and colouring only the desktop scroller left the phone one white. */
.rail__scroll,
.shell__drawer {
  scrollbar-width: thin;
  scrollbar-color: var(--mint) var(--forest);
}
/* THE FOLD, ANNOUNCED (#123). The region above scrolls, and on a 900px window a rail with every
   staff door in it is taller than the window — so rows sit below the fold with nothing but a
   scrollbar thumb macOS hides to say so. §8: anything that hides content owes a visible count
   and a visible control. shell.js injects this one, counts the rows under the fold from the DOM
   and unhides it only when the region actually overflows; everything here is the appearance.

   `sticky` rather than `absolute`: an absolutely positioned child of a scroll container is
   positioned against the CONTENT, so it scrolls away with it — the one thing this control must
   never do. Sticky pins it to the lower edge while there is anything below and lets it come to
   rest under the last row when there is not, which is why nothing is ever permanently covered.

   The ground is the rail's own and OPAQUE, because rows scroll underneath it. That is also why
   the hover state moves the ink and the edge rather than the background: a translucent hover
   here would show the row passing behind the label. */
.rail__fold {
  position: sticky;
  bottom: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  width: 100%;
  min-height: var(--tap-min);
  margin: 0;
  padding: var(--space-8) var(--space-12);
  border: 0;
  border-top: var(--hairline-rail);
  border-radius: 0;
  background: var(--bg-rail);
  color: var(--text-on-forest-dim);
  font-family: inherit;
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  cursor: pointer;
}
.rail__fold:hover { color: var(--text-on-forest); border-top-color: var(--mint) }

/* The fade, so the cut reads as a cut and not as the end of the list. It hangs ABOVE the
   control (bottom: 100%) and takes no pointer events, so it dims the rows passing under the
   edge without covering anything you can press. --bg-rail is --forest; the gradient is named
   through the role token so the rail's ground moves in one place. */
.rail__fold::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: var(--space-24);
  background: linear-gradient(to top, var(--bg-rail), transparent);
  pointer-events: none;
}

.rail__foot {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-20);
}

/* ------------------------------------------------------------------ the content column --- */

.shell__main {
  margin-left: var(--rail-width);
  padding: var(--space-28) var(--space-24);
}
.shell__content { max-width: 720px }

.shell__error {
  padding: var(--space-12) var(--space-16);
  border: var(--hairline);
  border-radius: var(--radius-panel);
  background: var(--bg-card);
  color: var(--text-colour);
}

/* ==========================================================================================
   FORMS — the primitives every credentialled surface reuses (§8, issue #34)

   Four of these are rules rather than styling:

   1. A LABEL IS AN ELEMENT, NOT A PLACEHOLDER. Placeholder text disappears the moment somebody
      starts typing, which is exactly when a form is hardest to read back — and it is grey on a
      ground by definition, so it fails contrast at the one size it is used. `.field` stacks a
      real <label> over its control, and no input in this app carries a placeholder at all.
   2. THE CONTROL CARRIES THE 44px FLOOR ITSELF. An input sized by its font is about 32px tall,
      which looks correct in a screenshot and gets missed by a thumb.
   3. THE ERROR REGION IS PART OF THE FORM, NOT A LAYER OVER IT. A toast is gone before a slow
      reader reaches it and cannot be returned to; this sits in the flow, above the fields it
      is about, and stays there until the next attempt.
   4. THE BUSY STATE IS WORDS. There is no spinner token here and no spinner anywhere in this
      app — an icon doing a label's job (§8), at the one moment a reader most needs telling
      what is happening. The button's own text changes; this rule only carries the ground.
*/

.form { display: flex; flex-direction: column; gap: var(--space-18) }

.field { display: flex; flex-direction: column; gap: var(--space-6) }

/* --text-body and not the 13px interface default: a label is a word somebody has to read in
   order to know what to type, and §8 puts running text a reader depends on above that tier. */
.field__label {
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  color: var(--forest);
  line-height: var(--leading-tight);
}

.field__note {
  font-size: var(--text-support);
  color: var(--text-muted-colour);
  line-height: var(--leading-prose);
}

/* A fact the form owes the reader BEFORE they commit to it, rather than a note about the field
   above (#160). At --text and not --text-support, because §8 puts running prose a reader
   depends on at the body tier and this is the sentence that stops somebody expecting to start
   tonight; on the quiet ground so it reads as the page talking rather than as a validation
   message. Nothing warm and nothing accented: it is neither a warning nor the action. */
.form__note {
  margin: 0 0 var(--space-18);
  padding: var(--space-14) var(--space-16);
  border: var(--hairline-mint);
  border-radius: var(--radius-panel);
  background: var(--bg-quiet);
  font-size: var(--text);
  color: var(--text-colour);
  line-height: var(--leading-prose);
}

.input {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-10) var(--space-12);
  border: var(--hairline);
  border-radius: var(--radius-control);
  background: var(--bg-card);
  color: var(--text-colour);
  font: var(--weight-regular) var(--text)/var(--leading-tight) var(--font-ui);
}
.input:hover { border-color: var(--sage) }

/* The submit on a single-action form is the one place full width is right: it is the only
   action on the page, so the width is hit area rather than a second claim on the eye. */
.btn--block { width: 100% }

/* Busy darkens whatever rung the button is, rather than assuming terracotta: the base carries
   no ground now, so a single rule here would have repainted a forest submit orange the moment
   it started working. The cursor is the part that is true of every rung. */
.btn[aria-busy='true'] { cursor: progress }
.btn--only[aria-busy='true'] { background: var(--action-hover) }
.btn--primary[aria-busy='true'] { background: var(--primary-hover) }
.btn--on-forest[aria-busy='true'] { background: var(--primary-on-forest-hover) }
.btn--quiet[aria-busy='true'] { background: var(--quiet-hover-bg) }

/* Failure sits ON the form and is never carried by colour alone: the text says what went
   wrong and what to do next, and the block is bordered and grounded so it reads as a block
   whatever a reader's colour vision. The warm chip rather than the action colour — an error
   is not an invitation to click, and terracotta is rationed to the thing you came to do. */
.form-error {
  margin: 0;
  padding: var(--space-12) var(--space-14);
  border: var(--hairline);
  border-color: var(--border-warm);
  border-radius: var(--radius-panel);
  background: var(--bg-warm);
  color: var(--text-colour);
  font-size: var(--text-body);
  line-height: var(--leading-prose);
}

/* A statement of fact rather than a failure — "you are already signed in". Quiet mint, so it
   cannot be mistaken for either the error above it or the action below it. */
.form-notice {
  margin: 0;
  padding: var(--space-12) var(--space-14);
  border: var(--hairline-mint);
  border-radius: var(--radius-panel);
  background: var(--bg-quiet);
  color: var(--text-colour);
  font-size: var(--text-body);
  line-height: var(--leading-prose);
}

/* On its own line rather than inline in the sentence, because a link inside running text is
   about 20px tall and §8's 44px floor has no exception for "it is only a link". Blocking it
   out is the only way to give it the height without spacing the prose apart to get it. */
.form-notice__link {
  display: flex;
  align-items: center;
  width: fit-content;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
}

/* ==========================================================================================
   THE SIGN-IN PAGE (issue #34)

   Deliberately NOT inside the shell. The rail is a signed-in member's frame: it lists sections
   this visitor cannot open and an account block whose whole content is the state this page
   exists to change. What stands in for it is the wordmark and a link back to the front door,
   so the page is somewhere you can leave from rather than a dead end.
*/

.signin {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-20);
  min-height: 100vh;
  padding: var(--space-28) var(--space-20);
}

.signin__card {
  width: 100%;
  /* The width one column of form fields wants. A layout number in a rule, like .wrap's 720px
     above it, and the only one on this page. */
  max-width: 420px;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-18);
}

/* Off the rail the wordmark is forest on paper for BOTH halves. Mint on paper is a 1.3:1 wash,
   and sage on paper measures 4.35:1 — below AA at 9px, which is #29 and Stephen's call. A
   second screen does not get to settle that by picking a colour, so this page paints neither.
   It is also a LINK home, so it carries the 44px floor in both dimensions like every other
   target — the rail's copy cleared it only incidentally, on the height of its two lines. */
.wordmark--on-paper {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  align-self: start;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  color: var(--forest);
  text-decoration: none;
}
.wordmark--on-paper span { color: var(--forest) }

.signin__intro {
  margin: 0;
  color: var(--text-muted-colour);
}

.signin__title { margin: 0 }

/* The forgotten-password answer. It is a paragraph and not a link because there is no reset
   route to link to (#34): a "Forgot your password?" pointing at nothing is the dead end §8
   forbids, and the true sentence is a better next step than a 404. */
.signin__help {
  margin: 0;
  padding-top: var(--space-16);
  border-top: var(--hairline);
  font-size: var(--text-body);
  color: var(--text-muted-colour);
  line-height: var(--leading-prose);
}
/* The question heads its own line at every width. Run inline, it reads as a sentence that
   happens to start in bold, and at phone width the answer starts mid-line after it. */
.signin__help strong {
  display: block;
  margin-bottom: var(--space-6);
  color: var(--forest);
  font-weight: var(--weight-semibold);
}

.signin__back {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding: 0 var(--space-8);
  font-size: var(--text-body);
}

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

/* For a word a screen reader needs and the design does not draw — "3" in a pill is a count of
   nothing until something says what of. Never used to hide a word sighted users also need. */
.u-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Off-screen until focused, then a real target at the top of the page. */
.skip {
  position: absolute;
  top: var(--space-10);
  left: var(--space-10);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: 0 var(--space-16);
  border-radius: var(--radius-control);
  background: var(--bg-card);
  color: var(--link);
  transform: translateY(-200%);
}
.skip:focus { transform: none }

/* ----------------------------------------------------------------------- narrow widths --- */

/* 236px of rail plus a usable column stops fitting here. Below it the rail becomes a top bar
   and the nav goes behind the labelled Menu button. */
@media (max-width: 860px) {
  .shell__rail {
    position: sticky;
    inset: 0 0 auto 0;
    width: auto;
    max-height: 100vh;
    gap: var(--space-16);
    padding: var(--space-12) var(--space-16) var(--space-16);
  }

  .rail__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-12);
  }

  .rail__toggle {
    display: flex;
    align-items: center;
    gap: var(--space-8);
    min-height: var(--tap-min);
    min-width: var(--tap-min);
    padding: var(--space-8) var(--space-14);
    border: var(--hairline-rail);
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--text-on-forest);
    font: var(--weight-medium) var(--text-body)/var(--leading-flat) var(--font-ui);
    cursor: pointer;
  }
  .rail__toggle:hover { background: var(--bg-rail-hover) }
  .rail__toggle-count {
    color: var(--text-on-forest-dim);
    font-size: var(--text-meta);
    font-weight: var(--weight-regular);
  }

  /* The bars are drawn beside the word "Menu", never instead of it. currentColor rather than a
     token so the glyph cannot drift away from the label it sits next to. */
  .rail__toggle-bars {
    flex: 0 0 auto;
    width: var(--space-16);
    height: var(--space-12);
    background:
      linear-gradient(currentColor 0 0) 0 0 / 100% 2px no-repeat,
      linear-gradient(currentColor 0 0) 0 50% / 100% 2px no-repeat,
      linear-gradient(currentColor 0 0) 0 100% / 100% 2px no-repeat;
  }

  /* In the top bar there is no "foot" to pin anything to — the bar is as tall as its content,
     capped at the viewport — so the split above collapses back into one scrolling column.
     Undoing `overflow` is NOT enough, and the half-undone version drew the account block on
     top of the next drop at 400×740: .rail__scroll is still `flex: 1 1 auto; min-height: 0`
     from the base, so the flex algorithm shrinks it below its own content — which is exactly
     what min-height: 0 is there to permit — and with overflow visible the content spills out
     and paints over the foot. Both halves have to come off together. */
  .shell__drawer { overflow-y: auto }
  .rail__scroll {
    flex: 0 0 auto;
    min-height: auto;
    overflow: visible;
  }
  /* Below the breakpoint the DRAWER is the scroller and .rail__scroll is not one at all
     (above), so a control announcing that region's fold would point at a scroll that does not
     exist. shell.js's measurement reaches the same answer on its own; this is the half that
     does not depend on a script having run. */
  .rail__fold { display: none }

  .shell[data-nav='closed'] .shell__drawer { display: none }

  .shell__main {
    margin-left: 0;
    padding: var(--space-24) var(--space-16);
  }
}

/* ==========================================================================================
   THE DASHBOARD HOME — the masthead, today's check-in, and the tracks (§8, issue #31)

   Three things here are rules, and scripts/dashboard-acceptance.js holds each of them:

   1. PROGRESS IS TWO COUNTS, NEVER A BAR. `4 of 46 done` is a numeral and a sentence. There is
      no meter, no width that grows, no percentage — §1's constraint, and the reason the number
      is display type rather than a fill.
   2. TERRACOTTA IS SPENT WHERE THE DESIGN SPENDS IT AND NOWHERE ELSE: the eyebrow over the
      lesson you are meant to sit with today, and the numerals that count the tracks. Both reach
      it through --action. The third place is the "Begin" button, which arrived with the lesson
      page (#87) and is this screen's one rung-4 button — over an open lesson and nowhere else.
   3. THE 9–10px TIER CARRIES NOTHING ALONE. The date line is a fact, so it sits at
      --text-support in --text-muted-colour (5.44:1 on paper) rather than in the eyebrow tier;
      the eyebrows inside the cards sit over a full-size title, on --surface, where sage measures
      4.99:1 and terracotta the 3.33:1 Stephen ruled on in #29.
*/

.dash__masthead { margin-bottom: var(--space-24) }

.dash__date {
  margin: 0 0 var(--space-8);
  font-size: var(--text-support);
  font-weight: var(--weight-medium);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted-colour);
}
.dash__greeting { margin-bottom: var(--space-6) }
.dash__tagline { margin: 0 0 var(--space-20) }

/* The badge and the count share a row and wrap under each other on a phone. Baseline-aligned
   so the 34px numeral and the 14px sentence read as one line. */
/* The next-drop fact, repeated out of the rail (#38). Body size because it is prose somebody
   has to read, --text-muted-colour for the sentence and forest for the date inside it: the date
   is the fact, and it is emphasised by weight and ink rather than by a second accent. */
.dash__drop {
  margin: 0 0 var(--space-12);
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.dash__drop-when { font-weight: var(--weight-semibold); color: var(--forest) }

.dash__status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-12) var(--space-20);
}
.dash__badge {
  display: inline-flex;
  align-items: center;
  padding: var(--space-8) var(--space-14);
  border-radius: var(--radius-pill);
  background: var(--bg-badge);
  color: var(--text-on-badge);
  font-size: var(--text-ui);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-flat);
}
.dash__progress {
  display: flex;
  align-items: baseline;
  gap: var(--space-8);
  margin: 0;
}
.dash__progress-numeral {
  font-family: var(--font-display);
  font-size: var(--text-title);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-flat);
  color: var(--forest);
}
.dash__progress-rest {
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted-colour);
}

/* The sentence every empty, signed-out and could-not-load state is made of. Body size, never
   the 12px support tier: it is the only thing on screen in those states, so it is prose. */
.dash__note {
  margin: 0;
  flex: 1 1 100%;
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.dash__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
}

/* ---------------------------------------------------------------- today's check-in --- */

.checkin { display: flex; flex-direction: column; gap: var(--space-10) }
.checkin > .eyebrow { margin: 0 }
/* The question as a Fraunces heading, at section size rather than card size: it is the one
   thing the page asks the reader to do, and it is the largest type inside any card. */
.checkin__title {
  margin: 0;
  font-size: var(--text-section);
  font-weight: var(--weight-semibold);
}
.checkin__body { margin: 0; font-size: var(--text); line-height: var(--leading-prose-loose) }
/* The Begin button sits in its own block so the card's flex gap owns the space above it rather
   than a margin on the anchor. The anchor is already 44px in both dimensions from `.btn`. */
.checkin__action { margin: 0; display: flex }

.checkin__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  margin: 0;
}

/* ---------------------------------------------------------------------- the tracks --- */

.tracks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-16);
}
.tracks__empty { margin: 0 }
.track {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  padding: var(--space-20);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
}
/* The numeral is decoration beside an ordered list; the list's own order is what a screen
   reader gets. At 23px semibold it is large text, where --action's 3.33:1 clears AA. */
.track__numeral {
  font-family: var(--font-display);
  font-size: var(--text-section);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-flat);
  color: var(--action);
}
.track__title { margin: 0; font-size: var(--text-card) }
/* The track's question on the card (#151) — the same italic display face the curriculum page
   gives it, one tier down because the card's title is one tier down. The card is a flex column
   with no action in it, so the line costs height and moves nothing. */
.track__question {
  margin: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text);
  font-weight: var(--weight-regular);
  line-height: var(--leading-prose);
  color: var(--forest);
}
.track__summary { margin: 0; font-size: var(--text-body); color: var(--text-muted-colour) }
.track__count { margin: 0; font-size: var(--text-body); font-weight: var(--weight-medium); color: var(--forest) }
.track__next { margin: 0; font-size: var(--text-body); color: var(--text-muted-colour) }

@media (max-width: 860px) {
  .tracks { grid-template-columns: minmax(0, 1fr) }
}

/* ------------------------------------------------ next live session, and the rooms --- */
/* The live panel is the one forest surface in the content column — the design draws it that
   way, and it is what rung 2 of the ladder is for: "Save my seat" is mint on forest (8.61:1),
   never a second terracotta. Text on it takes --text-on-forest / --text-on-forest-dim, the same
   pair the rail uses, because --text-muted-colour is a paper colour and disappears here.
   `.on-forest` is on the element for the focus ring; removing it does not look like anything
   and only breaks keyboard use. */

.card--forest {
  background: var(--bg-rail);
  border-color: transparent;
  color: var(--text-on-forest);
}
.card--forest .eyebrow { color: var(--text-on-forest-dim) }

.live { display: flex; flex-direction: column; gap: var(--space-10) }
.live > .eyebrow { margin: 0 }
.live__title {
  margin: 0;
  font-size: var(--text-card);
  color: var(--text-on-forest);
}

/* The time is the panel's fact, so it is full size and not the support tier. It carries the
   zone name because "6:00 PM" alone is a different hour on a laptop set to somewhere else. */
.live__when {
  margin: 0;
  font-size: var(--text);
  font-weight: var(--weight-medium);
  color: var(--text-on-forest);
}
.live__meta { margin: 0; font-size: var(--text-body); color: var(--text-on-forest-dim) }
.live__body,
.live__note {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-on-forest-dim);
}
.live__saved {
  margin: 0;
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  color: var(--text-on-forest);
}
.live__action { margin: 0; display: flex }

/* The way back out of a saved seat (§8: nothing without one). A text control rather than a
   fifth button treatment — the ladder is four rungs and a fifth is a decision, not a rule at
   the bottom of a stylesheet — carrying the 44px floor like every other target here. */
.live__release {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: 0;
  border: 0;
  background: none;
  font: var(--weight-medium) var(--text-body)/var(--leading-flat) var(--font-ui);
  color: var(--text-on-forest-dim);
  text-decoration: underline;
  cursor: pointer;
}
.live__release:hover { color: var(--text-on-forest) }

.rooms__card { display: flex; flex-direction: column; gap: var(--space-10) }
.rooms__card > .eyebrow { margin: 0 }
.rooms__title { margin: 0; font-size: var(--text-card) }

.rooms { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-12) }
.room {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding-bottom: var(--space-12);
  border-bottom: var(--hairline);
}
.room:last-child { padding-bottom: 0; border-bottom: 0 }
/* Each room on the dashboard is a LINK into the rooms page (#127), so the row carries the
   44px floor itself — a three-line row clears it comfortably and a one-line room with no
   description would not. */
.room__link {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-6);
  min-height: var(--tap-min);
  color: var(--text-colour);
}
.room__link { text-decoration: none }
.room__title { text-decoration: underline }
.rooms__all { margin: 0 }
.room__title { font-size: var(--text); font-weight: var(--weight-semibold); color: var(--forest) }
.room__summary { font-size: var(--text-body); line-height: var(--leading-prose); color: var(--text-muted-colour) }
.room__count { font-size: var(--text-body); color: var(--text-muted-colour) }

/* ==========================================================================================
   THE CURRICULUM BROWSER — track tabs, unit rows, and *why this order* (§8, issue #12)

   Four things here are rules, and scripts/curriculum-browser-acceptance.js holds each:

   1. THE TABS NEVER LEAVE THE SCREEN. Four tracks at 375px wrap into two rows of two; they do
      not scroll off the edge and they do not fold behind a control. A track a reader cannot see
      is a track that has disappeared (§8), and an unlabelled disclosure fails the same rule.
   2. A UNIT THAT IS NOT OPEN YET IS DRAWN LIKE ONE THAT IS. Same title, same note, same weight;
      the only difference is the state line under it, and that line says when — never how long
      is left. Dimming a row into the background is the visual form of "tantalising" (§1).
   3. THE 9px KIND CHIP IS DECORATION. The kind is repeated at --text-support in the meta line
      beside the length, so the chip is never the only place a reader learns what a unit is.
   4. MONOSPACE IS FOR THE PACING BLOCK AND NOTHING ELSE. `.why__meta` takes --font-mono through
      `.meta`; no other rule on this screen may reach for it (§8).
*/

.cur { max-width: 720px }
.cur__head { margin-bottom: var(--space-20) }
.cur__title { margin-bottom: var(--space-6) }
/* The year's theme, in Meghan's words (#151). Display face at subhead size, so it reads as the
   title's second line rather than as chrome — never an eyebrow, which §8 forbids as the only
   carrier of anything — and in --forest, because --sage is 4.35:1 on paper and misses AA here. */
.cur__theme {
  margin: 0 0 var(--space-10);
  font-family: var(--font-display);
  font-size: var(--text-subhead);
  font-weight: var(--weight-regular);
  line-height: var(--leading-tight);
  color: var(--forest);
}
.cur__intro { margin: 0 0 var(--space-16); color: var(--text-muted-colour) }

/* The sentence every empty, signed-out and could-not-load state is made of. Body size, never
   the 12px support tier: it is the only thing on screen in those states, so it is prose. */
.cur__note {
  margin: 0 0 var(--space-8);
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.cur__link,
.gradelist__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  font-size: var(--text);
  font-weight: var(--weight-medium);
}
.gradelist { list-style: none; margin: 0; padding: 0 }

.cur__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-24);
}

/* ---------------------------------------------------------------------- the tabs --- */

.tracktabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
}

/* A real button with the 44px floor in both dimensions. Selected is carried three ways —
   aria-selected for assistive tech, a solid forest edge and a heavier weight for everyone —
   because the ground tint alone is decoration, not a signal (§8). */
.tracktab {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  gap: var(--space-10);
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding: var(--space-10) var(--space-12);
  border: var(--hairline);
  border-bottom: 3px solid transparent;
  border-radius: var(--radius-control);
  background: var(--bg-card);
  color: var(--forest);
  font: var(--weight-medium) var(--text-body)/var(--leading-tight) var(--font-ui);
  text-align: left;
  cursor: pointer;
}
.tracktab:hover { background: var(--bg-quiet) }
.tracktab[aria-selected='true'] {
  background: var(--bg-quiet);
  border-bottom-color: var(--forest);
  font-weight: var(--weight-semibold);
}
/* The numeral is decoration beside an ordered tablist; forest, not the accent — this screen has
   no single action to spend terracotta on, and four of them would be none (§8). */
.tracktab__numeral {
  flex: 0 0 auto;
  font-family: var(--font-display);
  font-size: var(--text-card);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-flat);
}
.tracktab__text { display: flex; flex-direction: column; gap: 2px; min-width: 0 }
.tracktab__count {
  font-size: var(--text-support);
  font-weight: var(--weight-regular);
  color: var(--text-muted-colour);
}

/* --------------------------------------------------------------------- the units --- */

.cur__track-title { margin-top: 0 }
/* The question the track asks, in Meghan's words (#151). Fraunces has a drawn italic and a
   question in her voice is what it is for; at --text-tagline it is reading size, which is what
   keeps it out of the eyebrow tier §8 rules against. */
.cur__track-question {
  margin: 0 0 var(--space-8);
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--text-tagline);
  font-weight: var(--weight-regular);
  line-height: var(--leading-prose);
  color: var(--forest);
}
.cur__track-summary { margin: 0 0 var(--space-20); line-height: var(--leading-prose-loose) }

.units {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: var(--hairline);
}
.unit {
  display: flex;
  gap: var(--space-12);
  padding: var(--space-16) 0;
  border-bottom: var(--hairline);
}

/* The completion mark: a ring, filled with a check when done, dotted when started. Always
   aria-hidden — the words in .unit__state are the record and the mark is drawn over them. */
.unit__mark {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: var(--space-22);
  height: var(--space-22);
  margin-top: 2px;
  border: 2px solid var(--muted-faint);
  border-radius: var(--radius-avatar);
  color: var(--text-on-primary);
  font-size: var(--text-support);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-flat);
}
.unit__mark--done { background: var(--forest); border-color: var(--forest) }
.unit__mark--done::after { content: '✓' }
.unit__mark--started { border-color: var(--forest) }
.unit__mark--started::after {
  content: '';
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-avatar);
  background: var(--forest);
}

.unit__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--space-6) }
.unit__head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-8) }
.unit__title {
  margin: 0;
  font-family: var(--font-ui);
  font-size: var(--text);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  color: var(--forest);
}

/* The kind chip as the design draws it: --mint-wash ground, --mint border, 9px / 0.14em. */
.unit__kind {
  display: inline-flex;
  align-items: center;
  padding: var(--space-6) var(--space-8);
  border: var(--hairline-mint);
  border-radius: var(--radius-chip);
  background: var(--bg-quiet);
  color: var(--forest);
  font-size: var(--text-micro);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--track-chip);
  line-height: var(--leading-flat);
  text-transform: uppercase;
}

.unit__meta { margin: 0; font-size: var(--text-support); color: var(--text-muted-colour) }
.unit__note { margin: 0; font-size: var(--text-body); line-height: var(--leading-prose) }
.unit__why { margin: 0; font-size: var(--text-body); line-height: var(--leading-prose); color: var(--text-muted-colour) }
.unit__why-label { font-weight: var(--weight-semibold); color: var(--forest) }
.unit__tags { display: flex; flex-wrap: wrap; gap: var(--space-6); margin: 0; padding: 0; list-style: none }

/* The state line is words, at body size, in the muted ink — and forest once the unit is open or
   done, which is the one place the row's colour changes. Never a lock icon on its own. */
.unit__state { margin: 0; font-size: var(--text-body); font-weight: var(--weight-medium); color: var(--text-muted-colour) }
.unit[data-availability='open'] .unit__state,
.unit[data-mark='done'] .unit__state { color: var(--forest) }

/* ----------------------------------------------------------------- why this order --- */

.why { margin: 0; padding: var(--space-20) }
.why__title { margin-top: 0; font-size: var(--text-card) }
.why__prose { margin: 0 0 var(--space-12); font-size: var(--text); line-height: var(--leading-prose-loose) }
.why__prose--none { color: var(--text-muted-colour) }

/* `flow / grade / joined / pace / drip` — the one monospace block in the product (§8). */
.why__meta {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-6) var(--space-12);
  margin: var(--space-16) 0;
  padding: var(--space-12) var(--space-14);
  border: var(--hairline-mint);
  border-radius: var(--radius-panel);
  background: var(--bg-card);
}
.why__line { display: flex; gap: var(--space-6) }
.why__line dt::after { content: ':' }
.why__line dd { margin: 0; color: var(--forest) }

.why__pending { margin: 0 0 var(--space-12); font-size: var(--text-body); line-height: var(--leading-prose); color: var(--text-muted-colour) }
.why__unlock { margin: 0; font-size: var(--text); font-weight: var(--weight-medium); color: var(--forest) }

/* Wide enough for the note to sit beside the list rather than under it. A layout number, like
   .wrap's 720px, and the tabs span both columns so they never share a row with the aside. */
@media (min-width: 1160px) {
  .cur { max-width: 980px }
  .cur__body { grid-template-columns: minmax(0, 1fr) 300px }
  .cur__tabs { grid-column: 1 / -1 }
  .why { align-self: start; position: sticky; top: var(--space-24) }
}

@media (max-width: 860px) {
  /* Two per row at phone width, every one of them on screen. */
  .tracktab { flex-basis: calc(50% - var(--space-8)) }
}

/* ==========================================================================================
   THE LESSON — the header, the player, SIT FIRST, the private exercise slot, completion and
   the "Then:" pointer (§1, §8, issue #13)

   Four things here are rules, and scripts/lesson-acceptance.js holds each of them:

   1. THE PLAYER IS ONE BOX WITH TWO CONTENTS. Cloudflare Stream's embed fills it at 16:9; the
      sentence that stands in for it (not open yet, not ready, not for this sign-in) sits in
      the same forest box at prose size, so a member reads WHY there is no video rather than
      looking at an empty rectangle. Nothing in the box is fixed to a height in px.
   2. SIT FIRST IS THE DESIGN'S CARD, EXACTLY: --mint-wash ground, --mint edge, 14px radius,
      Meghan's sentence at the prose floor — decorated by an eyebrow, never carried by one.
   3. THE ACCENT IS SPENT ONCE, on "Mark complete", and only while there is something to mark.
      A locked lesson, a finished one and a guardian's view spend none.
   4. A LOCKED LESSON IS DRAWN LIKE AN OPEN ONE, with its state said in words in the header and
      in the player box. Nothing dims, nothing is absent, nothing counts down (§1).
*/

.lesson__head { margin-bottom: var(--space-20) }

/* The track and the grade over the title — a fact at --text-support in --text-muted-colour
   (5.44:1 on paper), never the 10px eyebrow tier on --paper (#29). */
.lesson__kicker {
  margin: 0 0 var(--space-8);
  font-size: var(--text-support);
  font-weight: var(--weight-medium);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted-colour);
}
.lesson__title { margin-bottom: var(--space-8) }
.lesson__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-6) var(--space-8);
  margin: 0;
  max-width: none;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}

/* The sentence every signed-out, not-found and could-not-load state is made of. Body size,
   never the 12px support tier: it is the only thing on screen in those states, so it is prose. */
.lesson__note {
  margin: 0 0 var(--space-8);
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.lesson__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
}

/* ------------------------------------------------------------------------ the player --- */

/* Forest, like the rail, so the embed's own dark chrome and the stand-in sentence share one
   ground; .on-forest recolours the focus ring to mint for the iframe and any control in it. */
.player {
  margin: 0 0 var(--space-16);
  border-radius: var(--radius-panel-lg);
  overflow: hidden;
  background: var(--bg-rail);
  color: var(--text-on-forest);
}
.player__frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
}
/* As tall as its words. A fixed 16:9 box under a four-line sentence at phone width clips the
   fourth line, and a sentence is the whole point of this state. */
.player__state {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  padding: var(--space-24);
}
.player__text {
  margin: 0;
  max-width: 52ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-on-forest);
}
.player__sub {
  margin: 0;
  max-width: 52ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-on-forest-dim);
}

/* ------------------------------------------------------------------------- sit first --- */

.sitfirst {
  margin: 0 0 var(--space-16);
  padding: var(--space-20);
  border: var(--hairline-mint);
  border-radius: var(--radius-panel-lg);
  background: var(--bg-quiet);
}
.sitfirst .eyebrow { margin: 0 0 var(--space-8) }
.sitfirst__body {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose-loose);
  color: var(--text-colour);
}

/* ---------------------------------------------------------------------- the exercise --- */

.exercise { display: flex; flex-direction: column; gap: var(--space-8) }
.exercise .eyebrow { margin: 0 }
.exercise__body {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}

/* The box itself (#132). It takes .input for its border, radius, ground and hit area, and
   overrides the two things a control-sized font gets wrong for prose somebody writes for
   twenty minutes: --text rather than the 13px interface default, and the prose leading. The
   height is `rows`, not a px — a member dragging it taller is the only person who knows how
   much they have to say, which is also why `resize` stays vertical rather than none. */
.exercise__field { display: flex; flex-direction: column; gap: var(--space-6) }
.exercise__input {
  display: block;
  resize: vertical;
  font-family: var(--font-ui);
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-colour);
}
.exercise__state {
  margin: 0;
  font-size: var(--text-support);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.exercise__action { margin: 0 }

/* Greyed and not gone (§6): the box keeps its border, its words and its place in the tab
   order, and loses only the contrast that says "type here". No opacity and no filter — a
   dimmed control is unreadable rather than quiet, and --text-muted-colour is 5.44:1. */
.is-pending .exercise__input {
  background: var(--bg-quiet);
  color: var(--text-muted-colour);
}

/* ------------------------------------------------------------- completion, and then --- */

.lesson__complete {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-8);
  margin: 0 0 var(--space-24);
}
.lesson__done {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--forest);
  font-weight: var(--weight-medium);
}

.lesson__then {
  margin: 0;
  padding-top: var(--space-16);
  border-top: var(--hairline);
  max-width: none;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.lesson__then-label { color: var(--forest); font-weight: var(--weight-semibold) }
.lesson__then-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  font-weight: var(--weight-medium);
}

/* ==========================================================================================
   THE COLLEGE SEARCH (issue #18)
   ==========================================================================================

   A page whose whole design argument is subtraction. Every college-search tool a student has
   ever seen sorts by a number, and §1 says this product exists to be the opposite of that —
   so there is no ordered list here, no badge with a figure in it, no colour that means better.
   What is left is a search field, chips built from the catalogue's own vocabulary, and cards
   that all look exactly alike, because they are all exactly as good as each other until the
   member decides otherwise.

   Two consequences for the rules below. THE SAVE MARK IS NOT THE ACCENT: §8 rations terracotta
   to the one action a screen exists for, and a mark repeated on every card is the opposite of
   rationed — it is mint-on-forest-label when on, outlined when off, and it carries the word
   either way. And NOTHING HERE IS A PROPORTION: no bar, no ring, no fill that grows.
   ------------------------------------------------------------------------------------------ */

.col { max-width: 720px }
.col__head { margin-bottom: var(--space-24) }
.col__title { margin-bottom: var(--space-12) }

/* #18's framing sentence, and it is the most important prose on the page — so it is set at the
   prose floor in forest (12.20:1 on paper), not at the 17px sage tagline size, which measures
   4.35:1 and would put the one sentence a fourteen-year-old most needs to read at the one
   contrast in this palette that misses AA. The mint rule marks it as the page's own voice. */
.col__framing {
  margin: 0;
  padding-left: var(--space-14);
  border-left: 3px solid var(--mint);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose-loose);
  color: var(--forest);
}

.col__note {
  margin: var(--space-20) 0 var(--space-12);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.col__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--forest);
  font-weight: var(--weight-medium);
}

.col__body { display: flex; flex-direction: column; gap: var(--space-24) }

/* An empty result column is a SENTENCE, never a blank — and it sits in a card so the column
   does not look like it stopped loading. */
.col__empty {
  margin: 0;
  padding: var(--space-20);
  border: var(--hairline-mint);
  border-radius: var(--radius-card);
  background: var(--bg-quiet);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--forest);
}

/* ------------------------------------------------------------------- the search field --- */

.search { display: flex; flex-direction: column; gap: var(--space-8) }
.search__label { color: var(--forest) }
.search__row { display: flex; flex-wrap: wrap; gap: var(--space-10) }
.search__input { flex: 1 1 260px; min-width: 0 }
.search__go { flex: 0 0 auto }

/* ------------------------------------------------------------------------- the chips --- */

.facets { display: flex; flex-direction: column; gap: var(--space-14) }
.facets__group { display: flex; flex-direction: column; gap: var(--space-8) }

/* A label, not an eyebrow: which axis a row of chips filters on is information, and §8 says a
   fact that exists only at 9px has failed the parent reading the same screen. */
.facets__lead {
  margin: 0;
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  color: var(--forest);
}
.facets__row { display: flex; flex-wrap: wrap; gap: var(--space-8) }

/* Visually a pill; a 44px target in both dimensions whatever it looks like (§8). The pressed
   state is a ground AND a tick AND a weight, because meaning may never ride on colour alone —
   and at 1.8:1 a mint wash on white is barely colour at all. */
.chipbtn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding: var(--space-10) var(--space-16);
  border: var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--bg-card);
  color: var(--forest);
  font: var(--weight-regular) var(--text-body)/var(--leading-flat) var(--font-ui);
  cursor: pointer;
}
.chipbtn:hover { background: var(--bg-quiet) }
.chipbtn[aria-pressed='true'] {
  background: var(--bg-badge);
  border-color: var(--mint);
  color: var(--text-on-badge);
  font-weight: var(--weight-semibold);
}
.chipbtn[aria-pressed='true']::before { content: '✓'; font-size: var(--text-body) }

/* The visible count and the visible way back that §8 owes for anything that hides content. */
.facets__state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-12);
  margin: 0;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.facets__clear {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: 0;
  border: 0;
  background: none;
  color: var(--forest);
  font: var(--weight-medium) var(--text-body)/var(--leading-flat) var(--font-ui);
  text-decoration: underline;
  cursor: pointer;
}

/* ------------------------------------------------------------------------- the cards --- */

/* A single column, and that is a product decision rather than a layout one: a grid invites the
   eye to compare cell against cell, which is the comparison this page exists not to offer.
   Separated by hairline and ground, never by shadow (§8). */
.colleges { display: flex; flex-direction: column; gap: var(--space-16) }

.college {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
  padding: var(--space-20);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
}
.college__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-12);
}
.college__name {
  margin: 0;
  flex: 1 1 200px;
  min-width: 0;
  font-family: var(--font-display);
  font-size: var(--text-card);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  color: var(--forest);
}
.college__where {
  margin: 0;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-colour);
}
/* Who runs it, how big it feels, what is around it — at prose size, because these are the
   facts a person came for and a 9px chip may never be the only carrier of one (§8). */
.college__facts {
  margin: 0;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}

.college__why {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding: var(--space-14);
  border-radius: var(--radius-panel);
  background: var(--bg-quiet);
}
.college__why > .eyebrow { margin: 0 }
.college__blurb {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose-loose);
  color: var(--forest);
}
.college__blurb--absent { color: var(--text-muted-colour) }

.college__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

.college__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-16);
  margin: 0;
  padding-top: var(--space-12);
  border-top: var(--hairline);
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
/* Wikipedia is CC BY-SA and the attribution is a condition of use, not a footnote (#63). */
.college__source { color: var(--text-muted-colour) }
.college__src-link { color: var(--forest) }
.college__site {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--forest);
  font-weight: var(--weight-medium);
}

/* --------------------------------------------------------------------- the save mark --- */

/* NOT the accent. §8 rations terracotta to the one action a screen exists for, and this one
   repeats on every card — so it is outlined when off and the mint badge pair when on, and it
   carries the word "Save" or "Saved" either way: an icon belongs next to a word, never
   instead of one, and a state carried by a filled shape alone is a state carried by colour. */
.savemark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  flex: 0 0 auto;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding: var(--space-10) var(--space-16);
  border: 1px solid var(--forest);
  border-radius: var(--radius-pill);
  background: var(--bg-card);
  color: var(--forest);
  font: var(--weight-medium) var(--text-body)/var(--leading-flat) var(--font-ui);
  cursor: pointer;
}
.savemark:hover { background: var(--bg-quiet) }
.savemark[aria-pressed='true'] {
  background: var(--bg-badge);
  border-color: var(--mint);
  color: var(--text-on-badge);
}
.savemark[aria-busy='true'] { cursor: progress }
.savemark__glyph::before { content: '☆' }
.savemark[aria-pressed='true'] .savemark__glyph::before { content: '★' }

/* ------------------------------------------------------------------- the saved list --- */

.saved {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
  padding: var(--space-20);
  border: var(--hairline-mint);
  border-radius: var(--radius-card);
  background: var(--bg-quiet);
}
.saved__title {
  margin: 0;
  font-size: var(--text-section);
  line-height: var(--leading-tight);
  color: var(--forest);
}
.saved__note {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.saved__note--warn { color: var(--forest) }

.saved__list { display: flex; flex-direction: column; gap: var(--space-8); margin: 0; padding: 0; list-style: none }
.saved__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  padding: var(--space-12) var(--space-14);
  border-radius: var(--radius-panel);
  background: var(--bg-card);
}
.saved__text { flex: 1 1 200px; min-width: 0; display: flex; flex-direction: column; gap: 2px }
.saved__name { margin: 0; font-size: var(--text); font-weight: var(--weight-medium); color: var(--forest) }
.saved__where { margin: 0; font-size: var(--text-body); color: var(--text-muted-colour) }

/* At 375px the head row and the meta row stop trying to sit side by side; nothing is hidden,
   nothing scrolls sideways, and every target keeps its 44px. */
@media (max-width: 860px) {
  .college { padding: var(--space-16) }
  /* The head becomes a column, so `flex: 1 1 200px` on the name stops meaning "at least 200px
     of WIDTH" and starts meaning 200px of HEIGHT — which drew a school's name at the top of a
     200px void with the save mark stranded at the bottom of it. A flex basis is on the main
     axis, and the main axis just turned. */
  .college__head { flex-direction: column; align-items: stretch }
  .college__name { flex: 0 0 auto }
  .savemark { justify-content: center }
  .college__meta { flex-direction: column; align-items: flex-start; gap: var(--space-8) }
  .saved { padding: var(--space-16) }
  .search__go { width: 100% }
}

/* ==========================================================================================
   THE SIGN-UP PAGES, AND THE PRE-GUARDIAN STATE (issue #107, §1 q6, §6)

   Two form primitives and one screen state, kept together because one issue introduced them
   and one suite governs them.

   1. `.choice` IS A QUESTION WITH NO DEFAULT ANSWER. The sign-up form's first field decides
      whether a minor's row or an adult's row is written, so it ships with neither radio
      checked — which means the control has to READ as a question rather than as a setting
      somebody has already answered. It is drawn as two bordered rows, each its own 44px
      target, with the label beside the control and never instead of it (§8).

   2. `.check` IS THE CONSENT RECORD. One checkbox, and the label is running prose at
      --text-body rather than a 12px aside: what it says is the whole of §1 q6, and a rule a
      guardian is asked to accept at 9px is a rule nobody read.

   3. `.is-pending` IS GREYED, NOT GONE. §6 is explicit that a student waiting for their
      guardian sees the curriculum VISIBLE and greyed with exactly one action, and that it must
      not read as a wall with a price behind it. So this is --text-muted-colour and reduced
      emphasis on content that is still there, still scrollable and still readable — never
      `display: none`, never a blur, never an overlay. The explanation sits at --text, the size
      §8 says running prose a reader depends on sits at, and the one action is a real button.
*/

.choice {
  margin: 0;
  padding: 0;
  border: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

/* A legend is the question. --text-body for the same reason .field__label is: it is a word
   somebody has to read in order to know what to answer. */
.choice__legend {
  padding: 0;
  margin-bottom: var(--space-6);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
  color: var(--forest);
  line-height: var(--leading-tight);
}

/* The whole row is the label, so the 44px target is the row and not the 13px dot inside it. */
.choice__option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-12);
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  padding: var(--space-12) var(--space-14);
  border: var(--hairline);
  border-radius: var(--radius-control);
  background: var(--bg-card);
  cursor: pointer;
}
.choice__option:hover { border-color: var(--sage) }

/* Forest, not terracotta: a checked control is not the action the screen exists for, and §8's
   ration is the reason "Create the account" is findable at the bottom of this form. */
.choice__input {
  flex: 0 0 auto;
  width: var(--space-18);
  height: var(--space-18);
  margin: 0;
  accent-color: var(--primary);
}

.choice__text { display: flex; flex-direction: column; gap: 2px }
.choice__title {
  font-size: var(--text);
  font-weight: var(--weight-medium);
  color: var(--forest);
  line-height: var(--leading-tight);
}
.choice__note {
  font-size: var(--text-support);
  color: var(--text-muted-colour);
  line-height: var(--leading-prose);
}

/* THE ATTESTATION. Mint rather than the warm chip: it is a thing to agree to, not a thing that
   went wrong, and the error block above it is already wearing the warm one. */
.check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-12);
  min-height: var(--tap-min);
  padding: var(--space-12) var(--space-14);
  border: var(--hairline-mint);
  border-radius: var(--radius-panel);
  background: var(--bg-quiet);
}
.check__input {
  flex: 0 0 auto;
  width: var(--space-18);
  height: var(--space-18);
  /* Nudged onto the first line of a label that wraps to three, without a magic offset: the
     line box is --text-body times --leading-prose, and half the difference centres the box. */
  margin: calc((var(--text-body) * var(--leading-prose) - var(--space-18)) / 2) 0 0;
  accent-color: var(--primary);
}
.check__label {
  font-size: var(--text-body);
  color: var(--text-colour);
  line-height: var(--leading-prose);
  cursor: pointer;
}

/* What happens next, once. It is the last thing read before the button, so it sits at --text
   with prose leading — not a 10px note under the submit, which is where a sentence goes to be
   skipped. */
.signup__next {
  margin: 0;
  font-size: var(--text);
  color: var(--text-muted-colour);
  line-height: var(--leading-prose);
}

/* The other door, on both credential pages. Not a .btn: the screen already spends its one
   terracotta on its own action (§8), and a second bordered control beside it would read as a
   second thing to decide. It still carries the 44px floor in both dimensions, because §8's
   rule has no exception for "it is only a link". */
.signin__alt-link {
  /* A block and not an inline-flex, like .form-notice__link above it and for the same reason:
     a 44px-tall box set inline pushes the rest of the sentence onto a line of its own after it,
     which reads as the paragraph having been cut in half. On its own line it is a door. */
  display: flex;
  align-items: center;
  width: fit-content;
  min-height: var(--tap-min);
  min-width: var(--tap-min);
  margin-top: var(--space-6);
  font-size: var(--text-body);
  font-weight: var(--weight-medium);
}

/* The sentence the link above sits in. Muted, because it is an offer rather than an
   instruction — the reader is on the page they probably wanted. */
.signin__alt {
  margin: 0;
  font-size: var(--text-body);
  color: var(--text-muted-colour);
  line-height: var(--leading-prose);
}

/* ------------------- the pre-guardian state and the waitlist, on both screens --- */

/* TWO ACCOUNT STATES, ONE TREATMENT, TWO CLASS NAMES. `.pending` is the household waiting for
   its grown-up (#107) and `.waitlist` is the member whose intake has not started (#160). They
   are different facts with different sentences and different things to do about them, so they
   are never the same element — but they are the same KIND of thing said to the reader, "this
   part of the product has not started for you yet", and a member who meets both in one week
   should not meet two different-looking explanations of it. Hence one rule, two selectors:
   a class name that says which state it is, and no second visual language to keep in step. */

/* THE ONE CARD ON THE PAGE THAT IS NOT GREYED. Mint, not the warm chip and not the accent: it
   is a state of the account rather than a failure, and warm on this page is the colour a
   refusal wears. Nothing about it is an overlay, a lock or a price (§6). */
.pending,
.waitlist {
  /* The only margin in this block, and it is on the bottom alone: the card is inserted above
     panels whose own container decides the spacing above it, and on the curriculum page there
     is no gap at all between it and the track tabs without this. */
  margin: 0 0 var(--space-20);
  padding: var(--space-20) var(--space-22);
  border: var(--hairline-mint);
  border-radius: var(--radius-card);
  background: var(--bg-quiet);
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}

/* Sage on --mint-wash, not on paper: 4.35:1 is under AA for a 10px label (#29), and the wash is
   light enough to clear it. The eyebrow never carries the meaning on its own — the title
   directly below says the same thing at 19px (§8). */
.pending__eyebrow,
.waitlist__eyebrow {
  margin: 0;
  font-size: var(--text-eyebrow);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: var(--weight-semibold);
  color: var(--sage);
}

.pending__title,
.waitlist__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-card);
  font-weight: var(--weight-semibold);
  color: var(--forest);
  line-height: var(--leading-tight);
}

/* THE EXPLANATION, AT A SIZE A STUDENT ACTUALLY READS. §6 is explicit that this must not be a
   10px label, and §8 puts running prose a reader depends on at 14–15px — so it is --text with
   prose leading, the largest body tier in the system. */
.pending__lead,
.waitlist__lead {
  margin: 0;
  font-size: var(--text);
  color: var(--text-colour);
  line-height: var(--leading-prose);
}

.pending__action,
.waitlist__action { margin: 0 }

.pending__note,
.waitlist__note {
  margin: 0;
  font-size: var(--text-body);
  color: var(--text-muted-colour);
  line-height: var(--leading-prose);
}

/* GREYED, AND NOTHING ELSE. No `display: none`, no blur, no overlay, no pointer-events trap —
   every word stays readable, every link stays clickable, and lib/membership.js is what actually
   refuses a write (§6, §8: nothing disappears without an obvious way back). What changes is the
   emphasis: the ink drops to the muted tier, and the two places this design puts colour to say
   "look here" — the terracotta eyebrow and the track numerals — stop saying it, because the one
   thing worth looking at is the card above. Contrast is still AA: --text-muted-colour is
   5.44:1 on --paper and higher on a card. */
.is-pending,
.is-waiting { color: var(--text-muted-colour) }
.is-pending .eyebrow--action,
.is-waiting .eyebrow--action { color: var(--muted) }
.is-pending .track__numeral,
.is-waiting .track__numeral { color: var(--muted-faint) }
.is-pending .checkin__title,
.is-pending .track__title,
.is-pending .track__question,
.is-pending .unit__title,
.is-waiting .checkin__title,
.is-waiting .track__title,
.is-waiting .track__question,
.is-waiting .unit__title { color: var(--muted-dim) }

/* ------------------------------------------------------------------- live sessions ---

   The live sessions page (#130). It reuses the dashboard's forest panel wholesale — the next
   session is `.card.card--forest.on-forest.live`, and every `.live__*` rule above styles it —
   so what is new here is only the page around it: the head, the question composer and the
   replays list.

   NOTHING HERE IS TERRACOTTA. §8 rations the accent to the one action a screen exists for, and
   this screen's actions are plural by nature: a seat, a question, and one button per replay.
   A ration spent on a list is a ration spent on nothing, so the seat is rung 2 on forest, the
   question and each replay are rung 1, and the way back out of a mounted player is rung 3.

   AND NOTHING HERE IS A CLOCK. There is no rule below that animates, fills or moves, because
   there is no countdown on this page to render (§1). -------------------------------------- */

.live-page { max-width: 720px }
.live-page__head { margin-bottom: var(--space-24) }
.live-page__title { margin-bottom: var(--space-12) }

/* The framing sentence, set the way the college page's is and for the same reason: it is the
   prose a member most needs to read, so it takes the prose floor in forest (12.20:1) rather
   than the 17px sage tagline, which measures 4.35:1 and misses AA. */
.live-page__framing {
  margin: 0;
  padding-left: var(--space-14);
  border-left: 3px solid var(--mint);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose-loose);
  color: var(--forest);
}
.live-page__note {
  margin: var(--space-20) 0 var(--space-12);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.live-page__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--forest);
  font-weight: var(--weight-medium);
}
.live-page__body { display: flex; flex-direction: column; gap: var(--space-24) }

/* The dashboard panel's link to this page. A word, never a bare arrow (§8). */
.live__more { margin: 0 }

/* ---- ask Meghan a question ---- */

.ask {
  padding: var(--space-20);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
}
.ask__title { margin: 0 0 var(--space-10); font-size: var(--text-card) }

/* ABOVE the field, not under the button: this sentence is what decides whether a student writes
   the true thing, and a promise made after the typing is a promise made too late. */
.ask__where {
  margin: 0 0 var(--space-16);
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-colour);
}
.ask__form { display: flex; flex-direction: column; gap: var(--space-8) }
.ask__label { font-size: var(--text-ui); font-weight: var(--weight-medium); color: var(--forest) }
.ask__field {
  width: 100%;
  min-height: 96px;
  padding: var(--space-12);
  border: var(--hairline);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--text-colour);
  font: inherit;
  font-size: var(--text);
  line-height: var(--leading-prose);
  resize: vertical;
}
.ask__hint { margin: 0; font-size: var(--text-support); color: var(--text-muted-colour) }
.ask__action { margin: var(--space-6) 0 0; display: flex }
.ask__status {
  margin: var(--space-12) 0 0;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--sage);
}
.ask__asked-title { margin: var(--space-20) 0 var(--space-8); font-size: var(--text-subhead) }
.ask__asked { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--space-8) }
.ask__asked-item {
  padding: var(--space-12);
  border-radius: var(--radius-panel);
  background: var(--mint-wash);
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-colour);
}

/* ---- the replays ---- */

.replays__title { margin: 0 0 var(--space-12); font-size: var(--text-section) }
.replays__list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--space-16) }
.replays__item {
  padding: var(--space-20);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
}
.replays__name { margin: 0 0 var(--space-6); font-size: var(--text-card) }
.replays__when { margin: 0 0 var(--space-12); font-size: var(--text-body); color: var(--text-muted-colour) }

/* A replay that is not up yet is LISTED with the date it will be — never dropped off the end,
   which reads as "my things are gone" (§8), and never "in N hours", which is the countdown §1
   forbids however politely it is worded. */
.replays__soon {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}
.replays__action { margin: var(--space-12) 0 0; display: flex }
.replays__status { margin: 0 0 var(--space-12) }

/* ==========================================================================================
   THE LIBRARY (issue #126) — the front-end half of #16
   ==========================================================================================

   The rules here are §1 item 5 rendered as CSS. "Podcasts, past lectures, guided sits,
   worksheets. Not dripped: open from day one" means there is nothing on this screen to be
   behind on — so there is no state a card can be in, no mark for done, no mark for new, and
   no proportion anywhere: no bar, no ring, no fill that grows. Every card looks exactly like
   every other card, because every one of them is exactly as available as every other one.

   The cards therefore carry NO accent. The one rung-1 button per card is the card's own
   primary — "Open" — and terracotta is spent nowhere on this page, because there is no single
   thing a member is meant to do here (§8's ladder is scope, not importance).
   ------------------------------------------------------------------------------------------ */

.lib { max-width: 720px }
.lib__head { margin-bottom: var(--space-24) }
.lib__title { margin-bottom: var(--space-12) }

/* The sentence that says nothing here is paced, set at the prose floor in forest (12.20:1 on
   paper) with the mint rule that marks the page's own voice — the college page's reasoning,
   for the same reason: it is the sentence that most needs reading and the sage tagline size
   measures 4.35:1. */
.lib__framing {
  margin: 0;
  padding-left: var(--space-14);
  border-left: 3px solid var(--mint);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose-loose);
  color: var(--forest);
}

.lib__note {
  margin: var(--space-20) 0 var(--space-12);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}

/* The way back, and the sign-in link. A 44px target whatever the text around it does (§8). */
.lib__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--forest);
  font-weight: var(--weight-medium);
}

/* The kind and the length of one item, beside its title. Full-size body, never a 9px eyebrow:
   a fact that exists only at 9px has failed the parent reading the same screen (§8). */
.lib__facts {
  margin: var(--space-6) 0 0;
  font-size: var(--text-body);
  color: var(--text-muted-colour);
}

.lib__body { display: flex; flex-direction: column; gap: var(--space-24) }

.lib__summary {
  margin: 0 0 var(--space-16);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose-loose);
  color: var(--text-colour);
}

/* An empty library is a SENTENCE in a card, never a blank column that reads as "it broke". */
.lib__empty {
  margin: 0;
  padding: var(--space-20);
  border: var(--hairline-mint);
  border-radius: var(--radius-panel-lg);
  background: var(--bg-quiet);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--forest);
}

/* The kind chips are LINKS, not buttons: the filter lives in the URL, so it survives a reload
   and is undone by following another link. An anchor cannot be aria-pressed, so the chosen one
   is marked with aria-current — same treatment as .chipbtn's pressed state, reached through
   the attribute an anchor is allowed to carry, and still a ground AND a tick AND a weight,
   because meaning may never ride on colour alone. */
.chipbtn[aria-current='page'] {
  background: var(--bg-badge);
  border-color: var(--mint);
  color: var(--text-on-badge);
  font-weight: var(--weight-semibold);
}
.chipbtn[aria-current='page']::before { content: '✓'; font-size: var(--text-body) }
/* A chip is a control, not a sentence with a link in it: the underline an anchor brings by
   default would make the row read as five links stacked next to each other. */
a.chipbtn { text-decoration: none }
.chipbtn__count { color: inherit }

/* A single column, and that is the product decision the college cards record: a grid invites
   the eye to compare cell against cell. Hairline and ground, never shadow (§8). */
.libitems { display: flex; flex-direction: column; gap: var(--space-16) }

.libitem {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
  padding: var(--space-20);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
}
.libitem__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-10);
}
.libitem__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-card);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  color: var(--forest);
}
.libitem__meta { margin: 0; font-size: var(--text-body); color: var(--text-muted-colour) }
.libitem__summary {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-colour);
}
.libitem__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin: 0;
  padding: 0;
  list-style: none;
}
.libitem__action { margin: 0; display: flex }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   THE ROOMS PAGE (#127). Three views on one page: the rooms, one room's conversations, one
   conversation. Everything here is a token, and the reasons two of these rules look excessive:

   - A ROOM CARD AND A CONVERSATION ROW ARE LINKS, so both carry the 44px floor themselves.
     They are full-width rows, which is exactly the shape that looks fine while being 30px tall.
   - `white-space: pre-wrap` on a post body. Somebody writing at two in the morning puts line
     breaks in, and a paragraph that eats them changes what they wrote. The body is escaped
     before it gets here (site/assets/rooms.js esc), so this is layout and not markup.

   There is NO accent on this page and no rule below reaches for --action: the ration is one
   terracotta ground per screen (§8) and this screen's answer is none — the thing you are meant
   to do here is read, and then say something in a box that is already in front of you. */

.rm { max-width: 720px }
.rm__head { margin-bottom: var(--space-24) }
.rm__title { margin-bottom: var(--space-12) }

.rm__framing {
  margin: 0;
  padding-left: var(--space-14);
  border-left: 3px solid var(--mint);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose-loose);
  color: var(--forest);
}

.rm__note {
  margin: var(--space-20) 0 var(--space-12);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-muted-colour);
}

.rm__link,
.rm__back > .rm__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--forest);
  font-weight: var(--weight-medium);
}

.rm__body { display: flex; flex-direction: column; gap: var(--space-20) }
.rm__back { margin: 0 }

.rm__panel {
  margin: 0;
  padding: var(--space-20);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}
.rm__panel-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-card);
  line-height: var(--leading-tight);
  color: var(--forest);
}
.rm__lead {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-colour);
}

.rm__empty {
  margin: 0;
  padding: var(--space-20);
  border: var(--hairline-mint);
  border-radius: var(--radius-card);
  background: var(--bg-quiet);
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--forest);
}

.rm__rooms,
.rm__threads,
.rm__posts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-12) }

.rm__room,
.rm__thread {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-6);
  min-height: var(--tap-min);
  padding: var(--space-16) var(--space-18);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
  color: var(--text-colour);
}
.rm__room:hover,
.rm__thread:hover { background: var(--bg-quiet); border: var(--hairline-mint) }
/* THE WHOLE CARD IS THE LINK, so the underline sits on the title alone: a summary and a meta
   line dragged underlined behind it read as three links stacked, and an underline on none of
   them would leave a card whose only affordance is a hover state nobody on a phone can see. */
.rm__room,
.rm__thread { text-decoration: none }
.rm__room-title,
.rm__thread-title { text-decoration: underline }

.rm__room-title,
.rm__thread-title {
  font-size: var(--text-card);
  font-family: var(--font-display);
  line-height: var(--leading-tight);
  color: var(--forest);
}
.rm__room-summary {
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--text-colour);
}
.rm__room-meta,
.rm__thread-meta {
  font-size: var(--text-body);
  color: var(--text-muted-colour);
}
.rm__thread-meta { margin: 0 }

.rm__post {
  padding: var(--space-18);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}
/* The author's own hidden post: the server's one sentence, on the quiet ground the SIT FIRST
   card uses. Not a warning colour, and deliberately not one — §1: what a student sees here is
   support, and a red card is a telling-off whatever the words say. */
.rm__post--quiet { background: var(--bg-quiet); border: var(--hairline-mint) }

.rm__post-head { display: flex; align-items: center; gap: var(--space-10) }
.rm__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-28);
  height: var(--space-28);
  border-radius: var(--radius-avatar);
  background: var(--bg-badge);
  color: var(--text-on-badge);
  font-size: var(--text-body);
  font-weight: var(--weight-semibold);
}
.rm__post-meta {
  margin: 0;
  font-size: var(--text-body);
  color: var(--text-muted-colour);
}
.rm__post-body {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--text-colour);
  white-space: pre-wrap;
}
.rm__notice {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text);
  line-height: var(--leading-prose);
  color: var(--forest);
}
.rm__post-actions { margin: 0 }

/* THE SECOND STEP OF A REPORT. It is a panel and not a confirm() dialog: the consequence has
   to be readable beside the post it is about, and a browser dialog is a sentence nobody can
   style, nobody can screen-read in context, and everybody dismisses. */
.rm__report {
  padding: var(--space-16);
  border: var(--hairline-mint);
  border-radius: var(--radius-panel);
  background: var(--bg-quiet);
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}
.rm__report-lead {
  margin: 0;
  font-size: var(--text);
  font-weight: var(--weight-semibold);
  color: var(--forest);
}
.rm__report-note {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--forest);
}
.rm__report-actions { margin: 0; display: flex; flex-wrap: wrap; gap: var(--space-10) }
/* The server's receipt after a report, and the only place on this page that renders a string
   this codebase's front end did not write. */
.rm__receipt {
  margin: 0;
  max-width: 60ch;
  font-size: var(--text-body);
  line-height: var(--leading-prose);
  color: var(--forest);
}

.rm__compose { display: flex; flex-direction: column; gap: var(--space-10) }
.rm__label {
  font-size: var(--text-ui);
  font-weight: var(--weight-medium);
  color: var(--forest);
}
.rm__input,
.rm__textarea {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-12) var(--space-14);
  border: var(--hairline);
  border-radius: var(--radius-control);
  background: var(--bg-card);
  color: var(--text-colour);
  font-family: var(--font-ui);
  font-size: var(--text);
  line-height: var(--leading-prose);
}
.rm__textarea { resize: vertical }
.rm__hint {
  margin: 0;
  font-size: var(--text-body);
  color: var(--text-muted-colour);
}

/* ==========================================================================================
   THE FEEDBACK PANEL (issue #148)

   A column over whichever screen she is on, because the screen is what the feedback is about
   (site/assets/feedback.js says why it is not a page). Four notes:

   1. IT SITS ABOVE THE RAIL. The rail is z-index 2 and fixed; a panel underneath it would be
      half-covered at exactly the width where the rail is widest relative to the page. The skip
      link at 3 is top-left and this is bottom-right, so nothing they both want is contested.
   2. NO SHADOW, because §8's design uses none anywhere. A panel that floats over content is
      exactly where a shadow is tempting; the hairline and the white ground do the same work as
      they do for every card in the product.
   3. NO TERRACOTTA. The accent is rationed to the one action a SCREEN exists for, and this
      panel is over a screen that has already spent it. Its send and file controls are rung 1.
   4. AT 860px IT IS THE PAGE. A 380px column on a phone is a letterbox over the thing she is
      writing about; below the shell's own breakpoint it fills the viewport and the hairline
      and corner radius come off, so it reads as a screen rather than as a clipped card.
   ====================================================================================== */

.fb {
  position: fixed;
  right: var(--space-16);
  bottom: var(--space-16);
  z-index: 4;
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
  width: var(--panel-width);
  max-width: calc(100% - var(--space-24));
  max-height: calc(100% - var(--space-24));
  padding: var(--space-18);
  border: var(--hairline);
  border-radius: var(--radius-card);
  background: var(--bg-card);
  color: var(--text-colour);
  font-family: var(--font-ui);
}

.fb__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
}

.fb__heading {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-card);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  color: var(--forest);
}

.fb__subhead {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-subhead);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  color: var(--forest);
}

/* The conversation and the form scroll together: the transcript is the context for the field
   under it, and a field pinned below a scrolling log puts her answer out of sight of the
   question. */
.fb__scroll {
  display: flex;
  flex-direction: column;
  gap: var(--space-14);
  overflow-y: auto;
  scrollbar-width: thin;
}

.fb__turns {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Who said it is a WORD at reading size, not a 9px eyebrow and not a ground colour: §8 forbids
   meaning carried by size or colour alone, and in a transcript "who said this" is the meaning. */
.fb__turn {
  padding: var(--space-10) var(--space-12);
  border-radius: var(--radius-panel);
  background: var(--bg-quiet);
}
.fb__turn--staff { background: var(--bg-page) }

.fb__who {
  margin: 0 0 var(--space-6);
  font-size: var(--text-body);
  font-weight: var(--weight-semibold);
  color: var(--forest);
  line-height: var(--leading-tight);
}

/* Her line breaks are hers — the issue body quotes this text verbatim, and a panel that reflows
   it is showing her something other than what will be filed. */
.fb__said {
  margin: 0;
  font-size: var(--text);
  line-height: var(--leading-prose);
  white-space: pre-wrap;
}

.fb__note {
  margin: 0;
  font-size: var(--text-body);
  color: var(--text-muted-colour);
  line-height: var(--leading-prose);
}

.fb__meta { font-weight: var(--weight-semibold); color: var(--forest) }

.fb__foot { margin-top: auto }

.fb__draft, .fb__filed {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}

.fb__textarea { resize: vertical }

@media (max-width: 860px) {
  .fb {
    inset: 0;
    width: auto;
    max-width: none;
    max-height: none;
    border: 0;
    border-radius: 0;
  }
}
