/* ------------------------------------------------------------------ *
 * IBM Plex, served from our own domain.
 *
 * The homepage was designed in Plex and falls back to system-ui without it,
 * which is a different page. Google Fonts would have been one line, but every
 * asset here is served from tydbureau.com — a student on a slow connection
 * has one origin to reach, and no third party gets a log of which of our
 * pages they read.
 *
 * Sans is a variable font: one file covers 400 through 700, so it is declared
 * once per subset with a weight range. Fetching it per weight, as the first
 * pass did, pulled identical bytes four times under four URLs.
 *
 * SIL Open Font License 1.1, which permits exactly this. Subsets keep their
 * unicode-range, so a Latin reader never downloads the Cyrillic.
 * ------------------------------------------------------------------ */

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-400-cyrillic.cd34c7793d97.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-500-cyrillic.0ba762bde65a.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-400-latin-ext.17a5b2a15bf5.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-500-latin-ext.216ee1a35b06.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-400-latin.814fe1615d4d.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: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-500-latin.6708425c446b.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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-variable-cyrillic.c352fb162d49.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-variable-latin-ext.d313794bd9fd.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/ibm-plex-sans-variable-latin.d28498895ed7.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: 'IBM Plex Serif';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-serif-600-cyrillic.0b32c87d2804.woff2") format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'IBM Plex Serif';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-serif-600-latin-ext.c7bb9c742f8f.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Serif';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-serif-600-latin.182f055b8c43.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;
}

/*
 * TyD Bureau — black on white, typography first.
 *
 * The governing rule: nothing decorative. No gradients, no shadows, no
 * motion, no rounded corners. If an element is not evidence or navigation,
 * it should not be here.
 */

:root {
  --ink: #000;
  --paper: #fff;
  --grey-1: #6b6b6b;   /* secondary text */
  --grey-2: #b8b8b8;   /* hairlines */
  --grey-3: #ededed;   /* block fills */

  --measure: 34rem;    /* readable line length for body copy */
  --page: 68rem;       /* outer page width */

  --sans: "IBM Plex Sans", system-ui, -apple-system, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --serif: "IBM Plex Serif", Georgia, serif;
}

*,
*::before,
*::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1.0625rem;      /* 17px, set generously as agreed */
  line-height: 1.65;
  font-feature-settings: "kern" 1;
  text-rendering: optimizeLegibility;
}

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

.page {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 1.5rem;
}

/* The workbench pages, and only those. 68rem is a reading width, and the
   editor is not reading — beside a statement it left a third of a laptop
   screen empty and half of a desktop one.

   Deliberately not edge to edge. LeetCode is full-bleed and caps its own
   statement pane; here the left column is prose, and stretched to 100vw the
   statement becomes a 130-character line that is harder to read than the
   narrow version it replaced. So: a wider shell, with `--measure` still
   holding the prose to a readable column inside it. The grip between the two
   panes does the rest, and remembers where it was put. */
.page--bench { max-width: 96rem; }

.prose { max-width: var(--measure); }

section { padding: 4.5rem 0; }
section + section { border-top: 1px solid var(--grey-2); }

/* ------------------------------------------------------------------- type */

h1, h2, h3 {
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0 0 1rem;
  text-wrap: balance;
}

h1 { font-size: clamp(2.25rem, 6vw, 4rem); }
h2 { font-size: clamp(1.5rem, 3vw, 2rem); margin-bottom: 1.75rem; }
h3 { font-size: 1.125rem; }

p { margin: 0 0 1.25rem; }

a { color: var(--ink); text-underline-offset: 0.2em; }
a:hover { text-decoration-thickness: 2px; }

.lede {
  font-size: 1.25rem;
  line-height: 1.5;
  color: var(--grey-1);
}

.label {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--grey-1);
}

/* ----------------------------------------------------------------- header */

.masthead {
  border-bottom: 1px solid var(--ink);
  /* Deliberately static: it does not shrink, blur, or float on scroll. */
}

.masthead__inner {
  display: flex;
  align-items: center;
  gap: 2rem;
  min-height: 4rem;
}

.wordmark {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: -0.03em;
  text-decoration: none;
  color: var(--ink);
}

/* The mark is taller than wide (453 : 490.43), so it is sized by height and
   left to find its own width. `currentColor` means it inherits from the link,
   which is what allows one file to work on light and dark alike. */
.wordmark .mark {
  height: 1.55rem;
  width: auto;
  display: block;
}

/* Below the header, the mark is set on the footer as a quiet endmark. */
.colophon .mark {
  height: 2.25rem;
  width: auto;
  color: var(--ink);
  margin-bottom: 1.25rem;
}

.masthead nav {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
}

/* The number is not one of the links: the numeral face sets it apart without
   a rule doing it. `margin-left: auto` here rather than on the nav — the two
   of them travel to the right together, with the inner gap between, instead
   of splitting the free space between two auto margins. */
.masthead__phone {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.9rem;
  white-space: nowrap;
  text-decoration: none;
  color: var(--grey-1);
}

.masthead__phone:hover { color: var(--ink); }


.masthead nav a {
  text-decoration: none;
  font-size: 0.9375rem;
  color: var(--grey-1);
}

.masthead nav a:hover,
.masthead nav a[aria-current="page"] { color: var(--ink); }

/* ----------------------------------------------------------------- button */

.btn {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  font-size: 0.9375rem;
  font-weight: 500;
}

.btn:hover { background: var(--paper); color: var(--ink); }

.btn--ghost { background: var(--paper); color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--paper); }

.btn--small { padding: 0.4rem 0.9rem; font-size: 0.875rem; }

/* ------------------------------------------------------------------- hero */

.hero { padding: 6rem 0 4rem; }

.hero h1 { max-width: 20ch; margin-bottom: 1.5rem; }

.hero__actions {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 2.5rem 0 1rem;
}

.attribution {
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--grey-1);
  padding-top: 2rem;
  margin-top: 3rem;
  border-top: 1px solid var(--grey-2);
}

/* ----------------------------------------------------------------- figures */

.figures {
  display: flex;
  flex-wrap: wrap;
  gap: 3rem;
  margin: 0;
}

.figures div { margin: 0; }

.figures dd {
  font-family: var(--mono);
  font-size: clamp(2.5rem, 7vw, 4.5rem);
  line-height: 1;
  margin: 0 0 0.35rem;
  font-variant-numeric: tabular-nums;
}

.figures dt { order: 2; }

/* ------------------------------------------------------------------ lists */

.steps { counter-reset: step; list-style: none; padding: 0; margin: 0; }

.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: 3rem 1fr;
  gap: 1.5rem;
  padding: 1.5rem 0;
  border-top: 1px solid var(--grey-2);
}

.steps li::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--mono);
  color: var(--grey-1);
  font-size: 0.875rem;
  padding-top: 0.3rem;
}

.steps h3 { margin-bottom: 0.25rem; }
.steps p { margin: 0; color: var(--grey-1); }

.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 2.5rem;
}

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

.topics li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--grey-2);
}

.topics span:last-child {
  font-family: var(--mono);
  color: var(--grey-1);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ instructor */

.instructor { display: grid; grid-template-columns: 20rem 1fr; gap: 3rem; }

.instructor img {
  width: 100%;
  height: auto;
  display: block;
  /* Print-like: no border, no radius, no shadow. Contrast lifted so the
     jacket reads near-black against the mid-grey backdrop. */
  filter: grayscale(100%) contrast(1.15);
}

@media (max-width: 46rem) {
  .instructor { grid-template-columns: 1fr; }
  .instructor img { max-width: 16rem; }
}

/* --------------------------------------------------------------- policies */

.policy { padding: 4rem 0 6rem; }
.policy h2 { font-size: 1.25rem; margin: 2.5rem 0 0.75rem; }
.policy ul { padding-left: 1.25rem; }
.policy li { margin-bottom: 0.5rem; }

.updated { font-family: var(--mono); font-size: 0.8125rem; color: var(--grey-1); }

/* Unfilled business details must be impossible to miss in review. */
.todo {
  background: var(--grey-3);
  border-left: 3px solid var(--ink);
  padding: 0.1rem 0.4rem;
  font-family: var(--mono);
  font-size: 0.875rem;
}

/* ------------------------------------------------------------- attendance */

/* Denser and plainer than the marketing page: this reports, it does not
   argue. Touch targets are large because it is used standing up, on a phone,
   at the start of a class. */

.roll { width: 100%; border-collapse: collapse; margin-top: 1.5rem; }

.roll__row { border-bottom: 1px solid var(--grey-2); }

.roll__name {
  padding: 0.9rem 0.5rem 0.9rem 0;
  font-weight: 500;
}

.roll__phone {
  display: block;
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--grey-1);
  font-weight: 400;
}

.roll__actions {
  display: flex;
  gap: 0.4rem;
  justify-content: flex-end;
  padding: 0.7rem 0;
}

