/*
 * The phone board — the rules that spend the tokens. **Every constant lives in `tokens.css`**, which
 * `index.html` links first; this file holds no bare number that a token could name.
 *
 * ⚠️ **Themeable via CSS variables and carrying no branding** — seam C5. The engine has no chrome, and
 * a colour named after the product here would be the first piece of chrome in it.
 *
 * The arithmetic, so a future change can be checked rather than guessed:
 *   375 viewport − 2×8 margin = 359 available
 *   359 − 24 slot − 66 label  = 269 for six data columns = 44.8px each
 * That is the 44px touch target with 0.8px to spare. **A seventh column does not fit** and cannot be
 * added; the label column is what would have to give, and it is already at seven characters.
 */

/* ⚠️ **Self-hosted, never linked.** §23 requires the day's puzzle to be playable with the backend
   down, and a webfont fetched from a third party is a dependency in the play path. Worse than a missing
   glyph: the board would lay out in a fallback with different metrics and **the columns would stop
   lining up**, which is the one failure a grid of numbers cannot absorb. `tools/fetch_fonts.py`
   downloads and subsets; `fonts.css` carries the faces.

   ⚠️ **IBM Plex Sans Condensed, and it took two passes to get here.** Both candidates carry true
   tabular figures — measured, ten `1`s and ten `8`s to the same pixel — so the non-negotiable property
   was a tie and the choice fell to the `1`.

   Roboto Condensed shipped first, on preference and on an argument of mine that was wrong in one
   direction and right in the other. Wrong: I first recommended Plex partly because Roboto draws `I` and
   `l` as one vertical stroke — but this board never renders either character, so that comparison was
   decided on a specimen the board does not contain. Right: Roboto also draws `1` as a bare stem with an
   angled flag and **no foot**, and that character the board renders constantly. On screen it reads as a
   cursor. Checked before switching, rather than assumed: all three weights had loaded and the digit's
   advance differed from the fallback's, so the bare `1` was the design and not a webfont that failed to
   arrive.

   Plex gives the `1` a foot serif. Its `⅓` and `⅔` are tighter than Roboto's, which costs four pixels
   of type size on two keypad keys — a cheap price for the digit that appears in every column.

   One family at three weights, not two families. The clubs use a display face beside a body face
   because they have full pages; this has 375 pixels, and two families on that width compete. The
   difference comes from weight and colour. */




* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--paper);
  color: var(--ink);
  font: 15px/1.2 var(--face), system-ui, -apple-system, sans-serif;
  /* ⚠️ **Tabular figures, everywhere, non-negotiable.** Proportional digits make a `1` narrower than an
     `8`, so a column's width depends on the values in it and the grid shifts as the player fills it —
     the most visible possible defect on a board that is ninety per cent numbers. Set on `body` rather
     than on the tables, because the scoreboard, the footnote line and the keypad are numbers too. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  /* The board must not rubber-band as a page; only the scroller inside it scrolls. */
  overscroll-behavior: none;
}

/* ⚠️ **The board never gets wider than the phone it was measured for.**
   Every number in this file comes from 375px: six data columns at 44.8px, a 66px label, a 24px slot.
   With `width: 100%` and no cap, a wider window stretched those columns to whatever it liked and the
   board stopped being the thing that was measured — §6 says the board is identical on all screens and
   that desktop gets more whitespace, not more content.

   This is the phone layer. Tablet and desktop are different layers with their own layouts; until they
   exist, a wide window shows the phone board centred, which is honest about what is built. */
#app {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  max-width: 375px;
  margin: 0 auto;
}

/* ---- line score: the anchor, read-only, never erased ------------------------------------------ */

/* ⚠️ **A scoreboard, not the first rows of a spreadsheet.** It is the one part of the board that is
   given, read-only and never erased — §6 calls it the puzzle's anchor — and reading as a physical
   object is what says so before any explanation does. */
.linescore {
  flex: none;
  padding: 8px var(--board-margin) 10px;
  /* Centred rather than left-hung when it cannot fill the width at a legible size: a scoreboard
     floating against the left margin reads as a fragment of something wider. */
  display: flex;
  justify-content: center;
}
.linescore table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  background: var(--fenway);
  color: var(--scoreboard-ink);
  border-radius: 6px;
  overflow: hidden;
  table-layout: fixed;
}
/* Centred on both axes. `vertical-align` matters here because the row heights are set by the tallest
   cell and a number sitting on the baseline of a coloured panel reads as printed on it, not in it. */
