/* §6's visual system, settled in PR #7. Every colour here is a token from
   §6.1's table and no screen hard-codes a raw value outside :root. The type
   scale is §6.2's, given outright because the handoff makes no framework
   choice. §6.3: one committed light theme, phone-first, 360px is the design
   width, no motion system. */

:root {
  --chrome:      #16261f;
  --chrome-line: #24382f;
  --brand:       #35594b;
  --field:       #e9e3d4;
  --card:        #ffffff;
  --ink:         #16261f;
  --ink-muted:   #5f5b4e;
  --line:        #ddd6c6;
  --cream:       #f5f0e3;
  --cream-muted: #c6d2c9;
  --gold:        #d9a441;   /* fill only */
  --gold-ink:    #7a5410;   /* text on light surfaces */
  /* §8.2's CTA hover: gold lightened 20% toward cream. A declared
     token because §6.1 forbids a screen hard-coding a raw colour, and
     a hover computed inline is a raw colour nobody audits. Ink on it
     is 8.08:1 — lightening the fill can only raise it. */
  --gold-lift:   #dfb361;   /* fill only, hover of --gold */
  --rust:        #9c4526;

  --radius: 0.625rem;
  --unit: 0.25rem;

  /* §6.2's scale */
  --label: 0.625rem;
  --sm:    0.8125rem;
  --base:  1rem;
  --lg:    1.125rem;
  --xl:    1.25rem;
  --xxl:   1.5rem;
  --xxxl:  1.875rem;
}

/* §6.2's typeface, self-hosted. The stack below named Archivo from the start
   and nothing ever defined it, so every screen rendered in Helvetica -- the
   intent was in the CSS and only the file was missing.

   TWO FILES, NOT SIX. §6.2 asks for a width axis and a *real drawn* italic,
   and both are axis-level requirements a set of static weights cannot meet:
   `font-stretch: 92%` has nothing to interpolate without `wdth`, and a
   synthesized oblique is the thing §6.2 rules out by name. So these are the
   variable faces, roman and italic, carrying `wght` and `wdth`.

   THE RANGES ARE THE ONES THE SCREENS ASK FOR. Upstream ships wght 100-900 and
   wdth 62-125; the CSS uses five weights (400, 500, 600, 700, 800) and exactly
   two widths (92% for .wordmark, 100% everywhere else). The design space is
   clipped to that before subsetting, and that clip is the bigger of the two
   levers: the charset takes the pair 348KB -> 273KB, the axes take it 273KB ->
   159KB. Both faces together are now 156KiB. The descriptors below declare the
   clipped ranges honestly, so a weight outside 400-800 clamps rather than
   rendering a lie. Widen the descriptor *and* rebuild the file if §8 ever
   needs a 900.

   Latin-1 + Latin Extended-A, plus the punctuation the screens actually emit
   (§ · -- ... -> <- x and the minus). `tnum` is kept through the subset because
   §6.2's tabular figures are a feature lookup, not a glyph. Rebuild:
   `tools/build-fonts.py`, which carries the full reasoning.

   ARCHIVO IS NOT A COMPLETE FALLBACK. It has no glyph for the rank arrows
   (U+25B2/U+25BC) or the warning sign (U+26A0); those three keep resolving
   down the stack to Helvetica exactly as they did before, which is why the
   stack stays declared in full on `body`. */
@font-face {
  font-family: Archivo;
  src: url("/static/archivo-var.woff2") format("woff2");
  font-weight: 400 800;
  font-stretch: 92% 100%;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: Archivo;
  src: url("/static/archivo-italic-var.woff2") format("woff2");
  font-weight: 400 800;
  font-stretch: 92% 100%;
  font-style: italic;
  font-display: swap;
}

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

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

body {
  margin: 0;
  background: var(--field);
  color: var(--ink);
  font-family: Archivo, "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: var(--base);
  line-height: 1.45;
}

/* §6.2: a .wordmark treatment -- weight 800, italic, width 92%,
   letter-spacing -0.02em -- for the site wordmark, headline numbers, and the
   primary number on any feature block. */
.wordmark {
  font-weight: 800;
  font-style: italic;
  font-stretch: 92%;
  letter-spacing: -0.02em;
}

/* §6.2: every numeric value in a column uses tabular figures. */
.tabular { font-variant-numeric: tabular-nums; }

.sm    { font-size: var(--sm); line-height: 1.35; }
.lg    { font-size: var(--lg); line-height: 1.3; }
.xl    { font-size: var(--xl); line-height: 1.25; }
.xxxl  { font-size: var(--xxxl); line-height: 1.1; }
.muted { color: var(--ink-muted); }

/* §6.3: centred at a max width of roughly 48rem, 1.5rem horizontal padding. */
.wrap {
  max-width: 48rem;
  margin: 0 auto;
  padding: 0 1.5rem;
}

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