.roll__btn {
  font: inherit;
  font-size: 0.875rem;
  padding: 0.55rem 0.9rem;
  min-width: 5rem;             /* comfortably tappable */
  border: 1px solid var(--grey-2);
  background: var(--paper);
  color: var(--grey-1);
  cursor: pointer;
}

.roll__btn:hover { border-color: var(--ink); color: var(--ink); }

.roll__btn.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
  font-weight: 500;
}

/* Absent rows are tinted so a glance down the list finds them. This is the
   one place colour would be tempting; the grey does the job without
   introducing a second accent. */
.roll__row--absent .roll__name { color: var(--grey-1); text-decoration: line-through; }
.roll__row--absent { background: var(--grey-3); }

.tally td {
  padding: 0.75rem 0;
  border-bottom: 2px solid var(--ink);
  font-family: var(--mono);
  font-size: 0.9375rem;
}

.tally__total { color: var(--grey-1); }

.roll__date {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
  font-size: 0.9375rem;
}

.roll__bulk { display: flex; gap: 0.75rem; flex-wrap: wrap; margin: 0 0 1rem; }

.roll__date input {
  font: inherit;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--grey-2);
  background: var(--paper);
}

@media (max-width: 32rem) {
  .roll__btn { min-width: 0; flex: 1; padding: 0.6rem 0.4rem; }
  .roll__actions { justify-content: stretch; }
}

/* ------------------------------------------------------------ sign in/out */

/* The sign-out control has to be a POST, or a prefetching browser can log a
   student out by looking at a link. Styled back down to look like the other
   navigation items. */
.masthead__who {
  color: var(--grey-1);
  font-size: 0.875rem;
  white-space: nowrap;
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

.masthead__signout { margin: 0; display: flex; }

.masthead__signout button {
  font: inherit;
  font-size: 0.9375rem;
  color: var(--grey-1);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.masthead__signout button:hover { color: var(--ink); }

.signin { padding: 5rem 0 7rem; }
.signin h1 { font-size: clamp(2rem, 5vw, 3rem); }

.form { max-width: 22rem; margin-top: 2.5rem; }

.form__field { display: block; margin-bottom: 1.5rem; }

.form__field label {
  display: block;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--grey-1);
  margin-bottom: 0.4rem;
}

.form__field input {
  font: inherit;
  width: 100%;
  padding: 0.7rem 0.75rem;
  border: 1px solid var(--grey-2);
  background: var(--paper);
  color: var(--ink);
}

.form__field input:focus {
  outline: 2px solid var(--ink);
  outline-offset: -2px;
  border-color: var(--ink);
}

.form__error {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.9375rem;
  border-left: 3px solid var(--ink);
  background: var(--grey-3);
  padding: 0.5rem 0.75rem;
}

/* The enrolment form is long enough to need grouping; the sign-in form is
   two fields and must not inherit any of this. */
.form--wide { max-width: 30rem; }

.form--wide fieldset {
  border: 0;
  border-top: 1px solid var(--grey-2);
  margin: 0 0 2rem;
  padding: 1.5rem 0 0;
}

.form--wide legend { padding: 0; }

.form__field select,
.form__field textarea {
  font: inherit;
  width: 100%;
  padding: 0.7rem 0.75rem;
  border: 1px solid var(--grey-2);
  background: var(--paper);
  color: var(--ink);
}

/* Radio choices, as a stack of separate tappable rows rather than inline,
   since the batch is chosen on a phone as often as not.

   Keyed on a class the template sets rather than on the markup Django emits.
   These rules were written for `ul > li`, and Django 5 renders radio choices
   as divs, so they matched nothing: every option took the small uppercase
   label type, and every radio took a text box's full width and border, which
   stood the circle on a line of its own above its words. */
.form__choices label {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 0 0 0.6rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--grey-2);
  font-family: var(--sans);
  font-size: 1.0625rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  cursor: pointer;
}

.form__choices input[type="radio"] {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0;
  padding: 0;
  border: 0;
  accent-color: var(--ink);
}

.form__choices label:has(input:checked) {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.form__choices input[type="radio"]:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.form__field--bad.form__choices label { border-color: var(--ink); }

.form__help {
  display: block;
  margin-top: 0.35rem;
  font-size: 0.875rem;
  color: var(--grey-1);
}

.form__optional {
  font-family: var(--sans);
  letter-spacing: 0;
  text-transform: none;
  color: var(--grey-2);
}

.form__field--bad input,
.form__field--bad select { border-color: var(--ink); border-width: 2px; }

.form__note {
  max-width: var(--measure);
  margin-top: 1.5rem;
  font-size: 0.9375rem;
  color: var(--grey-1);
}

/* ------------------------------------------------------------------ price */

.price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0;
  padding: 1rem 1.25rem;
  background: var(--grey-3);
  border-left: 3px solid var(--ink);
}

.price .label { width: 100%; }

.price strong {
  font-family: var(--mono);
  font-size: 1.75rem;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.price__was {
  font-family: var(--mono);
  color: var(--grey-1);
  text-decoration: line-through;
}

.price__note { font-size: 0.875rem; color: var(--grey-1); }

/* Ways to pay. Every choice is the same box with the same inner order —
   heading, how soon access opens, what to do — so that no one of them reads
   as the real path and the others as footnotes. */
.paychoices {
  counter-reset: paychoice;
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
}

.paychoices .paychoice {
  counter-increment: paychoice;
  border: 1px solid var(--grey-2);
  padding: 1.5rem;
  margin: 0 0 1rem;
}

.paychoice h2 {
  font-size: 1.25rem;
  margin: 0 0 0.5rem;
}

.paychoice h2::before {
  content: counter(paychoice) ". ";
  font-family: var(--mono);
  color: var(--grey-1);
}

.paychoice__aside {
  font-family: var(--mono);
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--grey-1);
  margin-left: 0.35rem;
}

.paychoice__when { margin: 0 0 1.25rem; color: var(--grey-1); }
.paychoice form { margin: 0; }
.paychoice .invoice__qr { margin: 0 0 1.25rem; }
.paychoice .invoice__how { margin: 0; }
.paychoice__tell { margin: 1.25rem 0 0; font-size: 0.9375rem; }

.invoice__how {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.4rem 1.5rem;
  margin: 1.25rem 0 0;
  font-size: 0.9375rem;
}

.invoice__how dt { color: var(--grey-1); }
.invoice__how dd { margin: 0; font-family: var(--mono); }

/* The QR is sized here rather than in the SVG, which is emitted without a
   width so that one number controls it. 12rem is about as small as a phone
   camera reliably reads across a room's worth of arm's length. */
.invoice__qr { margin: 1.5rem 0 0; }
.invoice__qr a { display: inline-block; line-height: 0; }
.invoice__qr svg { width: 12rem; height: auto; }

.invoice__qr figcaption {
  margin-top: 0.5rem;
  font-size: 0.875rem;
  color: var(--grey-1);
}

/* The classroom address. <address> is italic by default; nothing else here is. */
.venue { font-style: normal; }

.signin__help {
  max-width: var(--measure);
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--grey-2);
  font-size: 0.9375rem;
  color: var(--grey-1);
}

/* ----------------------------------------------------------------- portal */

.portal { padding: 3rem 0 6rem; }

.portal__head h1 { font-size: clamp(1.75rem, 4vw, 2.75rem); margin: 0.35rem 0 0; }

.portal__nav {
  display: flex;
  gap: 1.75rem;
  margin: 2rem 0 3rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--ink);
  flex-wrap: wrap;
}

.portal__nav a { font-size: 0.9375rem; color: var(--grey-1); text-decoration: none; }
.portal__nav a:hover { color: var(--ink); }

.portal__nav a[aria-current="page"] {
  color: var(--ink);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 0.75rem;
  text-decoration-thickness: 2px;
}

.portal h2 {
  font-size: 1.125rem;
  margin: 3rem 0 1.25rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--grey-2);
}

/* --------------------------------------------------------------- statistics */

.stats { display: flex; flex-wrap: wrap; gap: 3rem; margin: 0 0 2.5rem; }
.stats div { margin: 0; }

.stats dd {
  font-family: var(--mono);
  font-size: clamp(2rem, 5vw, 3rem);
  line-height: 1;
  margin: 0 0 0.35rem;
  font-variant-numeric: tabular-nums;
}

.stats__of { color: var(--grey-2); font-size: 0.5em; }
.stats dt { order: 2; }

/* A bar, not a pie or a ring: length is the one visual encoding people read
   accurately without a legend. */
.meter {
  height: 0.5rem;
  background: var(--grey-3);
  border: 1px solid var(--grey-2);
}

.meter span { display: block; height: 100%; background: var(--ink); }

.meter__caption {
  font-size: 0.9375rem;
  color: var(--grey-1);
  margin: 0.6rem 0 0;
}