.linescore th,
.linescore td {
  padding: 5px 1px;
  text-align: center;
  vertical-align: middle;
  color: var(--scoreboard-dim);
  font-weight: 500;
}
.linescore th:first-child, .linescore td.side { width: 52px; }
.linescore td.side {
  text-align: left;
  color: var(--scoreboard-ink);
  font-weight: 700;
  letter-spacing: .04em;
  padding-left: 8px;
}
.linescore td.value { color: var(--scoreboard-ink); }
.linescore .rhe { font-weight: 700; color: var(--scoreboard-ink); }
/* The separator between the last inning and R, which is where a printed line score puts one. */
.linescore .rhe-rule { border-left: 1px solid var(--scoreboard-rule); }
/* ⚠️ A half-inning that was never played. Not a zero — the home side leading after the top of the
   ninth does not bat, and printing 0 there states a fact that did not happen.

   ⚠⚠ **This is a given, and it was below the legibility floor.** The `x` makes the home side's outs 24
   rather than 27, which feeds the global identity directly — a marker the player cannot read is a lost
   given, not a faint decoration. It rendered at **2.21:1** against the scoreboard ground, where a
   played digit gets 4.51:1 and WCAG's floor for any content is 3.0. The dim colour was half of it and
   `opacity: .7` was the other half.

   ⚠⚠ **A dimmed-but-accessible variant does not exist on this ground, and the arithmetic says so.**
   Against `--fenway` (luminance 0.164), **pure black reaches only 4.27:1** — so nothing darker than the
   panel can pass 4.5 at all. Going lighter, a neutral grey must be `#f5f5f5` or brighter, which is the
   digit's own brightness. `--scoreboard-dim` at full opacity still only reaches 2.95:1. **The only
   compliant marker is one as bright as a digit**, so it is distinguished by weight instead of by
   dimming — and by being a letter among numerals, which no amount of contrast confuses.

   Full contrast costs nothing in meaning here: the dimming was saying "this is not a value", and the
   glyph already says that unambiguously.

   ⚠⚠ **The same defect is still on the inning header numbers, and it is not fixed here.** They carry
   `--scoreboard-dim` too and render at **2.95:1** — under both floors, on twelve-odd cells rather than
   one, and they are what tells the player which inning a column is. It was left alone because the fix
   is a decision about the panel's colour rather than about this rule.

   **There is a clean one:** darken `--fenway` to 75% of itself, `rgb(62,90,81)`. Today's dim colour
   then reaches **4.52:1** and the values reach **6.91:1**, so the whole hierarchy survives — dim
   headers, bright values — with everything above the floor. If that is taken, this rule can go back to
   `--scoreboard-dim` and recover the marker's original visual intent, because at that ground the dim
   colour is compliant. Recorded so the option is not lost with the reasoning that found it. */
/* No rule here on purpose: the marker is drawn as a value, in `board.js`. The lighter weight that
   briefly replaced the dimming had the same answer against it -- the `x` does not need to be lighter
   to read as not-a-value, it reads that way because it is an `x`. Deleting the rule outright is what
   stops the two from drifting; a rule that restates the default is a second definition waiting to
   disagree. */
/* ⚠⚠ **The smallest device in use cannot hold a twelve-inning game at 13px.** Measured with the real
   renderer and the real typeface: at 320px a twelve-inning line score at 13px leaves 2.7px between a
   two-digit R and a two-digit H, which is where `11 16 2` starts reading as `1116 2`. At 11px the gap
   is 4.9px and they separate comfortably.

   ⚠️ **This is the type-size exception to "the board is identical on all screens", and capping at
   twelve does not avoid it.** Eleven innings would: at 13px on 320px eleven leaves 4.0px. Twelve
   cannot be drawn the same at both widths, so the cap and this rule are one decision. It is type size
   and not content — every cell, every clue and every given is the same board. Narrowing R/H/E does not
   rescue it: at 13px `R` and `H` already need essentially a full column, so there is nothing to move.

   The line score is read-only — no 44px touch target applies — so it is the one element that may go
   below the grid's size. It may not go further: 10px reads as diminished rather than small beside the
   same board at 11px, and §6 makes this the anchor the player returns to constantly.

   ⚠⚠ **This rule was silently dead twice while these notes were being written.** First an edit closed
   the comment block early, so the remaining prose parsed as CSS and the parser discarded everything up
   to the next recoverable token — including this media query. Then the note explaining that closed it
   again, because the note quoted the very character pair that ends a CSS comment. **Prose about CSS
   syntax, inside CSS, is executable.** Neither failure raised anything: the page simply drew a
   twelve-inning board at 13px on a 320px screen, which is the case this rule exists for.

   Found by a measurement page reporting `13px` where it had reported `11px` an hour earlier, and then
   by reading `document.styleSheets` — the parsed rules, not the file. The file contained the rule both
   times. **A stylesheet fails by doing nothing**, so the check is to ask the parser what survived
   rather than to read the source and believe it.

   ⚠️ The breakpoint is documented in `tokens.css` beside the other presentation constants. It is a
   literal here because a media query condition is evaluated before the cascade and cannot read a
   custom property. §33 keeps it out of `forge/`: the inning cap it supports is a domain fact and lives
   in `boxscore_domain`, but a viewport width is presentation and the forge must not know it. */