.chrome {
  background: var(--chrome);
  border-bottom: 1px solid var(--chrome-line);
}
.chrome-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 56px;
  flex-wrap: wrap;
}
.chrome a.wordmark {
  color: var(--cream);
  text-decoration: none;
  font-size: var(--lg);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
/* §7: "Links wrap rather than scroll horizontally on a phone." The wrap was on
   `.chrome-inner` and not on the nav itself, so five links happened to fit at
   360px and a sixth did not — adding **Admin** for the commissioner (D20) put
   14px of the page off the right edge. `min-width: 0` is the half that makes
   the wrap reachable: a flex item will not shrink below its content's intrinsic
   width without it, so the nav kept its one-line width and pushed the row out
   instead of wrapping inside it. */
.chrome nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  min-width: 0;
}
.chrome nav a {
  color: var(--cream-muted);
  text-decoration: none;
  font-size: var(--sm);
  /* §6.3: minimum touch target 44px on every interactive element. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.chrome nav a.current { color: var(--cream); font-weight: 600; }

/* §6.3: a visible focus ring on every interactive element, using
   focus-visible so it appears for keyboard and not for touch. */
:focus-visible {
  outline: 2px solid var(--chrome);
  outline-offset: 2px;
}
.chrome :focus-visible { outline-color: var(--cream); }

/* ------------------------------------------------------------- typography */

/* §6.2's vertical rhythm: 1.25rem between a page heading and the first block,
   1rem between stacked blocks and cards, 0.5rem between a section label and
   the card it introduces. */
h1 {
  font-size: var(--xxxl);
  line-height: 1.1;
  font-weight: 700;
  margin: 1.5rem 0 0;
}
h1 + .detail { margin: calc(var(--unit) * 2) 0 1.25rem; }
.detail { color: var(--ink-muted); font-size: var(--sm); }

.label {
  font-size: var(--label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  margin: 0;
}
.section-label {
  font-size: var(--label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-muted);
  margin: 1rem 0 0.5rem;
}

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

.card {
  background: var(--card);
  border-radius: var(--radius);
  padding: 1.125rem;
  margin: 1rem 0;
}
/* §6.3: cards come in two forms -- padded (default) and flush, for lists
   whose rows own their own inset. */
.card.flush { padding: 0; overflow: hidden; }

/* §6.4's stat block: the brand-green block for the one number a screen is
   really about. §6.1: nothing gold can live on brand green -- text on brand
   is cream (6.88:1) or cream-muted (5.02:1), and nothing else. */
.stat-block {
  background: var(--brand);
  border-radius: var(--radius);
  padding: 1.125rem 1.25rem;
  margin: 1rem 0;
  color: var(--cream);
}
.stat-block .label { color: var(--cream-muted); }
.stat-value { margin: calc(var(--unit) * 2) 0 0; color: var(--cream); }
.stat-detail {
  margin: calc(var(--unit) * 1) 0 0;
  color: var(--cream-muted);
  font-size: var(--sm);
}

/* §6.4's notice, in three weights. */
.notice {
  background: var(--card);
  border-radius: var(--radius);
  padding: 0.875rem 1.125rem;
  margin: 1rem 0;
  font-size: var(--sm);
}
.notice-warning {
  background: var(--field);
  border-left: 3px solid var(--ink);
  box-shadow: inset 0 0 0 1px var(--line);
}
.notice-error { border-left: 3px solid var(--rust); }

/* §6.4's button, primary and quiet, usable as an action and as a link. */
.button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 1rem;
  border-radius: var(--radius);
  font-size: var(--sm);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}
.button.quiet {
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
}
.button.quiet:hover { background: var(--field); }
/* A quiet button whose action is destructive takes §6.1's rust on its **text**,
   never as a fill: rust ink is 6.38:1 on `card` and 4.98:1 on the `field` the
   quiet button hovers to, both past AA, while rust as a background would need a
   second text colour nobody has audited. Needs `.button` in the selector
   because `.button.quiet` sets `color` at the same specificity `.rust-ink`
   has — the class alone renders ink and looks like it worked. */
.button.rust-ink { color: var(--rust); }

/* ------------------------------------------------------- §8.6 the board */

.board { list-style: none; margin: 1rem 0; }
.board-row {
  display: grid;
  /* rank ~1.25rem, movement ~1.75rem, name flexible, gap ~4rem, points ~4rem */
  grid-template-columns: 1.5rem 1.75rem 1fr 3.25rem 4rem;
  align-items: center;
  gap: calc(var(--unit) * 2);
  padding: 0.625rem 1rem;
  min-height: 44px;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid transparent;
  position: relative;
}
.board-row:last-child { border-bottom: 0; }

/* §8.6: your own row is tinted to the field colour and carries a 3px gold
   inset rail on its left edge (§6.4's inset rail, gold for "you"). */
.board-row.mine {
  background: var(--field);
  border-left-color: var(--gold);
}
.board-row .rank { font-weight: 700; color: var(--ink-muted); }
.board-row.mine .rank { color: var(--gold-ink); }

.board-row .who { min-width: 0; }
.board-row .who a {
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* §6.3: minimum touch target 44px, and "a row that is a button counts as one
   target; it does not gain a second." §8.6 puts the link on the *name*, so the
   link is stretched to cover its whole row rather than adding a second
   tappable element beside it -- one target, full row, 44px by construction. */
.board-row .who a::after {
  content: "";
  position: absolute;
  inset: 0;
}
.board-row .who a:focus-visible::after {
  outline: 2px solid var(--chrome);
  outline-offset: -2px;
}
.board-row.mine .who a { font-weight: 700; }
.board-row .subline {
  display: block;
  font-size: var(--sm);
  color: var(--ink-muted);
}
.board-row .gap {
  text-align: right;
  font-size: var(--sm);
  color: var(--ink-muted);
}
.board-row .points {
  text-align: right;
  font-size: var(--lg);
  font-weight: 800;
  font-style: italic;
  font-stretch: 92%;
  letter-spacing: -0.02em;
}
/* §6.1: gold-ink for gold-meaning text on light surfaces; rust means falling. */
.move.up   { color: var(--gold-ink); font-weight: 700; }
.move.down { color: var(--rust); font-weight: 700; }
.move.none { color: var(--ink-muted); }
.movement  { font-size: var(--sm); }

/* --------------------------------------------------- §8.5 / §8.3 lineups */

.manager-card { padding: 1rem 1.125rem; }
/* T-11, measured 2026-08-07 by /design-review rather than argued.
 *
 * At 360x640 the reveal is 16.9 screens collapsed and 31.8 with every
 * breakdown open, and on **52% of the expanded page no manager name is on
 * screen at all** -- an expanded card runs 1120-1308px against a 640px
 * viewport, so the name is guaranteed to scroll off while you are still
 * inside that manager's lineup. That is the failure: not finding a manager,
 * but losing track of which one you are already reading.
 *
 * Sticky is the fix that matches it. The header carries the name AND the
 * total, so what pins is the answer to both "whose is this" and "what did
 * they score". `background` is required -- a sticky element with a
 * transparent background scrolls the rows underneath straight through it. */
.manager-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--card);
  padding-top: 0.5rem;
  margin-top: -0.5rem;
  z-index: 1;
}
/* T-11's index. A wrapping row of name chips, not a column: fourteen names
 * stacked would be its own screen of scrolling, which is the problem rather
 * than the fix. Chips are 44px tall so a thumb hits them (§6.3). */