.next-class {
  margin-top: 2.5rem;
  padding: 1rem 1.25rem;
  border-left: 3px solid var(--ink);
  background: var(--grey-3);
}

.next-class .label { display: block; }

.empty { color: var(--grey-1); }

/* ------------------------------------------------------------- one session */

.session { padding: 1.25rem 0; border-bottom: 1px solid var(--grey-2); }

.session__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.session h3 { margin: 0; }
.session__meta { margin: 0; font-size: 0.875rem; color: var(--grey-1); }

.session__concepts {
  font-family: var(--mono);
  font-size: 0.875rem;
  color: var(--grey-1);
  margin: 0.5rem 0 0;
}

.session__problems { margin: 0.9rem 0 0; padding-left: 1.25rem; }
.session__problems li { margin-bottom: 0.35rem; }

.session__topic {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--grey-1);
  margin-left: 0.5rem;
}

.session__empty { margin: 0.75rem 0 0; color: var(--grey-1); font-size: 0.9375rem; }

/* Outlined rather than filled, so a page of "Present" does not turn into a
   wall of black boxes. Absent is the one that inverts, because it is the one
   worth noticing. */
.badge {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--grey-2);
}

.badge--present { color: var(--grey-1); }
.badge--late { color: var(--ink); border-color: var(--ink); }
.badge--absent { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ------------------------------------------------------------- curriculum */

.topic h2 { display: flex; justify-content: space-between; gap: 1rem; align-items: baseline; }

.topic__count {
  font-family: var(--mono);
  font-size: 0.875rem;
  color: var(--grey-1);
  font-variant-numeric: tabular-nums;
  font-weight: 400;
}

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

.checklist__item {
  display: grid;
  grid-template-columns: 1.5rem 1fr auto;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--grey-3);
  color: var(--grey-1);
}

.checklist__item.is-covered { color: var(--ink); }

.checklist__box { font-family: var(--mono); text-align: center; }

.checklist__approach {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--grey-1);
  margin-left: 0.5rem;
}

.checklist__status { font-size: 0.6875rem; }
.checklist__item.is-covered .checklist__status { color: var(--ink); }

@media (max-width: 32rem) {
  .checklist__item { grid-template-columns: 1.5rem 1fr; }
  .checklist__status { display: none; }
  .stats { gap: 2rem; }
}

/* --------------------------------------------------------------- line gym */

.crumb { font-size: 0.875rem; margin-bottom: 1.5rem; }

/* The score sits above the drill and never moves, which is what makes a
   sitting feel like reps rather than a form being filled in. */
.gym__score {
  font-family: var(--mono);
  font-size: 1.125rem;
  font-variant-numeric: tabular-nums;
  margin-bottom: 1.5rem;
}

.gym {
  border: 1px solid var(--ink);
  padding: 2rem;
  margin-bottom: 2rem;
}

.gym__prompt { font-size: 1.375rem; line-height: 1.4; margin-bottom: 0.75rem; }

.gym__explain { color: var(--grey-1); max-width: 60ch; margin-bottom: 1.5rem; }

.gym__answer {
  font-family: var(--mono);
  background: var(--grey-4, #f4f4f4);
  border-left: 2px solid var(--ink);
  padding: 0.75rem 1rem;
  margin-bottom: 1.5rem;
  overflow-x: auto;
}

/* Monospace and oversized on purpose: the student is checking their own
   characters against a memory, and small type hides a missing bracket. */
.gym__input {
  width: 100%;
  font-family: var(--mono);
  font-size: 1.125rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--grey-2);
  background: var(--paper);
  color: var(--ink);
}

.gym__input:focus { outline: 2px solid var(--ink); outline-offset: -1px; border-color: var(--ink); }

.gym__verdict { font-family: var(--mono); font-size: 0.9375rem; margin-top: 1rem; }
.gym__verdict--right { color: var(--ink); }
.gym__verdict--wrong { color: var(--ink); font-weight: 600; }

.gym__caveat { display: block; font-weight: 400; color: var(--grey-1); font-size: 0.8125rem; }
.gym__caveat:empty { display: none; }

.gym__disclaimer {
  color: var(--grey-1);
  font-size: 0.8125rem;
  max-width: 34rem;
  margin: -1.5rem 0 2.5rem;
}

.gym__actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 1.5rem;
}

.gym__target { font-family: var(--mono); font-size: 0.75rem; color: var(--grey-1); }

.gym__stats { display: flex; flex-wrap: wrap; gap: 2.5rem; margin: 0 0 2.5rem; }
.gym__stats div { margin: 0; }
.gym__stats dd {
  font-family: var(--mono);
  font-size: 1.5rem;
  font-variant-numeric: tabular-nums;
  margin: 0 0 0.35rem;
}
.gym__stats dt { order: 2; }

.gym__notes { border-top: 1px solid var(--grey-3); padding-top: 1.5rem; }
.gym__notes h2 { font-size: 1rem; margin-bottom: 0.5rem; }
.gym__notes p { max-width: 60ch; color: var(--grey-1); }
.gym__seen { font-family: var(--mono); font-size: 0.8125rem; }

.checklist__name code { font-size: 0.9375rem; }

.drill-queue { list-style: none; padding: 0; margin: 0 0 2.5rem; }
.drill-queue li { padding: 0.5rem 0; border-bottom: 1px solid var(--grey-3); }
.drill-queue code { font-size: 0.9375rem; }
.drill-queue__none { color: var(--grey-1); margin-bottom: 2.5rem; }

/* ---------------------------------------------------------- indentation */

.indent {
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
  font-family: var(--mono);
  font-size: 0.9375rem;
  line-height: 1.6;
  overflow-x: auto;
  border: 1px solid var(--grey-3);
  outline: none;
}

.indent:focus { outline: 2px solid var(--ink); outline-offset: -1px; }

.indent__line {
  display: flex;
  align-items: baseline;
  padding: 0.15rem 0.75rem;
  cursor: default;
}

.indent__line.is-on { background: var(--grey-3); }
.indent__line.is-wrong { background: var(--ink); color: var(--paper); }
.indent__line.is-wrong .indent__pad { border-color: var(--paper); }
.indent__line.is-target { background: var(--ink); color: var(--paper); }
.indent__line.is-target .indent__pad { border-color: var(--paper); }

.indent__pad {
  flex: 0 0 calc(var(--depth, 0) * 1.25rem);
  width: calc(var(--depth, 0) * 1.25rem);
  align-self: stretch;
  border-left: 1px solid var(--grey-2);
  margin-right: 0.35rem;
}

.indent__line code { font-size: inherit; }

.indent__nudge {
  font-family: var(--mono);
  font-size: 0.8125rem;
  margin: 0 0 1rem;
}

.indent--read { cursor: default; }
.indent--read .indent__line { cursor: default; }

.own { border: 0; padding: 0; margin: 0 0 1rem; }
.own__choice {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--grey-3);
  cursor: pointer;
}
.own__choice code { font-size: 0.9375rem; }

/* ----------------------------------------------------------------- footer */

.colophon {
  border-top: 1px solid var(--ink);
  padding: 3rem 0;
  font-size: 0.9375rem;
}

.colophon nav { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
.colophon address { font-style: normal; color: var(--grey-1); }
.social-link { display: inline-flex; align-items: center; gap: 0.35em; }
.social-link svg { flex: none; }

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

@media print {
  .masthead nav, .hero__actions { display: none; }
  body { font-size: 11pt; }
}

/* ------------------------------------------------------------------ recall
   The queue reads as a short list of work, not as a dashboard: a student
   opens it to find out what to write next, and everything else is noise. */

.recall-queue { list-style: none; padding: 0; margin: 0 0 2.5rem; }

.recall-queue li {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem 1rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--grey-3);
}

.recall-queue__meta { font-family: var(--mono); font-size: 0.75rem; color: var(--grey-1); }
.recall-queue--quiet li { color: var(--grey-1); }

.recall__spill,
.recall__clear { color: var(--grey-1); margin-bottom: 1rem; }

.rewrite__gap { font-family: var(--mono); font-size: 0.8125rem; color: var(--grey-1); }

.rewrite__rule { max-width: 60ch; margin: 1.5rem 0 2rem; }

/* Monospace and unstyled beyond that. It is not an IDE and should not
   pretend to be one — the whole point is writing without assistance. */
.rewrite__editor {
  display: block;
  width: 100%;
  font-family: var(--mono);
  font-size: 0.9375rem;
  line-height: 1.6;
  padding: 1rem;
  border: 1px solid var(--grey-3);
  background: var(--paper);
  color: var(--ink);
  resize: vertical;
  tab-size: 4;
}

.rewrite__editor:focus { outline: 2px solid var(--ink); outline-offset: -1px; border-color: var(--ink); }