@media (max-width: 359px) {
  .linescore table { font-size: 11px; }
}

/* ---- the board ------------------------------------------------------------------------------- */

.scroller {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 0 var(--board-margin);
  -webkit-overflow-scrolling: touch;
}

table.grid {
  border-collapse: separate;
  border-spacing: 0;
  width: 100%;
  table-layout: fixed;
  font-variant-numeric: tabular-nums;
}

.grid col.slot { width: var(--slot-column); }
.grid col.label { width: var(--label-column); }

/* ⚠️⚠️ **Every cell, given and editable alike — the border used to mean *editable* and that broke the
   grid.** Only blank cells carried a box, so the table read as though pieces of it were missing, and in
   the dark palette it was worse: the line is the only thing separating a cell from the ground.

   **A grid is a grid.** Saying what a cell *is* was already fully done by colour — blue is the
   player's, black is given, the pale ground marks an empty editable cell — so the border was carrying a
   meaning it did not need to carry and paying for it in legibility.

   ⚠️ **`border-right` on every cell rather than a box on each.** The table is `border-collapse:
   separate`, because `collapse` drops borders on `position: sticky` elements in Chrome and Safari and
   the header and totals row are pinned (§6). With `separate`, a full box on each cell draws two lines
   on every shared edge. One bottom and one right per cell gives a single-line grid; the outer edges
   come from the section's own frame. */
.grid th, .grid td {
  height: var(--cell);
  padding: 0;
  text-align: center;
  border-bottom: 1px solid var(--grid-line);
  border-right: 1px solid var(--grid-line);
  font-weight: 400;
}
.grid th:last-child, .grid td:last-child { border-right: 0; }

/* ⚠️ Pinned, because in a deduction puzzle the totals are consulted constantly (§6) — and, as the
   real packages showed, **the totals row holds blanks the player must fill**, so it is an input
   surface that happens to be pinned rather than a read-only anchor. */
/* ⚠️ **The section label and its column row are one header block**, on one ground, so the board reads
   as one object rather than as a caption stuck above a table. Both rows are sticky at the same edge and
   share a background, which is also what stopped the label covering the column names. */
.grid thead th {
  position: sticky;
  z-index: 3;
  background: var(--header-ground);
  /* ⚠️ **Both rows of the block are white.** The column row was on `--header-dim`, on the reasoning
     that a heading is chrome around the data — but `PA AB R H BB K` is not chrome. It is the only
     thing that says what a column holds, and on a board where every number is part of an equation a
     player reads back up to it constantly. Dimming it made the second-most consulted text on the
     screen the faintest, and `--header-dim` is gone with it — a token nobody reads is a mechanism
     kept past its problem. */
  color: var(--header-ink);
  font-size: 11px;
  height: 26px;
  font-weight: 600;
}
.grid thead th.section-title {
  top: 0;
  height: 28px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-align: left;
  padding-left: 8px;
}
.grid thead tr:last-child th { top: 28px; }
.grid tfoot td {
  position: sticky;
  bottom: 0;
  z-index: 3;
  background: var(--paper);
  border-top: 1px solid var(--rule-strong);
  font-weight: 700;
}

/* ⚠️ **The section title was a sibling of the table and sticky at `top: 0`**, so it competed with the
   column headings for the same edge and covered them the moment you scrolled. It is now a row inside
   the head — see `.grid thead` above — which fixes the collision by making the question impossible
   rather than by tuning two offsets against each other. */
.grid + .grid, .scroller > div + div { margin-top: 14px; }

td.slot { color: var(--muted); font-size: 11px; border-right: 1px solid var(--rule); }
td.label { text-align: left; padding-left: 6px; font-size: 13px; white-space: nowrap; overflow: hidden; }
/* A row that did not start: indented, so the lineup slot reads as a group at a glance. */
td.label.substitute { padding-left: 14px; }