.manager-index ul {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}
.manager-index a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  font-size: var(--sm);
  text-decoration: none;
  /* Explicit, because a link with no `color` renders in the user agent's
   * default blue -- which is not in §6's palette and was exactly what the
   * first draft of this index shipped. `.button.quiet` sets `--ink` at this
   * same specificity for the same reason. */
  color: var(--ink);
}
/* §8.5's cards are `scroll-margin-top`ed so a jump does not land the name
 * underneath the sticky header it shares a page with. */
.manager-card { scroll-margin-top: 0.5rem; }

.manager-head h2 { margin: 0; font-size: var(--lg); }
.manager-head p { margin: 0; }
.manager-total {
  font-size: var(--lg);
  font-weight: 800;
  font-style: italic;
  font-stretch: 92%;
  color: var(--ink-muted);
  margin: 0;
  white-space: nowrap;
}
/* §6.1: gold moves to points once any score exists for the week. */
.manager-total.has-score { color: var(--gold-ink); }

.slots { list-style: none; margin: 0; padding: 0; }
/* §6.3: a list of repeated items MAY reflow into multiple columns at a wider
   viewport -- the same composition, widened. §8.5's nine slots are the
   intended case. No region and no data column appears or disappears. */
.manager-card .slots {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}
/* §8.5: "one column on a phone, three on wider viewports", and §6.3 permits a
   repeated list reflowing -- that is the same composition widened, not a
   different one. Two columns is the intermediate step of the same reflow: at
   48rem three columns leaves ~7rem for a name, which truncates most of them.
   See T-11, which is exactly this question and is deliberately not solved here.
   Measured for the PR rather than redesigned. */
@media (min-width: 34rem) {
  .manager-card .slots { grid-template-columns: repeat(2, 1fr); column-gap: 1.25rem; }
}
@media (min-width: 52rem) {
  .manager-card .slots { grid-template-columns: repeat(3, 1fr); column-gap: 1rem; }
}

.slot-row { border-bottom: 1px solid var(--line); }
.manager-card .slot-row:last-child,
.manager-card .slot-row:nth-last-child(-n+1) { border-bottom: 0; }

.slot-main {
  display: grid;
  grid-template-columns: 2.25rem 1fr 2.75rem 0.75rem;
  align-items: center;
  gap: calc(var(--unit) * 2);
  width: 100%;
  min-height: 44px;
  padding: 0.5rem 0;
  background: none;
  border: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.own-lineup .slot-main { padding: 0.5rem 1rem; }
.slot-main[disabled] { cursor: default; }

.slot-name {
  font-size: var(--sm);
  font-weight: 700;
  color: var(--ink-muted);
}
.slot-main .player {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--sm);
}
.slot-main .team { color: var(--ink-muted); font-size: var(--label); }
.slot-main .pts { text-align: right; font-size: var(--sm); font-weight: 600; }
.has-score-week .slot-main .pts { color: var(--gold-ink); }

/* §8.3: the row shows that it expands -- a small ink-muted chevron at the
   row's trailing edge, pointing down when collapsed and up when open. There is
   no hover on a phone and §6.3 forbids motion, so the affordance has to be a
   mark on the row. */
.chevron {
  justify-self: end;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--ink-muted);
  border-bottom: 2px solid var(--ink-muted);
  transform: rotate(45deg);
  margin-bottom: 0.25rem;
}
.slot-main[aria-expanded="true"] .chevron {
  transform: rotate(-135deg);
  margin-bottom: -0.25rem;
}

.breakdown {
  list-style: none;
  margin: 0;
  padding: 0 0 0.625rem 2.75rem;
  font-size: var(--sm);
  color: var(--ink-muted);
}
.own-lineup .breakdown { padding-left: 3.75rem; padding-right: 1rem; }
.breakdown li { display: flex; justify-content: space-between; gap: 1rem; }

.owns { list-style: none; }
.owns li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.625rem 1rem;
  min-height: 44px;
  align-items: center;
  border-bottom: 1px solid var(--line);
  font-size: var(--sm);
}
.owns li:last-child { border-bottom: 0; }
.owns .player { font-weight: 600; }
.owns .owners { color: var(--ink-muted); text-align: right; }

/* ------------------------------------------------------------ §8.0 footer */

.footer-line {
  font-size: var(--sm);
  color: var(--ink-muted);
  margin: 1rem 0 0;
}
/* §8.0: on two consecutive failed refreshes the footer goes rust. The
   connection failure outranks a stale ingestion, because it is the one the
   manager can act on. */
.footer-line.failing { color: var(--rust); font-weight: 600; }

/* §6.1: no opacity on text -- dimming is applied to a row's surface, never to
   its content. rust at 45% over card is 2.09:1 and ink at 45% is 2.77:1. */

/* --------------------------------------------------- §8.7 standings */

.note-early { margin-top: calc(var(--unit) * 2); }

.pot-small { margin: calc(var(--unit) * 2) 0 0; }

/* §8.7: when a week has voided, the money it moved is stated, not implied. */
.pot-moved { margin: calc(var(--unit) * 2) 0 0; font-size: var(--sm); }
/* §6.1: rust carries "this will not happen", and §8.7 asks for the
   never-awarded line specifically in rust. */
.pot-never {
  margin: calc(var(--unit) * 2) 0 0;
  font-size: var(--sm);
  color: var(--rust);
  font-weight: 600;
}

.standings { list-style: none; margin: 1rem 0; }
/* §8.7: FOUR COLUMNS AT EVERY WIDTH. The five-column form was measured at
   360px and failed §14's own acceptance test, hiding 48px of Best behind a
   horizontal scroll; the permission to scroll horizontally was explicitly
   withdrawn, because a permitted scroll is how the checkbox gets marked green
   while failing. Best lives in the subline under the manager's name instead --
   the shape §8.6 already uses for `4 left`. Do not add a fifth column. */