.rewrite__check { border-top: 1px solid var(--grey-3); margin-top: 2rem; padding-top: 1.5rem; }
.rewrite__check p { max-width: 60ch; }
.rewrite__honest { color: var(--grey-1); font-size: 0.9375rem; }

.rewrite__verdict { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.5rem; }

/* -------------------------------------------------------------- assessment
   Sat once at the start and once at the end. The stage pages carry no
   navigation on purpose — during a proctored sitting there is nowhere else
   to be, and a nav bar is an invitation to open another tab. */

.battery__clock { font-family: var(--mono); font-size: 0.8125rem; color: var(--grey-1); }

.battery__passage {
  border: 1px solid var(--grey-3);
  background: var(--paper);
  padding: 1rem;
  overflow-x: auto;
  font-size: 0.9375rem;
  line-height: 1.6;
  margin: 1.5rem 0;
}

.battery__editor {
  display: block;
  width: 100%;
  font-family: var(--mono);
  font-size: 0.9375rem;
  line-height: 1.6;
  padding: 1rem;
  border: 1px solid var(--grey-3);
  background: var(--paper);
  color: var(--ink);
  resize: vertical;
  tab-size: 4;
}

.battery__editor:focus { outline: 2px solid var(--ink); outline-offset: -1px; border-color: var(--ink); }

.battery__statement { border-left: 2px solid var(--ink); padding-left: 1.25rem; margin: 1.5rem 0; }

/* A test paper, not a drill: all twenty on one page, numbered, handed in once. */
.sheet { margin: 2rem 0; padding-left: 2rem; }
.sheet__item { margin-bottom: 1.25rem; }
.sheet__item label { display: block; margin-bottom: 0.375rem; }

.sheet__input {
  width: 100%;
  max-width: 34rem;
  font-family: var(--mono);
  font-size: 0.9375rem;
  padding: 0.5rem 0.625rem;
  border: 1px solid var(--grey-3);
  background: var(--paper);
  color: var(--ink);
}

.sheet__input:focus { outline: 2px solid var(--ink); outline-offset: -1px; border-color: var(--ink); }

.battery__control { margin: 2rem 0; }

/* Not .roll__actions: that cell is a flex row holding three buttons, and
   display:flex on a <td> drops it out of the table layout. Here each of the
   four observations is its own column, so the cell has to stay a cell. */
.battery__tick { padding: 0.7rem 0.25rem; text-align: center; }
.roll__btn--tick { min-width: 3.5rem; padding: 0.45rem 0.6rem; }
.battery__grid th { text-align: left; font-size: 0.6875rem; letter-spacing: 0.06em;
                    text-transform: uppercase; color: var(--grey-1); padding: 0.5rem 0.5rem; }
.battery__num { font-family: var(--mono); font-size: 0.875rem; text-align: right; padding-right: 1rem; }

.callout {
  border: 1px solid var(--ink);
  padding: 1.25rem 1.5rem;
  margin-bottom: 2.5rem;
}
.callout p { margin: 0 0 0.5rem; }
.callout p:last-child { margin: 1rem 0 0; }

.paste-note { font-size: 0.875rem; color: var(--ink); font-weight: 500; margin-top: 0.75rem; }
.battery__doubt { font-weight: 700; cursor: help; }

/* ------------------------------------------------------------ the daily loop
   Three items and a finish line. Separate from the dashboard because the
   dashboard is a report, and a report cannot be completed. */

.loop { border-top: 2px solid var(--ink); padding-top: 1.25rem; margin-bottom: 2.5rem; }

.loop__head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem 1rem;
  margin-bottom: 1.25rem;
}
.loop__date { margin: 0; font-weight: 500; }
.loop__streak { margin: 0; font-family: var(--mono); font-size: 0.8125rem; color: var(--grey-1); }
.loop__streak strong { font-size: 1rem; color: var(--ink); }

.loop__list { list-style: none; padding: 0; margin: 0; counter-reset: none; }

.loop__item {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
  padding: 1rem 0;
  border-bottom: 1px solid var(--grey-3);
}

.loop__tick { font-family: var(--mono); font-size: 1.125rem; line-height: 1.3; width: 1.25rem; }
.loop__item.is-done .loop__tick { font-weight: 700; }
.loop__item.is-done .loop__label { color: var(--grey-1); }

.loop__body { flex: 1; min-width: 0; }
.loop__label { display: block; font-size: 1.0625rem; font-weight: 500; }
.loop__detail { display: block; color: var(--grey-1); font-size: 0.9375rem; margin-top: 0.125rem; }
.loop__queue { display: block; font-size: 0.875rem; margin-top: 0.375rem; }

.loop__state {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--grey-1);
}

.loop__closed { margin-top: 1.25rem; font-weight: 500; }
.loop__open { margin-top: 1.25rem; color: var(--grey-1); }
.loop__elsewhere { margin-bottom: 2.5rem; }

.homework__steps { margin: 1.5rem 0 2rem; }
.homework__steps li { margin-bottom: 0.5rem; }
.homework__form { max-width: 34rem; }
.homework__form .field { margin-bottom: 1.25rem; }
.field__help { display: block; font-size: 0.8125rem; color: var(--grey-1); margin-top: 0.25rem; }

.verdict { font-family: var(--mono); font-size: 1rem; font-weight: 600; margin: 1.5rem 0 1rem; }
.verdict--pass { color: var(--ink); }
.verdict--fail { color: var(--ink); }
.verdict__detail { color: var(--grey-1); }
.verdict__input,
.verdict__source {
  border: 1px solid var(--grey-3);
  padding: 1rem;
  overflow-x: auto;
  font-size: 0.875rem;
  line-height: 1.6;
  margin-bottom: 2rem;
  white-space: pre-wrap;
  word-break: break-word;
}

/* -------------------------------------------------------------- workbench
   The editor, statement and examples, shared by practice and recall. The two
   pages mean different things and must feel identical to type in. */

.statement { border-left: 2px solid var(--ink); padding-left: 1.25rem; margin: 1.5rem 0 2rem; }
.workbench__heading { font-size: 1rem; margin-bottom: 0.75rem; }

.example {
  border: 1px solid var(--grey-3);
  padding: 0.875rem 1rem;
  margin-bottom: 0.75rem;
}
.example__row { margin: 0 0 0.25rem; font-size: 0.9375rem; }

/* The input drawn as a tree. Evidence, not decoration: the same list as the
   Input row above it, laid out the way it is on the board. Scrolls sideways
   rather than shrinking, so a wide tree never becomes unreadably small. */
.example__trees {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  margin: 0.5rem 0 0.75rem;
  overflow-x: auto;
}
.tree { margin: 0; }
.tree__svg { display: block; max-width: none; }
.tree__svg line { stroke: var(--grey-2); stroke-width: 1.5; }
.tree__svg circle { fill: var(--paper); stroke: var(--ink); stroke-width: 1.5; }
.tree__svg text {
  fill: var(--ink);
  font-family: var(--mono);
  font-size: 0.75rem;
  text-anchor: middle;
  dominant-baseline: central;
}
.tree__empty {
  margin: 0;
  padding: 0.375rem 0;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--grey-1);
}
.tree__node.is-marked circle { fill: var(--ink); }
.tree__node.is-marked text { fill: var(--paper); }
.tree__name {
  margin-top: 0.25rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--grey-1);
  text-align: center;
}
.example__row:last-child { margin-bottom: 0; }
.example__label {
  display: inline-block;
  min-width: 4rem;
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--grey-1);
}

.workbench { margin-top: 1.5rem; }
.workbench__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1rem;
}
.workbench__note { font-size: 0.8125rem; color: var(--grey-1); }

.report { margin-top: 2rem; }
.report__count { font-family: var(--mono); font-size: 0.75rem; font-weight: 400; color: var(--grey-1); }

.cases { width: 100%; border-collapse: collapse; margin-top: 1rem; }
.cases__row { border-bottom: 1px solid var(--grey-3); }
.cases__row td { padding: 0.5rem 0; vertical-align: top; }
.cases__mark { font-family: var(--mono); width: 1.75rem; }
.cases__row.is-wrong .cases__mark { font-weight: 700; }
.cases__input { font-size: 0.875rem; }
.cases__diff { display: block; font-size: 0.8125rem; color: var(--grey-1); margin-top: 0.125rem; }

/* Closed by default and quiet when open: this is looked up rather than read,
   and a list of twenty attempts left open would push the run you just did off
   the screen. */