.cell { cursor: pointer; }
.cell.given { color: var(--given); }
/* ⚠️⚠️ **The 1.5px outline that used to box an editable cell is gone, and the grid line replaced it.**
   It was the thing that made the border mean *editable*, and with every cell bordered it would be a
   second, heavier line saying what the pale ground already says.

   What remains is what actually distinguishes the three kinds of cell, and none of it is a border:
   the **pale ground** is empty-and-editable, the **blue bold digit** is the player's, plain black on
   paper is given. */
/* ⚠️ **A hinted cell keeps the blank's ground and takes its own ink.** It *was* an editable cell and
   still is — the player can clear it and enter something else — so the ground has not changed. What
   changed is who put the number there. */
.cell.blank.hinted { color: var(--hinted); }

.cell.blank {
  background: var(--blank);
  color: var(--entered);
  /* The player's own writing has to be findable at a glance among nine rows of given numbers: a cell
     they filled and a cell they were given are different kinds of fact. */
  font-weight: 700;
}
/* ⚠️ **The pale ground means one thing: empty and editable.** A filled cell kept it, so a blue `5` on
   pale blue and an empty pale-blue cell read alike at a glance — the ground was saying *editable* and
   being heard as *empty*. Filled goes white and the digit alone carries the colour, which also keeps
   the three empty states — plain, cream on the active row or column, amber on the active cell — as far
   apart as they were. They were already close to the limit of what separates at a glance. */
.cell.blank:where(.filled) { background: var(--paper); }

.cell.blank:empty::after { content: ""; }
/* ⚠️ **Ground first, then state, and the two must not compete on specificity.**
   `.cell.blank.filled` is three classes and these are two, so **the ground outranked the state
   whatever the source order** — a filled cell kept its white and lost both the cream of the active row
   or column and the amber of the selection. The player saw the highlight on every given cell and on
   every empty one, and nothing on the cells they had actually written in, which is where the eye was.
   `:where()` on `.filled` contributes no specificity, so the ground rules sit at two classes like
   these and **the later rule wins, which is what the cascade is for.**
   This is not a CSS detail: ground says *what kind of cell this is*, state says *where you are now*,
   and state has to be able to paint over ground or it is not a state. */
.cell.peer { background: var(--peer); }
/* ⚠️⚠️ **Colour at the grid's own width, not a thicker line.** This was `inset 0 0 0 2px`, and a wider
   edge either shifts the digit or shrinks the cell — with a 44px touch target measured to the pixel
   (§6) there is nothing to spend. The amber is already on the board as the selected ground, so the
   edge adds no fifth colour to a palette that carries four with meaning.

   ⚠️ `outline` rather than a border, because the selection moves cell to cell on every tap: an outline
   takes part in no layout at all, so nothing reflows as it moves. `-1px` keeps it inside the box so it
   cannot overlap the neighbour's grid line. */
.cell.selected {
  background: var(--selected);
  outline: 1px solid var(--selected-rule);
  outline-offset: -1px;
}

/* ---- the footnote line ----------------------------------------------------------------------- */

/* ⚠️ **Live footnotes must be visually prominent** (§6). Nine rows in ten have every term at zero, so
   players learn to stop looking — and the row where one appears is exactly the row whose arithmetic
   fails if they miss it. A zero term is muted; a live one is not. */
/* ⚠️ **Live footnotes must be visually prominent** (§6). Nine boards in ten have every term at zero, so
   players learn to stop looking — and the board where one appears is exactly the one whose arithmetic
   fails if they miss it. A term whose total is zero is not printed at all.

   This block held cells for a week. They are gone with the decision that opened them: the terms are
   given, so the line is text and one row of it. */
/* ⚠️ **15px, up from 13.** §6 requires live footnotes to be visually prominent, and the reason is the
   distribution rather than taste: nine rows in ten have every term at zero, so a player learns to stop
   looking — and the row where one finally appears is exactly the row whose arithmetic fails without it.
   The line is the smallest type on the board and it carries the terms that break a row identity.
   It wraps rather than clips, so a longer line costs height and never a hidden term; measured at 375
   and at 320 with the widest footnote in the reference set. */
.footnotes { padding: 8px 0 14px; font-size: 15px; line-height: 1.6; }
.footnotes .term { margin-right: 14px; white-space: nowrap; }
.footnotes .term.lob { font-weight: 700; }
.footnotes .term.live { color: var(--ink); font-weight: 700; }
/* The reference is given, so it is the same black as the rest of the line. */
.footnotes .who { color: var(--ink); font-weight: 700; }

/* ---- keypad ---------------------------------------------------------------------------------- */