.standings-row,
.standings-head {
  display: grid;
  grid-template-columns: 1.5rem 1fr 4.25rem 4rem;
  align-items: center;
  gap: calc(var(--unit) * 2);
  padding: 0.625rem 1rem;
  min-height: 44px;
  border-bottom: 1px solid var(--line);
}
.standings-row:last-child { border-bottom: 0; }
.standings-head {
  min-height: 0;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  background: var(--field);
  font-size: var(--label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  font-weight: 700;
}
.standings-head .third,
.standings-head .adjusted { text-align: right; }

.standings-row .rank { font-weight: 700; color: var(--ink-muted); }
.standings-row .who { min-width: 0; }
.standings-row .who .name {
  display: block;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.standings-row .subline {
  display: block;
  font-size: var(--sm);
  color: var(--ink-muted);
}
.standings-row .third {
  text-align: right;
  font-size: var(--sm);
  color: var(--ink-muted);
}
.standings-row .adjusted {
  text-align: right;
  font-size: var(--base);
  font-weight: 800;
  font-style: italic;
  font-stretch: 92%;
  letter-spacing: -0.02em;
}

/* §8.7's receipts: the week and the amount hold a fixed width on the first
   row and the names wrap beneath them, so a three-line two-name tie at 360px
   cannot push the amounts out of column. */
.receipts { list-style: none; }
.receipt {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 calc(var(--unit) * 2);
  padding: 0.625rem 1rem;
  border-bottom: 1px solid var(--line);
}
.receipt:last-child { border-bottom: 0; }
.rc-week { font-weight: 600; }
.rc-amount { text-align: right; font-weight: 600; }
.rc-who {
  grid-column: 1 / -1;
  font-size: var(--sm);
  color: var(--ink-muted);
}

/* ---------------------------------------------------- §8.8 playoffs */

.champion { text-align: left; }
.champion .stat-value { font-size: var(--xxl); }

.seeds { list-style: none; }
.seed-row {
  display: grid;
  grid-template-columns: 1.5rem 1fr auto 3.5rem;
  align-items: center;
  gap: calc(var(--unit) * 2);
  padding: 0.625rem 1rem;
  min-height: 44px;
  border-bottom: 1px solid var(--line);
}
.seed-row:last-child { border-bottom: 0; }
.seed-row .rank { font-weight: 700; color: var(--ink-muted); }
.seed-row .who { min-width: 0; }
.seed-row .name {
  display: block;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.seed-row .adjusted { text-align: right; font-size: var(--sm); }
.bye {
  font-size: var(--label);
  letter-spacing: 0.08em;
  color: var(--ink-muted);
  border: 1px solid var(--line);
  border-radius: 0.25rem;
  padding: 0.125rem 0.375rem;
}

.bracket-round { display: grid; gap: calc(var(--unit) * 2); }
.matchup { padding: 0.75rem 1rem; }
.side {
  display: grid;
  grid-template-columns: 1.5rem 1fr auto auto;
  align-items: center;
  gap: calc(var(--unit) * 2);
  padding: 0.25rem 0;
  color: var(--ink-muted);
}
.side .seed { font-weight: 700; }
.side .name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* §8.8: the winner's line is bold; the other is muted. */
.side.won { color: var(--ink); font-weight: 700; }
.side .mp { text-align: right; min-width: 3.25rem; }
/* §6.1: gold-ink is the gold-meaning text colour on a light surface. */
.advances {
  font-size: var(--label);
  letter-spacing: 0.08em;
  color: var(--gold-ink);
  font-weight: 700;
}
.mc-note {
  margin: calc(var(--unit) * 2) 0 0;
  font-size: var(--sm);
  color: var(--ink-muted);
}
/* §8.8: `TBD` means *not yet* and an em dash means *never*. They must not
   look the same -- using one for the other tells a manager to keep waiting
   for a matchup that will never exist. */
.side.tbd .name,
.side.never .name { color: var(--ink-muted); font-weight: 400; }
.side.tbd .name { letter-spacing: 0.06em; font-size: var(--sm); }

/* ------------------------------------------------------ §8.9 action log */

/* §8.9: "{Actor} {what they did} {(Week N)} {— detail}, with the timestamp
   right-aligned in small muted type". At 360px the timestamp carries a date, a
   time and a zone, so it wraps to its own line beneath the sentence rather than
   squeezing the sentence into a column two words wide. §6.3 permits a repeated
   list to reflow; it does not permit the row to stop being one row of meaning,
   so the stamp stays right-aligned at both widths. */
.log { list-style: none; }
.log-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.125rem calc(var(--unit) * 2);
  padding: 0.625rem 1rem;
  border-bottom: 1px solid var(--line);
}
.log-row:last-child { border-bottom: 0; }
.log-what { line-height: 1.45; }
.log-when {
  font-size: var(--sm);
  color: var(--ink-muted);
  text-align: right;
  white-space: nowrap;
}
@media (min-width: 34rem) {
  .log-row { grid-template-columns: 1fr auto; }
  .log-when { text-align: right; }
}

/* --------------------------------------- §8.11 salary upload / unmatched */

/* §6.4's button, primary variant: chrome fill, cream text. "Hover lightens the
   *fill*; text colour never changes on hover." */
.button.primary {
  background: var(--chrome);
  color: var(--cream);
  border: 0;
}
.button.primary:hover { background: var(--brand); }
.button.wide { width: 100%; justify-content: center; }
/* §8.11: when the list empties, [ Import the file again to open the week ] is
   "the largest thing on it". Size is how that is said. */
.button.huge { min-height: 60px; font-size: var(--lg); }

.upload-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: calc(var(--unit) * 2);
}
.upload-head h1 { margin-bottom: 0; }
/* §8.11: "the time to lock beside it [...] going rust inside one hour." */
.lock-readout { color: var(--ink-muted); font-size: var(--sm); margin: 0; }
.lock-urgent { color: var(--rust); font-weight: 600; }
.back { margin: 0.75rem 0 0; }

/* §8.11: "Result message in brand green on success, rust on failure." The rust
   half is §6.4's error notice, already defined; this is the green half. */
.notice-good {
  border-left: 3px solid var(--brand);
  color: var(--brand);
}
.fail-list { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.fail-list li {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  gap: calc(var(--unit) * 2);
  padding: 0.125rem 0;
  color: var(--ink);
}
.fail-line { color: var(--rust); font-weight: 600; }
.rust-ink { color: var(--rust); }

/* §8.11's running count sits above the list and is the thing that turns forty
   names from an hour into ten minutes. */
.running-count {
  font-weight: 600;
  margin: 0.5rem 0 0.75rem;
}

.unresolved-list { list-style: none; }
.unresolved {
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--line);
}
.unresolved:last-child { border-bottom: 0; }
.un-name { font-weight: 600; margin: 0; }
.unresolved .detail { margin: 0.125rem 0 0; }
.un-form { margin-top: 0.625rem; }
.un-form .label { display: block; margin-bottom: 0.25rem; }
.or-add { margin: 0.5rem 0 0.25rem; }
.un-new { display: flex; flex-wrap: wrap; gap: calc(var(--unit) * 2); }
.un-new input { flex: 1 1 6rem; min-width: 0; }

/* §6: form controls inherit the type scale rather than the browser's. 44px is
   §6.3's minimum target and is not negotiable on the one screen with a
   deadline. */
.un-form select, .un-form input, .import-form input {
  width: 100%;
  min-height: 44px;
  padding: 0 0.625rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: var(--sm);
}
.un-form button { margin-top: 0.625rem; }

/* §8.11: "a bulk action in its own tinted block". The page background is
   already `field`, so a bare field fill is not a block -- it is invisible. The
   1px inset line is §6.4's warning-notice treatment, reused rather than
   invented, which is what makes the tint read as a boundary. */
.add-all {
  background: var(--field);
  box-shadow: inset 0 0 0 1px var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.125rem;
  margin: 1rem 0;
}
.add-all .detail { margin: 0.625rem 0 0; }

.all-resolved { margin: 1rem 0; }
.all-resolved .stat-value { margin: 0 0 0.75rem; }

.import-form .label { display: block; margin: 0.75rem 0 0.25rem; }
.import-form .label:first-child { margin-top: 0; }
.import-form button { margin-top: 0.875rem; }
.import-form .stage { margin: 0.5rem 0 0; }
.replace-file summary {
  cursor: pointer;
  font-size: var(--sm);
  color: var(--ink-muted);
  min-height: 44px;
  display: flex;
  align-items: center;
}

/* ═══════════════════════════════════════════════════ §8.4 the lineup builder
 *
 * "The centrepiece screen. A manager makes nine consecutive priced decisions
 * under a cap, usually on a phone." 360px is §6.3's design width and every
 * measurement below was taken there.
 *
 * Two rules from §8.4 are enforced here and are easy to break by accident:
 *
 *   1. **The row's surface dims, never its content.** A whole-row fade to 45%
 *      puts the name at 2.77:1, the salary at 2.09:1 and the subline at 2.05:1
 *      against AA's 4.5:1, "and no opacity value dims perceptibly while still
 *      clearing the bar." So `.unaffordable` sets a `field` background and
 *      touches no colour inside the row. There is no `opacity` in this block
 *      and there must never be one.
 *   2. **§6.3 permits exactly one sticky element in the app and it is the cap
 *      bar.** It pins to the viewport, not beneath §7's header — the header is
 *      not sticky, cannot become sticky, and is not a fixed height (it wraps to
 *      two or three rows at 360px).
 */

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

/* --------------------------------------------------- block 1: cap readout */

.cap-readout {
  display: flex;
  gap: calc(var(--unit) * 2);
  margin: 1rem 0;
}
.cap-left {
  flex: 1 1 auto;
  background: var(--brand);
  color: var(--cream);
  border-radius: var(--radius);
  padding: 0.875rem 1rem;
}
/* "When the remaining cap goes negative, the whole left block inverts — a rust
   fill with cream text (5.60:1), label and value together." */
.cap-left.over { background: var(--rust); }
.cap-right {
  flex: 0 0 6rem;
  background: var(--chrome);
  color: var(--cream);
  border-radius: var(--radius);
  padding: 0.875rem 1rem;
}
.cap-label {
  margin: 0;
  font-size: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--cream-muted);
}
.cap-value { margin: calc(var(--unit) * 2) 0 0; color: var(--cream); }