.history { margin-top: 2rem; border-top: 1px solid var(--grey-3); padding-top: 1rem; }
.history__summary { cursor: pointer; font-size: 0.875rem; color: var(--grey-1); }
.history__list { list-style: none; margin: 1rem 0 0; padding: 0; }
.history__item { border-left: 2px solid var(--grey-3); padding: 0 0 0 0.75rem; margin-bottom: 1.25rem; }
.history__item--accepted { border-left-color: var(--ink); }
.history__line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; margin: 0; }
.history__verdict { font-weight: 700; font-size: 0.875rem; }
.history__when,
.history__count { font-family: var(--mono); font-size: 0.75rem; color: var(--grey-1); }
.history__error,
.history__input,
.history__complexity { font-size: 0.8125rem; color: var(--grey-1); margin: 0.25rem 0 0; }
.history__source { margin-top: 0.5rem; }
.history__source summary { cursor: pointer; font-size: 0.8125rem; color: var(--grey-1); }
.history__source pre { overflow-x: auto; font-size: 0.8125rem; margin-top: 0.5rem; }

/* ------------------------------------------------------------------ bench
   Problem on the left, editor on the right, each scrolling on its own.
   Stacked, the two fought: writing code pushed the statement off screen,
   which is the opposite of the habit the course is trying to build. */

.bench { display: grid; gap: 2rem; margin-bottom: 2.5rem; }

.bench__grip { display: none; }

@media (min-width: 60rem) {
  .bench {
    /* The fractions come from Alpine as inline custom properties, so the
       stored split is applied without a class per position. */
    grid-template-columns:
      minmax(0, var(--bench-left, 1fr))
      0.75rem
      minmax(0, var(--bench-right, 1fr));
    gap: 1.25rem;
    align-items: start;
  }

  /* Safe again now that the result appears under the editor. It was removed
     when the report lived in this column, because a result inside a scrolling
     box is a result nobody sees. */
  .bench__problem {
    position: sticky;
    top: 1.5rem;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
    padding-right: 0.5rem;
  }

  .bench__grip {
    display: block;
    align-self: stretch;
    min-height: 12rem;
    cursor: col-resize;
    background: var(--grey-3);
    border: 0;
    border-radius: 2px;
  }
  .bench__grip:hover,
  .bench__grip:focus-visible { background: var(--ink); outline: none; }
}

/* While dragging, stop the pointer selecting the text it passes over. */
body.is-resizing { user-select: none; cursor: col-resize; }
body.is-resizing-tall { user-select: none; cursor: row-resize; }

/* The editor's bottom edge. Full width, short, and the same hairline grey as
   the column divider so the two read as the same affordance. */
.editor__grip {
  height: 0.5rem;
  margin: 0.25rem 0 0.75rem;
  background: var(--grey-3);
  border-radius: 2px;
  cursor: row-resize;
}
.editor__grip:hover,
.editor__grip:focus-visible { background: var(--ink); outline: none; }

.sizer { display: inline-flex; align-items: baseline; gap: 0.4rem; }
.sizer__value {
  font-family: var(--mono);
  font-size: 0.6875rem;
  color: var(--grey-1);
  min-width: 2.5rem;
  text-align: center;
}
.linkish[disabled] { opacity: 0.35; cursor: default; text-decoration: none; }

/* Deliberately not sticky, and the left column is deliberately not a capped
   scrolling box. Both were tried; between them the result of a Run ended up
   off-screen in the other column, which reads as the button doing nothing.
   Two plain columns are duller and always show you what just happened. */

.bench__problem .statement { margin-top: 0; }

/* --- the editor ------------------------------------------------------- */

/* The plain textarea, which is also the fallback when CodeMirror does not
   load. A dropped script should cost colouring, not the lesson. */
.code-editor {
  display: block;
  width: 100%;
  font-family: var(--mono);
  font-size: 0.9375rem;
  line-height: 1.6;
  padding: 1rem;
  border: 1px solid var(--grey-3);
  background: var(--paper);
  color: var(--ink);
  resize: vertical;
  tab-size: 4;
}
.code-editor:focus { outline: 2px solid var(--ink); outline-offset: -1px; border-color: var(--ink); }

/* Structural. Colours below, scoped so the toggle can turn them off. */
.CodeMirror {
  /* Sized to the window, not a fixed block. At a flat 30rem the Run button
     sat below the fold on a 900px laptop: you scrolled down to press it, and
     the result appeared just past the bottom of the screen — which is what
     "I click Run and nothing happens" actually was. Capped so it does not
     become absurd on a large monitor, floored so it stays usable on a small
     one. */
  /* The stylesheet's answer is sized to the window; once somebody drags the
     bottom edge, their answer wins. */
  height: var(--editor-height, clamp(15rem, 42vh, 28rem));
  border: 1px solid var(--grey-3);
  font-family: var(--mono);
  font-size: var(--editor-font, 15px);
  line-height: 1.6;
}
.CodeMirror-focused { outline: 2px solid var(--ink); outline-offset: -1px; }
.CodeMirror-gutters { border-right: 1px solid var(--grey-3); }

/* Monochrome, and the default. Weight and italics rather than a syntax
   rainbow, which on a black and white site is the loudest thing on the page.
   Scoped to a class Alpine removes, so the colour palette underneath is
   CodeMirror's own and costs nothing to restore. */
.editor--mono .CodeMirror,
.editor--mono .CodeMirror-gutters { background: var(--paper); color: var(--ink); }
.editor--mono .CodeMirror-linenumber { color: var(--grey-2); }
.editor--mono .CodeMirror-cursor { border-left: 2px solid var(--ink); }
.editor--mono .CodeMirror-activeline-background { background: var(--grey-3); opacity: 0.35; }
.editor--mono .CodeMirror-matchingbracket { color: var(--ink); font-weight: 700; text-decoration: underline; }

.editor--mono .cm-s-default .cm-keyword,
.editor--mono .cm-s-default .cm-builtin,
.editor--mono .cm-s-default .cm-def,
.editor--mono .cm-s-default .cm-atom      { color: var(--ink); font-weight: 600; }
.editor--mono .cm-s-default .cm-variable,
.editor--mono .cm-s-default .cm-property  { color: var(--ink); }
.editor--mono .cm-s-default .cm-variable-2,
.editor--mono .cm-s-default .cm-variable-3,
.editor--mono .cm-s-default .cm-operator,
.editor--mono .cm-s-default .cm-number,
.editor--mono .cm-s-default .cm-meta      { color: var(--grey-1); }
.editor--mono .cm-s-default .cm-string    { color: var(--grey-1); font-style: italic; }
.editor--mono .cm-s-default .cm-comment   { color: var(--grey-2); font-style: italic; }

/* --- the bar above the editor ---------------------------------------- */

.workbench__bar {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: 0.5rem;
}
.workbench__tools { display: flex; gap: 1rem; }

.linkish {
  font: inherit;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--grey-1);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.linkish:hover { color: var(--ink); }

/* --- while it runs ---------------------------------------------------- */

/* htmx toggles .htmx-request on this while the submission is in flight. A
   button that sits there looking idle for a second reads as broken, and this
   is the single cheapest thing that makes software feel finished. */
.workbench__running {
  display: none;
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--grey-1);
}
.workbench__running.htmx-request { display: inline; }

.workbench button[disabled] { opacity: 0.5; cursor: wait; }

.console {
  border: 1px solid var(--grey-3);
  border-left: 2px solid var(--ink);
  padding: 0.75rem 1rem;
  overflow-x: auto;
  font-size: 0.875rem;
  line-height: 1.6;
  margin-bottom: 1.5rem;
  white-space: pre-wrap;
}

/* ------------------------------------------------------------- staffroom
   The instructor's three screens. Sorted lists rather than charts: what a
   teacher needs before a class is the names of the people to talk to, in
   order. Charts are for the student and for marketing. */

.today { border-top: 1px solid var(--grey-3); padding: 1.5rem 0; }
.today__batch { display: flex; gap: 1rem; align-items: baseline; font-size: 1.25rem; margin-bottom: 0.75rem; }
.today__count { font-family: var(--mono); font-size: 0.75rem; color: var(--grey-1); }
.today__queue { list-style: none; padding: 0; margin: 0 0 1rem; }
.today__queue li { padding: 0.25rem 0; }
.today__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1rem 0 1.5rem; }
.today__chase { list-style: none; padding: 0; margin: 0.5rem 0 0; }
.today__chase li { padding: 0.3rem 0; border-bottom: 1px solid var(--grey-3); }
.today__why, .today__none { font-family: var(--mono); font-size: 0.75rem; color: var(--grey-1); }
.today__none { font-family: inherit; font-size: 0.9375rem; }

.ok   { font-family: var(--mono); font-size: 0.6875rem; text-transform: uppercase; color: var(--grey-1); }
.warn { font-family: var(--mono); font-size: 0.6875rem; text-transform: uppercase; font-weight: 600; }

.roster { width: 100%; border-collapse: collapse; margin: 1.5rem 0 2.5rem; }
.roster th {
  text-align: left; font-size: 0.6875rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--grey-1);
  padding: 0.5rem 0.5rem 0.5rem 0; border-bottom: 1px solid var(--ink);
}
.roster td { padding: 0.6rem 0.5rem 0.6rem 0; border-bottom: 1px solid var(--grey-3); vertical-align: top; }
.roster .num { font-family: var(--mono); font-size: 0.875rem; }
.roster .of { font-family: var(--mono); font-size: 0.75rem; color: var(--grey-1); }
.roster .weakest { font-size: 0.875rem; }
/* Behind on something. A left rule rather than a colour, so it still reads
   on the projector and in a photocopy. */