.keypad {
  flex: none;
  background: var(--keypad);
  padding: 6px;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 6px;
}
.keypad button {
  height: var(--cell);
  border: 0;
  border-radius: 8px;
  background: var(--key);
  color: var(--key-ink);
  font: inherit;
  font-size: 18px;
  /* The digits and the two fractions. `.word` below sets its own weight through the `font` shorthand,
     so `Clear`, `Hint` and `Check` are unaffected by this. */
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.keypad button:active { background: var(--selected); }
.keypad button.wide { grid-column: span 2; }
/* ⚠️ **The three word keys are one rule.** `Clear`, `Hint` and `Check` had three different treatments
   — 18px regular, 14px regular and 14px semibold — sitting side by side in one row, which reads as
   three kinds of control rather than as three keys. They are the same kind of thing: a word that does
   something to the board. One declaration, so they cannot drift apart again. */
.keypad button.word {
  font: 600 14px/1 var(--face), system-ui, sans-serif;
  letter-spacing: 0.02em;
}
/* ⚠️ **The fraction is built from Plex's own digits, not from a `⅓` glyph.**
   Plex draws `U+2153` with the numerator crushed onto the slash — unreadable at key size. Borrowing the
   two glyphs from Roboto by `unicode-range` was tried and removed: the subset Google returns for a
   three-character request **rendered nothing**, and the clean fractions in that comparison turned out to
   be the operating system's fallback, not Roboto. Measured, after the widths had already fooled me once.

   Composing needs no glyph, so it cannot fall back, and it is identical on every device — which is the
   whole reason the faces are self-hosted (§23). */
.keypad button.thirds { font-size: 15px; }
.keypad .frac { display: inline-flex; align-items: center; font-variant-numeric: tabular-nums; }
.keypad .frac .num { font-size: 15px; align-self: flex-start; }
.keypad .frac .bar { font-size: 20px; margin: 0 1px; }
.keypad .frac .den { font-size: 15px; align-self: flex-end; }
/* ⚠️⚠️ **A disabled key keeps its own ground and dims only its ink.** This was `opacity: .35` on the
   whole button, which fades the *background* too — and the key ground and the pad ground are four
   values apart in the dark palette, so a disabled key sank into the pad and read as a hole where a key
   should be rather than as a key that is not available. Reported as "the background looks different",
   which is exactly what it was.

   Dimmer than a live key on purpose and by a wide margin: a disabled control that reads as available
   is worse than one that reads as missing. WCAG exempts disabled controls from the contrast minimum
   for this reason, so `--muted` is the right token and the low ratio is the message. */
/* ⚠️⚠️ **A line through the key, because dimmer ink was not enough.** Muted text on a key that keeps
   its own ground reads as *quiet*, not as *unavailable* — and the two states it has to separate are a
   live `Check` and a dead one, at the exact moment the player is looking for it.

   ⚠️ **Drawn as an overlay, not as `text-decoration`.** The thirds keys are a composed fraction —
   three spans — and striking that through cuts the numerator from the bar. An overlay works the same
   for a word and for a glyph, and it takes part in no layout, so nothing moves as a key changes state.

   Corner to corner would look like a rendering fault on a key this wide; the line is inset so it reads
   as a mark on the key rather than as the key being crossed out of existence. */
.keypad button:disabled {
  color: var(--muted);
  position: relative;
}
.keypad button:disabled .frac { color: var(--muted); }
.keypad button:disabled::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 8px;
  /* ⚠️⚠️ **Hatched, chosen from six treatments rendered side by side in both schemes.** The picture is
     what decided it, and it decided differently in each: *outlined* was cleanest on paper and nearly
     invisible on the dark ground, *no ground* vanished into the pad — the hole this project already
     rejected once today — and a single diagonal collided with the composed fraction on the thirds keys
     and read as scribble. **Hatching is the only one that reads in both**, which is the whole test,
     because the board that gets played is the dark one. */
  background: repeating-linear-gradient(
    -45deg,
    transparent 0 5px,
    color-mix(in srgb, var(--muted) 38%, transparent) 5px 7px
  );
}

/* ---- the ending: check, and what the player gets for being right ------------------------------ */

/* ⚠️⚠️ **Check moved into the keypad's last slot, and this comment used to argue against that.** It
   said a button ending the game must not sit under the thumb. What answers it is the gate rather than
   the position: the key is disabled for the whole time the player is filling cells, so the accident
   window is the instant after the last digit — and §27 has no fail state, so an unwanted press on a
   complete board does what a deliberate one would have done a second later.

   The strip below the pad stays, carrying only the verdict. */