/* ---------------------------------------------------- block 1a: the bar */

.sticky-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 20;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--unit) * 3);
  padding: 0 1rem;
  background: var(--chrome);
  color: var(--cream);
  font-size: var(--sm);
  font-weight: 600;
}
.sticky-bar[hidden] { display: none; }
.sticky-bar.over { background: var(--rust); }
.sticky-bar.urgent .sticky-lock { color: var(--gold); }
.sticky-lock { color: var(--cream-muted); font-weight: 500; }
.sticky-bar.over .sticky-lock { color: var(--cream); }

/* "Every focusable row reserves the sticky bar's height as scroll clearance, so
   a row scrolled into view by a focus change lands below the bar rather than
   underneath it. [...] It is invisible with a mouse, which means it is
   invisible in ordinary testing and only fails for the users these rules exist
   to serve." */
.roster-row, .pool-row { scroll-margin-top: 52px; }

/* ------------------------------------------------------ block 2: roster */

.roster-card, .pool-card { padding: 0; overflow: hidden; }
.roster, .pool { list-style: none; margin: 0; padding: 0; }

.roster-row {
  width: 100%;
  display: grid;
  grid-template-columns: 3.25rem 1fr 4.5rem;
  align-items: center;
  gap: calc(var(--unit) * 2);
  min-height: 48px;
  padding: 0.5rem 0.875rem;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid transparent;
  background: var(--card);
  color: var(--ink);
  text-align: left;
  font-size: var(--base);
  cursor: pointer;
}
.roster li:last-child .roster-row { border-bottom: 0; }
/* "The active row is tinted to the field colour and carries a 3px brand-green
   inset rail on its left edge." */