.roster tr.is-behind td:first-child { border-left: 2px solid var(--ink); padding-left: 0.5rem; }

.btn--small { font-size: 0.8125rem; padding: 0.4rem 0.75rem; }

/* -------------------------------------------------------- queue a class */

.queue__head { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; }

.queue__list { list-style: none; margin: 0 0 1.5rem; padding: 0; }
.queue__item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--grey-3);
}
.queue__item form { margin: 0; }

.queue__meta { font-size: 0.8125rem; color: var(--grey-1); }
.queue__ok { font-family: var(--mono); font-size: 0.75rem; }
/* Weight rather than red: the whole site reads in one ink, and this screen
   gets looked at on a projector as often as on a laptop. */
.queue__missing { font-family: var(--mono); font-size: 0.75rem; font-weight: 600; color: var(--ink); }

.queue__add { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin: 0 0 1rem; }
.queue__add select { font: inherit; padding: 0.45rem 0.6rem; border: 1px solid var(--grey-2); max-width: 28rem; }

.queue__lines { list-style: none; margin: 1rem 0 1.5rem; padding: 0; }
.queue__line { padding: 0.5rem 0; border-bottom: 1px solid var(--grey-3); }
.queue__line label { display: flex; align-items: baseline; gap: 0.6rem; cursor: pointer; }
.queue__line code { font-family: var(--mono); font-size: 0.875rem; }
.queue__line .queue__meta { display: block; margin-left: 1.5rem; }
/* Suggested by the problem's own concepts. A rule, so the ones you are meant
   to glance at and accept are separable from the twenty you are not. */
.queue__line.is-suggested { border-left: 2px solid var(--ink); padding-left: 0.6rem; }
.today__drills { font-size: 0.875rem; color: var(--grey-1); margin: 0.5rem 0 0; }

/* --------------------------------------------------- today's problem */

/* Above the day's three, because it is what they were told to open. The rule
   down the left is the same device used for "worth a word tonight": weight
   without colour. */
.problem-today { border-left: 2px solid var(--ink); padding: 0.25rem 0 0.25rem 1rem; margin: 0 0 2rem; }
.problem-today__list { list-style: none; margin: 0.4rem 0 0; padding: 0; }
.problem-today__list li { margin: 0 0 0.35rem; }
.problem-today__link { font-family: var(--serif); font-size: 1.375rem; }
.problem-today__meta { font-size: 0.8125rem; color: var(--grey-1); margin-left: 0.5rem; }

.loop__drills { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.loop__drills li { margin: 0 0 0.3rem; font-size: 0.875rem; }
.loop__drills code { font-family: var(--mono); font-size: 0.8125rem; }
.loop__drills li.is-done a { color: var(--grey-1); text-decoration-line: line-through; }
.loop__reps { font-family: var(--mono); font-size: 0.75rem; color: var(--grey-1); margin-left: 0.5rem; }

/* The student's own statement of the cost. Optional and unmarked; it sits
   under the editor because that is where the thought happens, not beside the
   problem where it would read as part of the question.

   Time and space, side by side and equal in weight: two of the same block
   rather than a label-and-field row, so neither reads as the afterthought.
   They stack below 34rem, where two 16rem fields no longer fit side by side. */
.workbench__complexity { display: flex; gap: 0.75rem 1.25rem; margin: 0.75rem 0 0; flex-wrap: wrap; }
.workbench__cost { display: flex; flex-direction: column; gap: 0.3rem; flex: 1 1 16rem; max-width: 26rem; }
.workbench__complexity label {
  font-size: 0.6875rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--grey-1);
}
.workbench__optional { text-transform: none; letter-spacing: 0; font-style: italic; }
.workbench__complexity input {
  font-family: var(--mono); font-size: 0.875rem; padding: 0.35rem 0.5rem;
  border: 1px solid var(--grey-2); width: 100%; background: var(--paper); color: var(--ink);
}
.workbench__complexity input:focus { outline: 2px solid var(--ink); outline-offset: -1px; }

/* Who has handed tonight's problem in. Collapsed to a count, because the
   count is the question asked from the front of the room. */
.handed { margin: 0.35rem 0 0.75rem; }
.handed summary { font-size: 0.875rem; cursor: pointer; color: var(--grey-1); }
.handed summary strong { color: var(--ink); font-family: var(--mono); }
.handed__list { list-style: none; margin: 0.5rem 0 0 1rem; padding: 0; }
.handed__list li { font-size: 0.875rem; margin: 0 0 0.2rem; }
/* Nobody home. A left rule, the same device the roster uses for behind. */
.handed__list li.is-missing { border-left: 2px solid var(--ink); padding-left: 0.5rem; }
.handed__note { font-family: var(--mono); font-size: 0.6875rem; text-transform: uppercase; color: var(--grey-1); }


.checkout { max-width: 34rem; padding-top: 4rem; }
.checkout__fallback { margin: 2rem 0 0; }
.checkout__note { font-size: 0.875rem; color: var(--grey-1); margin-top: 1.5rem; }

/* ------------------------------------------------------------- receipt */

/* A document rather than a page: narrow, quiet, and printable without a
   stylesheet switch doing anything clever. */
.receipt { max-width: 42rem; padding-top: 3rem; padding-bottom: 4rem; }
.receipt__head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem; flex-wrap: wrap; border-bottom: 1px solid var(--ink); padding-bottom: 1rem;
}
.receipt__number { font-family: var(--mono); font-size: 1.25rem; margin: 0.2rem 0 0; letter-spacing: 0; }
.receipt__date { margin: 0; color: var(--grey-1); font-size: 0.875rem; }

.receipt__parties { display: flex; flex-wrap: wrap; gap: 2rem 4rem; margin: 2rem 0; }
.receipt__parties > div { flex: 1 1 14rem; }
.receipt__block { margin: 0.4rem 0 0; line-height: 1.5; }
.receipt__gstin { font-family: var(--mono); font-size: 0.8125rem; margin: 0.5rem 0 0; }

.receipt__lines { margin: 2rem 0 1rem; }
.receipt__total td { font-weight: 600; border-top: 1px solid var(--grey-2); }
.receipt__note { font-size: 0.8125rem; color: var(--grey-1); margin: 1rem 0 0; }

/* The browser is the PDF generator. Everything that is chrome goes, and the
   page prints on white whatever the reader's theme is. */
@media print {
  .receipt { max-width: none; padding: 0; }
  .receipt__hide-in-print, .masthead, .footer { display: none !important; }
  a { text-decoration: none; color: inherit; }
}

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

.marks__note, .marks__scale { font-size: 0.875rem; color: var(--grey-1); margin-top: 1.5rem; }

/* The rubric. Reference rather than news — a rule to set it apart, smaller and
   greyer than the marks above, and never competing with the three figures at
   the top of the page, which are what the page is for. */
.rubric {
  margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--grey-2);
  font-size: 0.875rem; color: var(--grey-1); max-width: 44rem;
}
.rubric__head {
  font-size: 0.6875rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--grey-1); margin: 0 0 1rem;
}

/* The weights, as pairs that stay together when the row wraps. */
.rubric__list { display: flex; flex-wrap: wrap; gap: 0.5rem 2rem; margin: 0 0 1.25rem; }
.rubric__list > div { display: flex; align-items: baseline; gap: 0.5rem; }
.rubric__list dt { color: var(--ink); font-weight: 500; }
.rubric__list dd { margin: 0; font-family: var(--mono); color: var(--ink); }
.rubric__each { font-family: var(--sans); font-size: 0.75rem; color: var(--grey-1); }

.rubric__note { margin: 0 0 1.25rem; }
.rubric__rules { margin: 0; padding-left: 1.1rem; }
.rubric__rules li { margin: 0 0 0.6rem; }
.rubric__rules strong { color: var(--ink); }
.marks td:first-child { font-weight: 500; }

.mark__input {
  font-family: var(--mono); font-size: 0.9375rem; width: 3.5rem;
  padding: 0.3rem 0.4rem; border: 1px solid var(--grey-2); text-align: right;
}
.mark__input:focus { outline: 2px solid var(--ink); outline-offset: -1px; }

/* The criterion boxes, in a row that wraps. Each is labelled above rather than
   beside, because four labels on one line push the boxes off the screen and
   the label is what tells you which number you are typing. */