.footer {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px var(--board-margin) calc(8px + env(safe-area-inset-bottom));
  background: var(--keypad);
  border-top: 1px solid var(--rule);
}

/* ⚠️⚠️ **This element may never name a cell or a count.** It says the board does not balance, which is
   a fact about the whole. Marking which cells are wrong makes probing beat deducing, and counting them
   is a binary search over the board — both rejected in `serve-flow.md`. The single sentence is the
   design, not a placeholder for a better message. */
.verdict {
  flex: 1;
  margin: 0;
  font-size: 13px;
  line-height: 1.3;
  color: var(--ink);
  min-height: 1.3em;
}
.verdict[data-state="wrong"] {
  /* `flex: none` so the badge is the width of its sentence. The empty strip keeps its height from
     `min-height` above, so the board does not jump when the message appears or goes. */
  flex: none;
  align-self: center;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--wrong-ground);
  color: var(--wrong-ink);
  font-weight: 600;
}

/* The hint key carries no count: it goes dead at zero, which is the same answer the thirds keys give
   for *not applicable here*. Its type comes from `.word` with `Clear` and `Check`. */

/* ⚠️ The reference key, in the chrome rather than in the pad. Quiet: it is always there and it is not
   what the player came for, so it reads as a label until it is wanted. */
.reference-key {
  flex: none;
  /* ⚠️⚠️ **44px, like everything else that takes a tap.** (v36-P2-14) This was 12px of line-height
     plus 6px of padding and a border — about 26px — on a product whose §6 floor is 44px and which
     rebuilt an entire footnote line because its terms measured 29. Quiet is a matter of weight and
     colour; it is not a reason to be hard to hit, and this is the only route to the relations on a
     finished board, where there is no keypad. The pill keeps its size: the padding grows the box, the
     text does not. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font: 600 12px/1 var(--face), system-ui, sans-serif;
}

/* The sheet itself: one relation per line, the name quiet and the formula not. */
.reference-list { margin: 0; font-size: 13px; line-height: 1.45; }
.reference-list dt { color: var(--muted); font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; }
.reference-list dd {
  margin: 2px 0 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ⚠️ The Check key. It takes the pad's ground when disabled and the header's green when live, so the
   change from *not yet* to *ready* is visible without reading it — the one moment the board changes
   what it offers. `:disabled { opacity: .35 }` above applies to it like every other key. */
.keypad button.check {
  background: var(--header-ground);
  color: var(--header-ink);
}
.keypad button.check:disabled {
  /* Back to an ordinary key: the ground its neighbours have and the ink they get when disabled. The
     green is what says *ready*, so it has to be absent while the board is not.

     ⚠️ **`color` is repeated here and it is not redundant.** `.keypad button.check` and
     `.keypad button:disabled` have the same specificity, and the class rule is written later, so it
     won — a disabled Check key kept `--header-ink` and rendered in full white beside two muted
     fraction keys, reading as available on an empty board. Found by looking at it. */
  background: var(--key);
  color: var(--muted);
}

/* ⚠️ **The way back to the clipping, where the keypad was.** A finished board takes no entries, so
   the pad is gone rather than disabled — a row of dead keys over a third of the screen at the moment
   the player is looking at their own finished work. Full width because it is the only thing offered.

   ⚠️ **And the entered values stay blue underneath.** They are the record of what the player deduced,
   and looking at that is what going back to the board is for. Turning them black on completion would
   make the board stop being theirs at the exact moment it became worth keeping. No rule is needed for
   that: `paint()` derives `filled` from the game, which does not forget who filled a cell. */
.again {
  flex: none;
  margin: 0;
  /* ⚠️⚠️ **The bottom inset moved here with the strip that used to carry it.** `.footer` padded itself
     by `env(safe-area-inset-bottom)` and stood between this button and the edge of the phone; with the
     strip gone on a finished board, this is the last element on the screen and the home indicator sits
     over it. The operator's own handset showed the label clipped **before** the strip was removed —
     taking it away without moving the inset would have made the thing being fixed worse. */
  padding: 0 18px env(safe-area-inset-bottom);
  min-height: calc(var(--cell) + env(safe-area-inset-bottom));
  border: 0;
  border-top: 1px solid var(--rule);
  background: var(--header-ground);
  color: var(--header-ink);
  font: 600 15px/1 var(--face), system-ui, sans-serif;
  letter-spacing: 0.02em;
}

/* A finished board is read: the cells are not controls, so nothing about them offers to be pressed. */
.app-finished td.cell { cursor: default; }

/* ---- the reveal: a printed boxscore, not a results card -------------------------------------- */

/* The board is still there, dimmed, and the clipping arrives over it. §7 draws the real boxscore
   complete at the end, so it is a panel over the game rather than a new page. */
.reveal {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  animation: reveal-fade 220ms ease-out;
}
@keyframes reveal-fade {
  from {
    opacity: 0;
  }
}

.reveal-sheet {
  width: 100%;
  max-width: 375px;
  max-height: 94dvh;
  overflow-y: auto;
  background: var(--paper);
  border-radius: 14px 14px 0 0;
  padding: 14px var(--board-margin) calc(14px + env(safe-area-inset-bottom));
  animation: reveal-rise 260ms cubic-bezier(0.2, 0.8, 0.3, 1);
}
@keyframes reveal-rise {
  from {
    transform: translateY(18px);
  }
}

/* `Solved` on the left, the result boxed on the right. */
.reveal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 14px;
}
.reveal-title {
  margin: 0;
  font-size: 20px;
  letter-spacing: 0.02em;
}

/* ⚠️ The box is what groups them. Two label/value rows loose on the sheet read as two facts about
   nothing in particular; inside a frame they read as one result with two parts, which is exactly what
   gets shared. */
.reveal-result {
  flex: none;
  min-width: 150px;
  padding: 8px 12px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--blank);
}