.roster-row.active {
  background: var(--field);
  border-left-color: var(--brand);
}
.rr-slot { font-weight: 700; font-size: var(--sm); color: var(--ink-muted); }
.roster-row.active .rr-slot { color: var(--ink); }
.rr-who { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.375rem; }
.rr-name { font-weight: 600; }
.rr-team { font-size: var(--sm); }
.rr-empty { color: var(--ink-muted); font-style: italic; }
.rr-salary {
  text-align: right;
  color: var(--gold-ink);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.badge-out {
  color: var(--rust);
  font-size: var(--label);
  font-weight: 800;
  letter-spacing: 0.08em;
}
.badge-q { color: var(--ink-muted); font-size: var(--label); font-weight: 700; }
/* "A row holding a ruled-out player says what to do about it." */
.rr-ruled-out {
  flex-basis: 100%;
  color: var(--rust);
  font-size: var(--sm);
  font-weight: 600;
}
.unsaved { margin: 0.5rem 0 0; }

/* --------------------------------------- blocks 3 and 4: header, toggles */

.pool-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--unit) * 2);
  margin: 1.5rem 0 0.5rem;
}
.pool-head h2 { margin: 0; }
#pool-search {
  flex: 1 1 8rem;
  min-height: 44px;
  padding: 0 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font-size: var(--base);
}

.toggles {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: calc(var(--unit) * 2);
  margin-bottom: 0.5rem;
}
/* "a two-button segmented control inside a bordered rounded container; the
   selected option is chrome-filled with cream text, the other is white with
   muted text. Each button carries its own pressed state for assistive
   technology." */
.segmented {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--card);
}
/* §8.4: "**Two toggles** on one line beneath — mode on the left, sort on the
   right." Measured at 360px: the four labels plus §6.3's 1.5rem gutters leave
   312px, so the horizontal padding is 0.625rem and not a rem — at 0.875rem the
   pair wraps to two rows and the sentence stops being true on the one width
   §6.3 designs for. The 44px target height is untouched. */