.mark__parts { display: flex; flex-wrap: wrap; gap: 0.5rem 0.75rem; align-items: flex-end; justify-content: flex-end; }
.mark__part { display: inline-flex; flex-direction: column; gap: 0.2rem; align-items: flex-end; }
.mark__part-name {
  font-size: 0.625rem; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--grey-1); white-space: nowrap;
}
/* The sum, shown but never typed into. It is the mark, and seeing it move as
   the boxes are filled is what makes the breakdown feel like the mark rather
   than paperwork beside it. */
.mark__total { font-family: var(--mono); font-weight: 600; align-self: flex-end; padding-left: 0.5rem; }

.mark__note {
  font: inherit; font-size: 0.8125rem; width: 100%; margin-top: 0.5rem;
  padding: 0.4rem 0.5rem; border: 1px solid var(--grey-2);
  background: var(--paper); color: var(--ink); resize: vertical; text-align: left;
}
.mark__note:focus { outline: 2px solid var(--ink); outline-offset: -1px; }

.mark__repo { font-size: 0.875rem; }
.mark__repo .roll__phone { display: block; }

/* --- a project, from the student's side --- */

.project__brief { margin: 0 0 2rem; }
.project__heading { font-size: 1rem; margin: 2.5rem 0 0.75rem; }
.project__state { margin-bottom: 1.5rem; }
.project__state td:first-child { color: var(--grey-1); width: 12rem; }
.project__recheck { margin: 0 0 1rem; }
.project__breakdown { margin-bottom: 1rem; }
.project__total td { font-weight: 600; border-top: 1px solid var(--grey-2); }

/* Late and on time, said in words. Colour alone would carry it for most
   people and nobody else, and this is a page about a mark. */
.project__late { font-weight: 600; }
.project__ok { color: var(--grey-1); }
.project__closed { margin: 1.5rem 0 0; }

/* --- the stack --- */

/* A live project is a section with a rule above it, so two of them read as two
   things rather than one long page. */
.project__live + .project__live,
.project__live + .project__done,
.project__done + .project__done,
.project__done + .project__live { border-top: 1px solid var(--grey-2); padding-top: 2rem; margin-top: 2.5rem; }

.project__name { font-size: 1.375rem; margin: 0 0 0.25rem; }

/* Finished, folded. The summary carries the two things worth seeing without
   opening it: what it was called and what it earned. */
.project__done > summary {
  cursor: pointer; padding: 0.75rem 0; font-size: 1rem;
  display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: baseline;
}
.project__done > summary:hover { color: var(--grey-1); }
.project__done-meta { font-size: 0.875rem; color: var(--grey-1); }
.project__done-body { padding-left: 1rem; border-left: 1px solid var(--grey-2); }

.queue__note { max-width: 42rem; margin: 0 0 1.5rem; }
.queue__note label { display: block; margin: 1rem 0 0.35rem; }
.queue__note input[type=text], .queue__note textarea {
  font: inherit; width: 100%; padding: 0.5rem 0.6rem;
  border: 1px solid var(--grey-2); background: var(--paper); color: var(--ink);
}
.queue__note textarea { resize: vertical; min-height: 5rem; }
.queue__note input:focus, .queue__note textarea:focus { outline: 2px solid var(--ink); outline-offset: -1px; }
.queue__note button { margin-top: 1rem; margin-right: 0.75rem; }

.queue__cancel { max-width: 42rem; margin: 0 0 1.5rem; }
.queue__cancel p { margin: 0 0 0.75rem; }

/* ------------------------------------------------------------- still pending */

/* Renders only when there is something to render. A student who is up to date
   sees the page end after the day's three, which is the reward. */
.pending { border-top: 1px solid var(--grey-2); margin: 2.5rem 0 0; padding-top: 1.5rem; }
.pending__head { font-size: 1rem; margin: 0 0 0.25rem; }
.pending__lede { font-size: 0.875rem; color: var(--grey-1); max-width: var(--measure); margin: 0 0 1rem; }
.pending__list { list-style: none; margin: 0; padding: 0; }
.pending__group { margin: 0 0 0.9rem; }
.pending__what { margin: 0; font-size: 0.9375rem; }
.pending__what strong { font-family: var(--mono); }
.pending__since { color: var(--grey-1); font-size: 0.8125rem; margin-left: 0.4rem; }
.pending__items { margin: 0.15rem 0 0; font-size: 0.875rem; }
.pending__items code { font-family: var(--mono); font-size: 0.8125rem; }
.pending__more { color: var(--grey-1); }

/* ------------------------------------------------------------------ classes */

.klass { border-top: 1px solid var(--grey-3); padding: 1.25rem 0; }
.klass__head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
.klass__head h3 { font-size: 1.0625rem; margin: 0; }
.klass__meta { margin: 0; font-size: 0.8125rem; color: var(--grey-1); }
.klass__note { margin: 0.5rem 0 0; max-width: var(--measure); }
.klass__note p { margin: 0 0 0.5rem; }
.klass__concepts { font-family: var(--mono); font-size: 0.8125rem; color: var(--grey-1); margin: 0.4rem 0 0; }
.klass__problems { margin: 0.75rem 0 0; padding-left: 1.1rem; }
.klass__problems li { margin: 0 0 0.4rem; }
.klass__topic { font-size: 0.75rem; color: var(--grey-1); margin-left: 0.4rem; }
.klass__state { display: block; font-family: var(--mono); font-size: 0.75rem; color: var(--grey-1); }
.klass__drills { list-style: none; margin: 0.6rem 0 0; padding: 0; }
.klass__drills li { font-size: 0.875rem; margin: 0 0 0.2rem; }
.klass__drills code { font-family: var(--mono); font-size: 0.8125rem; }
.klass__drills li.is-done a { color: var(--grey-1); text-decoration-line: line-through; }
.klass__empty { font-size: 0.875rem; color: var(--grey-1); margin: 0.6rem 0 0; }

/* The instructor's message on Today. Set apart from the problem above it and
   the contract below it, because it is the one thing on this page addressed
   to the student rather than asked of them. */
.message { background: var(--grey-3); padding: 1rem 1.25rem; margin: 0 0 2rem; }
.message__body { max-width: var(--measure); }
.message__body p { margin: 0 0 0.5rem; }
.message__body p:last-child { margin-bottom: 0; }

.homework__check { margin: 1.5rem 0 0.5rem; }
.homework__check .field__help { display: block; margin-top: 0.5rem; max-width: var(--measure); }
.homework__failed { max-width: var(--measure); margin: 1rem 0; }
/* Was the heading over the screenshot fallback; now the heading over the
   conditions the LeetCode check needs to see a solve. Same weight — it is
   still secondary to the button above it. */
.homework__fallback { font-size: 1rem; margin: 2.5rem 0 0.35rem; }

/* ================================================================== *
 * Phones.
 *
 * The layout already reflows without overflowing at 360px — it was built
 * on grid with real breakpoints. What it did not do was size anything for
 * a thumb: the navigation was seven 25px links wrapped over three rows,
 * eating 123px of an 844px screen, and the editor's own accessibility
 * controls were 20px tall. 44px is the figure both Apple and Google
 * publish, and it is roughly the width of an adult fingertip.
 * ================================================================== */

@media (max-width: 860px) {
  /* The navigation moves to its own full-width row and scrolls sideways
     rather than wrapping. Three wrapped rows at a usable tap height would
     be 132px; one scrolling row is 48, and reads like a tab bar, which is
     the pattern a phone user already knows. */
  .masthead__inner {
    flex-wrap: wrap;
    gap: 0;
    min-height: 0;
    padding-top: 0.5rem;
  }

  .masthead nav {
    flex: 1 0 100%;
    margin-left: 0;
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 1.5rem;
    scrollbar-width: none;
    /* The row is flush to the page gutter but scrolls edge to edge, so the
       last link does not look cut off by the margin. */
    margin-inline: -1.5rem;
    padding-inline: 1.5rem;
  }

  .masthead nav::-webkit-scrollbar { display: none; }

  .masthead nav a,
  .masthead__signout button,
  .masthead__who {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }

  .wordmark, .masthead__phone { min-height: 44px; display: inline-flex; align-items: center; }

  /* Editor controls. These exist for students on tired laptops; on a phone
     they were the least tappable thing on the site, which is the wrong way
     round for a control whose whole job is accessibility. */
  .linkish {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  .workbench__tools { gap: 1.25rem; row-gap: 0; }
}

/* ------------------------------------------------------- the enrolment page
 *
 * The form is unchanged; what is above it is new. Somebody on this page has
 * decided in principle and is about to type a password, and until now the
 * last thing they saw before doing that was eleven empty boxes.
 *
 * Both blocks borrow classes tyd.css already defines — `.figures` and
 * `.steps` — so this is spacing only. The homepage's own `.amt` / `.who` /
 * `.feat` are in home.css, which this page deliberately does not load.
 */
.enrol__figures { margin-top: 2.5rem; }

.enrol__figures dd { font-size: clamp(1.75rem, 4vw, 2.5rem); }

.enrol__included {
  margin-top: 2.5rem;
  padding-top: 2rem;
  border-top: 1px solid var(--grey-2);
}

/* The form is the point of the page and has to stay findable under the
   argument for it, rather than reading as the last item of the list above. */
.enrol__included + .form { margin-top: 3rem; }

/* The password box with its Show button on the same line. */
.form__reveal { display: flex; gap: 0.5rem; align-items: stretch; }
.form__reveal input { flex: 1; min-width: 0; }
.form__reveal .btn { flex: none; }

/* What is starting soon: read, not chosen, so it is a plain list. */
.enrol__upcoming ul { margin: 0.35rem 0 0; padding-left: 1.1rem; }
.enrol__upcoming li { margin: 0.2rem 0; font-variant-numeric: tabular-nums; }

/* Payer name and coupon, folded under one line. */
.enrol__extras { margin-top: 1.25rem; }
.enrol__extras summary {
  cursor: pointer;
  font-size: 0.9375rem;
  color: var(--grey-1);
}
.enrol__extras[open] summary { margin-bottom: 1rem; }

/* ------------------------------------------------------------- live class
 *
 * The student's half is two things: a bar saying they are being watched, and
 * a note under a line. The bar is black on purpose — it is the one thing on
 * the page that must not be overlooked, because sharing without saying so is
 * the version of this that should never exist.
 */
.live-bar {
  margin: 0 0 0.75rem;
  padding: 0.5rem 0.75rem;
  background: var(--ink);
  color: var(--paper);
  font-size: 0.875rem;
}

.live-note {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
  margin: 0.25rem 0 0.5rem;
  padding: 0.5rem 0.75rem;
  border-left: 3px solid var(--ink);
  background: var(--grey-3);
  font-family: var(--sans);
  font-size: 0.875rem;
  white-space: normal;
}
.live-note__from {
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--grey-1);
}
.live-note__text { flex: 1 1 12rem; }