/* ⚠️ The clock, seen for the first time. Invisible during play (§26), so this is the first and only
   place a number of seconds appears — which is why it is the largest thing on the panel. */
/* ⚠️⚠️ **One rule for both rows of the result, because they are two halves of one thing.** The time
   was dim grey and the hint count was ink, which read as a headline with a footnote attached — and the
   two are the shared result: a time without its hint count is a time presented as if it were clean.
   That is the whole argument for allowing hints at all.

   ⚠️ They had drifted apart before this: the hint row was 13px of dim grey under a 30px clock, and a
   player who spent two hints could not see that he had. Declaring them together is what stops the
   pair separating again — the same move the three word keys on the pad just needed. */
.reveal-time,
.reveal-hints,
.reveal-tries {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin: 0;
  font-size: 12px;
  color: var(--ink);
}
.reveal-hints,
.reveal-tries { margin-top: 4px; }
.reveal-hints-figure {
  font-size: 18px;
  font-weight: 700;
  color: var(--hinted);
  font-variant-numeric: tabular-nums;
}
/* ⚠️ A clean solve says so quietly. `None` in the hint colour would shout an absence, and the whole
   point of printing the line unconditionally is that its presence is not the news. */
.reveal-hints[data-state="none"] .reveal-hints-figure {
  font-size: 15px;
  color: var(--muted);
}

/* ⚠️ The same weight as the hints figure but not the same colour: `--hinted` marks a cell the game
   gave away, and an attempt is not that. `--ink` says the number is a fact about the solve rather than
   a debt against it — §27, the game cannot be lost. */