.segmented button {
  min-height: 44px;
  padding: 0 0.625rem;
  border: 0;
  background: var(--card);
  color: var(--ink-muted);
  font-size: var(--sm);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.segmented button[aria-pressed="true"] {
  background: var(--chrome);
  color: var(--cream);
}

/* -------------------------------------------------------- block 5: pool */

/* "a flush white card, scrollable, capped at ~24rem tall with contained
   overscroll." */
.pool-card { max-height: 24rem; overflow-y: auto; overscroll-behavior: contain; }
.pool-row {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 4.5rem;
  align-items: center;
  gap: calc(var(--unit) * 2);
  min-height: 48px;
  padding: 0.5rem 0.875rem;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  text-align: left;
  font-size: var(--base);
  cursor: pointer;
}
.pool li:last-child .pool-row { border-bottom: 0; }
/* THE DIM IS ON THE SURFACE. No opacity, no colour change inside the row —
   every foreground here stays at 6.38:1 or better against the field wash. */
.pool-row.unaffordable { background: var(--field); }
/* "A player already assigned to another slot renders at full strength on an
   undimmed surface, and the `In {SLOT}` label carries the information the dim
   used to." */
.pool-row.assigned { background: var(--card); }
/* "The row is tinted to the field colour when it holds the player currently in
   the active slot." */
.pool-row.in-active-slot { background: var(--field); }
.pr-main { display: flex; flex-wrap: wrap; align-items: baseline; }
.pr-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.pr-sub { flex-basis: 100%; }
.pr-in { color: var(--brand); font-weight: 600; }
.pr-salary {
  text-align: right;
  font-weight: 700;
  color: var(--gold-ink);
  font-variant-numeric: tabular-nums;
}
/* "gold-ink when affordable, rust when not" */
.pr-salary.over { color: var(--rust); }
.pool-empty { padding: 1rem 0.875rem; }

/* ------------------------------------- blocks 6, 7 and 8: say, note, save */

.live-region { margin: 0.5rem 0 0; min-height: 1.2em; color: var(--ink-muted); }
.live-region.problem { color: var(--rust); font-weight: 700; }
.hidden-note { margin: 0.375rem 0 0; }

.save {
  width: 100%;
  justify-content: center;
  min-height: 52px;
  margin-top: 1rem;
  border: 0;
  border-radius: var(--radius);
  background: var(--chrome);
  color: var(--cream);
  font-size: var(--lg);
  font-weight: 600;
  cursor: pointer;
}
.save:hover { background: var(--brand); }
.save[disabled] { cursor: progress; }
.save-status { margin: 0.5rem 0 0; }
.save-status.saved { color: var(--brand); font-weight: 600; }
/* A rejection and a dropped connection are both rust and must still be
   distinguishable: one is a heading with a list, the other is one line. */
.save-status.illegal, .save-status.transit { color: var(--rust); }
.problems { margin: 0.375rem 0 0 1.125rem; padding: 0; }

/* §8.4's lock refusal. Not a bullet among §9.2's problems — a different block
   entirely, which is the structural version of that rule. */
.notice-locked {
  border-left: 3px solid var(--rust);
  background: var(--card);
}
.locked-head { margin: 0; }
.notice-locked strong { color: var(--rust); }
.locked-lineup { padding: 0; overflow: hidden; }
.locked-row {
  display: grid;
  grid-template-columns: 3.25rem 1fr 4.5rem;
  align-items: center;
  gap: calc(var(--unit) * 2);
  min-height: 40px;
  padding: 0.25rem 0.875rem;
  border-bottom: 1px solid var(--line);
}
.locked-lineup li:last-child { border-bottom: 0; }
.locked-total { padding: 0.625rem 0.875rem; margin: 0; }
.quiet-link { font-size: var(--sm); }
.quiet-link a { color: var(--ink-muted); }

.page-title { margin: 1.25rem 0 0.25rem; }
.page-detail { margin: 0 0 0.5rem; }

/* ------------------------------------------ §8.1 and §8.12 — the way in */

/* §7: "Right (signed in only): the manager's display name in `cream-muted`,
   and beneath or beside it a `quiet` Sign out control." Beside it, because the
   header already wraps on a phone and a second stacked row costs the 56px
   chrome another 44. */
.who-out {
  display: flex;
  align-items: center;
  gap: calc(var(--unit) * 2);
}
.whoami { color: var(--cream-muted); }
.signout { margin: 0; }
/* The control sits on the green chrome, so §6.4's `quiet` treatment — a card
   fill with an ink border — would be a white block in the header. Same shape,
   chrome-side colours. */
.chrome .button.quiet {
  background: transparent;
  border-color: var(--chrome-line);
  color: var(--cream-muted);
  min-height: 36px;
  padding: 0 0.625rem;
}
.chrome .button.quiet:hover { background: var(--brand); color: var(--cream); }

/* §8.1 and §8.12: "Narrow, vertically centred, ~24rem wide." Centred against
   the viewport rather than against the content, so the card does not drift as
   a state replaces the form with a shorter card. */
.door {
  max-width: 24rem;
  margin: 0 auto;
  min-height: calc(100vh - 56px - 4rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.door h1 { margin: 0 0 0.25rem; }
.door .detail { margin: 0 0 1.25rem; }
.door .card { margin: 0; }
.door-form { margin: 0; }
.door-form label { display: block; margin-bottom: 0.25rem; }
.door-form input[type="email"] {
  width: 100%;
  min-height: 44px;
  padding: 0 0.625rem;
  margin-bottom: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: var(--sm);
}
.door-action { margin: 1rem 0 0; }
/* §8.1: the arrival notice sits **above** the heading — "someone arriving from
   a dead link needs to be told why before they are asked to do anything." */
.door .arrival { margin: 0 0 1.25rem; }
.door .send-failed { margin: 1rem 0 0; }
.door .sent + .door-form { margin-top: 1rem; }
.door .failed p { margin: 0; font-weight: 600; }

/* The demo's mailbox (server._demo_outbox) — declared demo furniture, not a
   product screen, so it borrows §6.4's card and button and invents nothing. */
.outbox, .outbox-letters { list-style: none; margin: 1rem 0; padding: 0; }
.outbox-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--line);
}
.outbox-row form { margin: 0; }
.outbox-row .detail { font-size: var(--sm); }
.outbox-letters li { margin-bottom: 0.75rem; }
.outbox-letters p { margin: 0 0 0.375rem; }

/* ─────────────────────────────────────── §8.10 the commissioner dashboard
   "Inherits the token layer and gets no bespoke layout work." Everything below
   is layout: the banners are §6.4's notice in its three weights, unchanged, and
   the forms borrow §8.11's field styling rather than growing their own.

   Built one-column first. §8.10's banner stack is the part most likely to be
   unusable at 360px — four notices with an action apiece — so the action sits
   on its own line under the text at phone width and moves inline at 34rem. */
.banner { display: grid; gap: calc(var(--unit) * 2); }
.banner-text { margin: 0; }
.banner-do { margin: 0; }

.admin-block { margin: 0 0 1.5rem; }
.admin-empty { margin: 1rem 1.125rem; color: var(--ink-muted); }
.admin-footer { margin: 1.5rem 0 0; }

.admin-weeks { list-style: none; margin: 0; padding: 0; }
.admin-week {
  display: grid;
  grid-template-columns: auto auto;
  align-items: baseline;
  gap: 0.125rem calc(var(--unit) * 2);
  padding: 0.75rem 1.125rem;
  border-bottom: 1px solid var(--line);
}
.admin-week:last-child { border-bottom: 0; }
.aw-name { font-weight: 600; }
.aw-status { text-transform: uppercase; font-size: var(--label); letter-spacing: 0.06em; }
.aw-lock, .aw-pool { grid-column: 1 / -1; }
.aw-do { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: calc(var(--unit) * 2); margin-top: calc(var(--unit) * 2); }
.aw-do form { margin: 0; }

/* D34's Reschedule and Delete. Full width in both layouts — it holds a date
   field, and §8.11's `.replace-file` already set the rule that a disclosure's
   summary is a 44px target in muted small type. Closed, it costs the row one
   line; open, it pushes nothing sideways at 360px. */
.aw-edit { grid-column: 1 / -1; margin-top: calc(var(--unit) * 2); }
.aw-edit summary {
  cursor: pointer;
  font-size: var(--sm);
  color: var(--ink-muted);
  min-height: 44px;
  display: flex;
  align-items: center;
}
.aw-edit .admin-form { margin-top: 0; }
.week-drop { margin: calc(var(--unit) * 3) 0 calc(var(--unit) * 2); }
.week-drop .detail { margin: 0 0 0.5rem; }

.admin-form { margin: 0.75rem 0 0; display: grid; gap: calc(var(--unit) * 1); }
.admin-form .label { margin-top: calc(var(--unit) * 2); }
.admin-form input {
  width: 100%;
  min-height: 44px;
  padding: 0 0.625rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: var(--sm);
}
.admin-form button { margin-top: 0.75rem; justify-self: start; }

.invite-list { list-style: none; margin: 1rem 0 0; padding: 0; }
.invite-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.125rem calc(var(--unit) * 2);
  padding: 0.625rem 0;
  border-bottom: 1px solid var(--line);
}
.invite-row:last-child { border-bottom: 0; }
/* An address is long and a phone is 360px: it wraps rather than widening the
   card, which is the same rule §14 states for a long manager name. */