/* The line the instructor is pointing at, for a few seconds. */
.live-point {
  background: var(--grey-2);
  box-shadow: inset 4px 0 0 var(--ink);
}

/* The instructor's page: a panel per editor, two across on a laptop. */
.live__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  margin-bottom: 1rem;
}
.live__status { margin: 0; }
.live__explain { color: var(--grey-1); margin-top: 0; }
.live__missing { color: var(--grey-1); font-size: 0.875rem; }
.live__tally { margin: 0 0 0.5rem; font-weight: 600; }

.live__notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 1rem 0 0;
}
.live__notice[hidden] { display: none; }
.live__notice input { flex: 1 1 16rem; }
.live__notices { margin-bottom: 1rem; }

.live__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 26rem), 1fr));
  gap: 1rem;
}

.live-panel {
  min-width: 0;
  padding: 0.75rem;
  border: 1px solid var(--grey-2);
}
.live-panel--wide { grid-column: 1 / -1; }
/* Idle past three minutes, or last run failed. Heavier rather than coloured,
   on a site that is black and white. */
.live-panel--stuck { border: 2px solid var(--ink); padding: calc(0.75rem - 1px); }

.live-panel__head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}
.live-panel__name { margin: 0; font-weight: 600; flex: 1; }
.live-panel__state { font-size: 0.8125rem; }
.live-panel__state.is-idle { color: var(--grey-1); }
.live-panel__meta,
.live-panel__result { margin: 0.25rem 0 0; font-size: 0.8125rem; color: var(--grey-1); }

.live-panel__code { margin-top: 0.5rem; }
.live-panel__code .CodeMirror { height: 18rem; font-size: 0.8125rem; }
.live-panel--wide .live-panel__code .CodeMirror { height: 32rem; font-size: 0.9375rem; }
.live-panel__code .CodeMirror-linenumber { cursor: pointer; }
.live-panel__code .CodeMirror-cursors { display: none; }

/* Where the student's cursor is. Their real one is in their browser; this is
   a drawing of it. */
.live-caret {
  border-left: 2px solid var(--ink);
  margin-left: -1px;
  margin-right: -1px;
}

.live-panel__note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.live-panel__note[hidden] { display: none; }
.live-panel__note input { flex: 1 1 12rem; }
.live-panel__label { font-size: 0.8125rem; color: var(--grey-1); }
.live-panel__error { flex-basis: 100%; margin: 0; font-size: 0.8125rem; }
.live-panel__error:empty { display: none; }

.live-panel__notes { list-style: none; margin: 0.5rem 0 0; padding: 0; font-size: 0.8125rem; }
.live-panel__sent { display: flex; gap: 0.5rem; padding: 0.25rem 0; border-top: 1px solid var(--grey-3); }
.live-panel__line,
.live-panel__seen { color: var(--grey-1); white-space: nowrap; }
.live-panel__sent > span:nth-child(2) { flex: 1; }
.live-panel__sent.is-seen .live-panel__seen { color: var(--ink); }

/* ------------------------------------------------------------------ vim
 *
 * Alpine removes x-cloak once it has decided; until then, nothing that
 * depends on its answer is shown — otherwise the Vim mode label flashes on
 * the page of somebody who has never turned Vim on.
 */
[x-cloak] { display: none !important; }

.editor__mode {
  margin: 0.25rem 0 0;
  font-family: var(--mono);
  font-size: 0.8125rem;
  color: var(--grey-1);
}

/* Vim's block cursor in normal mode. CodeMirror's own is bright green, which
   on the monochrome theme is the loudest thing on the page. */
.editor--mono .cm-fat-cursor .CodeMirror-cursor { background: var(--grey-2); }
.editor--mono .cm-fat-cursor-mark { background: var(--grey-3); }

/* The : and / prompt, in the site's type rather than the browser's. */
.CodeMirror-dialog { font-family: var(--mono); font-size: 0.875rem; }
.CodeMirror-dialog input { font-family: var(--mono); }

/* ---------------------------------------------------------------- night
 *
 * The same black-and-white site, at night: the five colours swapped, and
 * nothing else. Set only on the pages a student works in (see
 * templates/_night_head.html), and only when they ask for it.
 *
 * Not pure black and white, which glares in a dark room: off-black paper and
 * off-white ink. The greys keep the jobs they have by day — secondary text
 * readable, hairlines faint, fills barely there.
 */
html[data-theme="night"] {
  --ink: #e6e6e6;
  --paper: #141414;
  --grey-1: #a3a3a3;   /* secondary text */
  --grey-2: #555;      /* hairlines */
  --grey-3: #262626;   /* block fills */
  --grey-4: #1d1d1d;   /* the gym's answer box */

  /* Scrollbars, checkboxes and date pickers drawn dark by the browser. */
  color-scheme: dark;
}

.portal__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
}
.portal__night { flex: none; margin-top: 0.35rem; }

/* CodeMirror's own stylesheet paints the editor white with black text. The
   monochrome theme already reads the site's colours; these make the base, and
   the colour theme, follow at night too. */
html[data-theme="night"] .CodeMirror { background: var(--paper); color: var(--ink); }
html[data-theme="night"] .CodeMirror-gutters { background: var(--paper); border-right-color: var(--grey-3); }
html[data-theme="night"] .CodeMirror-linenumber { color: var(--grey-2); }
html[data-theme="night"] .CodeMirror-cursor { border-left-color: var(--ink); }
html[data-theme="night"] .CodeMirror-selected,
html[data-theme="night"] .CodeMirror-focused .CodeMirror-selected { background: #3a3a3a; }
html[data-theme="night"] .CodeMirror-line::selection,
html[data-theme="night"] .CodeMirror-line > span::selection,
html[data-theme="night"] .CodeMirror-line > span > span::selection { background: #3a3a3a; }
html[data-theme="night"] .CodeMirror-activeline-background { background: #1e1e1e; }
html[data-theme="night"] .CodeMirror-dialog-bottom,
html[data-theme="night"] .CodeMirror-dialog-top { border-color: var(--grey-3); }

/* The colour theme, lightened to read on a dark page. The same hues as
   CodeMirror's defaults, so a student who chose colour sees the colours they
   chose — just not ones meant for white paper. Scoped to colour mode: the
   monochrome theme already reads the site's colours, and these are specific
   enough to override it if they were not. */
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-keyword  { color: #c792ea; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-atom     { color: #82aaff; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-number   { color: #8fd18f; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-def      { color: #82aaff; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-variable-2 { color: #7fb6e0; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-variable-3,
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-type     { color: #6fcf9f; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-comment  { color: #c9a26b; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-string   { color: #f07f7f; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-string-2 { color: #f5a06a; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-meta,
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-qualifier { color: #a3a3a3; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-builtin  { color: #b4a7f5; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .cm-s-default .cm-bracket  { color: #c8c88a; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .CodeMirror-matchingbracket { color: #8fd18f; }
html[data-theme="night"] .bench__editor:not(.editor--mono) .CodeMirror-nonmatchingbracket { color: #f07f7f; }