.reveal-tries-figure {
  font-size: 18px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* Solved first time, said quietly for the reason the clean-hints row is: its presence is not the news. */
.reveal-tries[data-state="none"] .reveal-tries-figure {
  font-size: 15px;
  color: var(--muted);
}

.reveal-time-figure {
  font-size: 26px;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* ⚠⚠ **Monospace, because that is what a printed boxscore used**, and because it puts visible
   distance between this and the board's condensed face: the player is looking at a clipping rather
   than at the thing they were typing into. The columns then align by themselves.

   ⚠️ **No aged-paper texture and no newsprint ground.** At 375px there is no room for decoration,
   and a textured background lowers contrast exactly where the player wants to read names. Get the
   form right — columns, monospace, rules — and the texture is unnecessary. */
.clipping {
  font-family: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  font-size: 11px;
  line-height: 1.45;
  color: var(--ink);
}
.clipping .linescore {
  padding: 0;
  margin-bottom: 10px;
}

.clipping-head {
  margin: 0 0 8px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* A real table, so the columns *are* columns: a screen reader gets rows and headers instead of one
   run-on line, and a long name pushes its own column rather than shearing everything after it. */
.clipping-grid {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  margin-bottom: 12px;
}
/* ⚠⚠ **The padding is what separates one number from the next, and without it they read as one.**
   With `padding: 1px 0` a row of single digits printed `40 1  0 0 0` — four at-bats, no runs and a
   hit, rendered as forty-and-one. **Every width measurement passed**: nothing was clipped, nothing
   overflowed, no cell scrolled. The check that found it was looking at the screen. */
/* ⚠️⚠️ **Centred on the column, header and figures alike.** Right-aligned, a three-character header
   like `RBI` hung its last letter over a column of single digits, so the label and the numbers it
   names sat on two different axes — visible on every row of a boxscore that is otherwise a grid.
   Centring gives the column one centre line and puts the header on it.

   ⚠️ **Safe here and not on the playing board.** These are printed figures nobody adds up on screen,
   and `font-variant-numeric: tabular-nums` keeps the digits equal width so a `10` beside a `1` shifts
   by half a digit rather than lurching. The board's own grid stays as it is: its columns are summed by
   eye and by thumb, and a units digit that moves is a column that cannot be read down. */
.clipping-grid th,
.clipping-grid td {
  padding: 1px 0 1px 7px;
  text-align: center;
  white-space: nowrap;
}
/* The rules a newspaper had: under the header, and above the totals. Nowhere else. */
.clipping-grid tr:first-child th {
  border-bottom: 1px solid var(--ink);
  padding-bottom: 2px;
}
.clipping-totals td {
  border-top: 1px solid var(--ink);
  padding-top: 2px;
  font-weight: 700;
}
/* ⚠️ **`.clipping-grid` in front, because `.clipping-grid td` out-specifies a bare `.clipping-who`.**
   Two classes beat one, so the right-alignment meant for the numbers won and every name in the
   boxscore sat flush against its at-bats column. Correct rules, correct classes, and the cascade
   deciding against both — the same shape as the filled cell that lost its highlight to `.cell.blank`. */
.clipping-grid .clipping-team,
.clipping-grid .clipping-who {
  text-align: left;
  width: 99%;
  padding: 1px 6px 1px 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.clipping-grid .clipping-team {
  font-weight: 700;
  letter-spacing: 0.04em;
}

/* Below the grids, as printed boxscores carry it. Named carriers rather than slot references — the
   reference existed to point at a row without naming anyone, and that reason is gone here. */
.clipping-notes {
  margin: 0 0 12px;
  padding-top: 8px;
  border-top: 1px solid var(--rule-strong);
  font-size: 10.5px;
  line-height: 1.5;
}
.clipping-notes-team {
  font-weight: 700;
}

/* The date under the result line. A reveal is written for a reader, so the date reads as one. */
.clipping-date {
  margin: 0 0 10px;
  color: var(--rule-strong);
}

/* A club's name inside the shared pitching block. Ruled above, so the two clubs read as two groups
   rather than as one list — which is what a newspaper's extra leading did. */
.clipping-club th {
  text-align: left;
  padding: 6px 0 1px;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.clipping-club:not(:first-of-type) th {
  border-top: 1px solid var(--rule);
  margin-top: 4px;
}

/* ⚠️⚠️ **This pair is the tutorial's footer, and the resemblance is the fix rather than an accident.**
   `.reveal-close` used to be one full-width button in white paper with a grey border at 44px and 15px
   type — a shape that appears nowhere else in the product. Every other place the app asks for a
   decision uses the same two things: a dark ground for the way onward, an outline for the way back,
   50px tall and 16px. The reveal was the odd screen out, and it is the one screen the player reaches
   by earning it.

   ⚠️ **It is a fourth copy of that look and it should not be.** `.landing-play`, `.tutorial-back` and
   `.tutorial-next` each spell it again, in `landing.css`, which `play.html` does not load. Four places
   saying one thing with nothing checking they agree is this project's most-recorded defect, and the
   copy is being made knowingly rather than discovered later — recorded in `docs/reviews/todo.md`. The
   fix is a shared button rule the three pages can reach, which is a change to three stylesheets. */
.reveal-actions {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.reveal-close,
.reveal-share {
  flex: 1;
  min-height: 50px;
  border: 1px solid var(--rule-strong);
  border-radius: 10px;
  background: var(--key);
  color: var(--ink);
  font: 600 16px/1 var(--face), system-ui, sans-serif;
}

/* ⚠️ **Equal halves, and the tutorial's 1:2 split was tried first.** At 375px that leaves the way back
   121px wide and `Back to the board` breaks onto two lines against a single-line neighbour — a row of
   two buttons at different heights, which reads as a mistake before it reads as a hierarchy. The
   tutorial can afford the split because *Back* is one word. Seen in a picture, not computed. */
.reveal-share {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-color: var(--header-ground);
  background: var(--header-ground);
  color: var(--header-ink);
}

/* ⚠️ Not yet wired to anything, and it says so. The mark and the label dim together because the icon
   is drawn in `currentColor`; a full-strength icon over faded text is the tell of a button that was
   disabled by editing one rule. */
.reveal-share:disabled {
  opacity: 0.45;
}

.reveal-share svg {
  width: 18px;
  height: 18px;
  flex: none;
}