.iv-who { font-weight: 600; overflow-wrap: anywhere; }
.iv-status { text-transform: uppercase; font-size: var(--label); letter-spacing: 0.06em; }
.iv-why { grid-column: 1 / -1; }
/* D38 put a second control on the row, so this is a flex row rather than a
   slot holding one form. On a phone the two sit side by side and wrap to a
   second line before either narrows; `Invite again` reads first because the
   stranded row is the one that has both, and re-inviting is what that banner
   sends the commissioner here to do. */
.iv-do {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(var(--unit) * 2);
}
.iv-do form { margin: calc(var(--unit) * 2) 0 0; }
.ceiling-list { margin: 0.5rem 0 0.5rem 1rem; font-size: var(--sm); }
.share-url { margin: 0.5rem 0 0; overflow-wrap: anywhere; }

@media (min-width: 34rem) {
  .banner {
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 1rem;
  }
  /* §8.10's row, as one row: `Week N`, its status, the lock and cap, the pool
     state, then the actions. Eighteen of these stack, so an action wrapping to
     a line of its own doubles the table's height for nothing. */
  .admin-week {
    grid-template-columns: 5rem 4.5rem 1fr auto auto;
    align-items: center;
  }
  .aw-lock, .aw-pool { grid-column: auto; }
  .aw-pool { text-align: right; }
  .aw-do { grid-column: auto; margin-top: 0; justify-content: flex-end; }
  /* Still full width: the row above is five columns of one line each, and a
     date field does not belong in any of them. */
  .aw-edit { margin-top: 0; }
  .aw-edit .admin-form { max-width: 22rem; }
  .invite-row { grid-template-columns: 1fr 6rem auto; align-items: center; }
  .iv-why { grid-column: 1 / -1; }
  .iv-do { grid-column: auto; }
  .iv-do form { margin: 0; }
}

/* ─────────────────────────────────────────────────────────── §8.2 home */

/* §8.2's call to action: "a gold fill with ink text (7.01:1). On hover the
   fill lightens toward cream and the text stays ink." §6.1 adds the failure
   this is written against: "cream text on gold is 1.98:1 and must never ship",
   and §14 says the failures all lived in *derived* colours rather than in the
   token table.

   Two things make that structural rather than careful. `color` is set once and
   the hover rule does not restate it, so there is no second place for the text
   colour to drift to. And `--gold-lift` is a declared token rather than an
   inline lighten — §6.1's rule is that no screen hard-codes a raw colour, and a
   hover computed in the ruleset is a raw colour nobody audits. Both pairings
   are computed from these two lines by `app/tests/test_home.py`:
   ink on gold 7.01:1, ink on the lift 8.08:1. */
.button.gold {
  background: var(--gold);
  color: var(--ink);
  border: 0;
}
.button.gold:hover { background: var(--gold-lift); }

/* §6.1: the focus card is a **feature block**, so it is brand green, and text
   on brand green is cream or cream-muted and nothing else. §8.2 gives gold one
   meaning domain on this screen — "the one action the page wants" — which is
   the button and nothing else here. */
.focus {
  background: var(--brand);
  border-radius: var(--radius);
  padding: 1.25rem;
  margin: 1rem 0 1.5rem;
}
.focus-head {
  margin: 0;
  color: var(--cream);
  font-size: var(--xxl);
}
.focus-body {
  margin: 0.5rem 0 0;
  color: var(--cream);
  max-width: 34rem;
}
.focus-line {
  margin: 0.375rem 0 0;
  color: var(--cream-muted);
  font-size: var(--sm);
}
.focus-do { margin: 1rem 0 0; }
.focus-empty { margin: 1rem 0 1.5rem; }
.focus-empty p { margin: 0 0 0.5rem; }
.focus-empty p:last-child { margin-bottom: 0; }

/* §8.2's All weeks card. Three columns at every width — at 360px the status
   word is uppercase `--label` type and the third column is `nowrap`, so the
   week number never wraps under it (T-11's failure one screen over).

   **The status rides with the week number, and the third column takes the
   `1fr`.** §8.2 asks for the third column right-aligned and says nothing about
   the second, and the obvious `1fr auto auto` is wrong once T-12 lands: the two
   `auto` columns pack right, so the status word's position is decided by how
   wide that row's *score* is and the `FINAL`s wobble down the card. Measured in
   a browser at 1280px before it was noticed. */
.home-weeks { list-style: none; margin: 0; padding: 0; }
.home-week { border-bottom: 1px solid var(--line); }
.home-week:last-child { border-bottom: 0; }
.hw-link {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: baseline;
  gap: calc(var(--unit) * 3);
  padding: 0.75rem 1.125rem;
  min-height: 44px;
  text-decoration: none;
  color: var(--ink);
}
/* §8.2: "Rows highlight to the field colour on hover." */
.hw-link:hover { background: var(--field); }
/* `Week 18` and its status never wrap; the note in the `1fr` does, if it must.
   At 360px the one row with a long note is the `pending` week, whose lock time
   is a full Eastern stamp with its zone (§7) — and `Week` over `18` is a worse
   row than a stamp on two lines. */
.hw-name { font-weight: 600; white-space: nowrap; }
.hw-status {
  text-transform: uppercase;
  font-size: var(--label);
  letter-spacing: 0.06em;
  color: var(--ink-muted);
  white-space: nowrap;
}
.hw-note { text-align: right; }

/* ────────────────────────────────────────── §8.13 how scoring works */

.sc-half { margin: 1rem 0 1.5rem; font-weight: 600; }
.sc-card { list-style: none; margin: 0 0 1.25rem; padding: 0; }
.sc-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: calc(var(--unit) * 3);
  padding: 0.5rem 1.125rem;
  border-bottom: 1px solid var(--line);
}
.sc-row:last-child { border-bottom: 0; }
.sc-value { text-align: right; white-space: nowrap; font-weight: 600; }
.sc-unit { font-weight: 400; }
/* §8.13: an unlabelled constant "renders loudly, never vanishes". Loud is the
   monospaced stat id — it reads as a defect report on the page, which is what
   it is, rather than as a rule somebody named badly. */
.sc-raw .sc-what { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.sc-note { margin: 0 0 0.75rem; max-width: 40rem; }
