/* A race: the stage, the entry note beside it, the board beneath.
 *
 * The strip over the door and the column under it are shell.css's, shared with
 * the front and a player's page; the objects are design/components.css's. This
 * sheet places what only a race has, in the same paper and ink: square corners,
 * ink rules at 2 px and 3 px, and a shadow that is an offset with no blur.
 *
 * The one surface that stays dark is the stage. It is a screen, and the club
 * draws every screen on deep ink behind an ink bezel. */

/* 15 px rather than the shell's body step: a timing board reads as one, and
   its density is part of what it is. The face stays the club's. */
/* Warm paper for the ground, one step off the shell's white: the objects on
   this page are all paper — the entry note, the board, the cards under it —
   and on white they had nothing to sit on. The board goes the other way, to
   full paper, so the results read as the sheet pinned to the wall rather than
   as part of the wall. Scoped to this sheet, so the front page keeps its own
   ground. */
body { font: 15px/1.55 var(--body); background: var(--warm-paper); }

/* ── race page chrome ─────────────────────────────────────── */

/* The way out, above everything. A link home and not history.back(): a race
   page is a thing people paste to each other, and on a fresh tab there is no
   previous page for a back button to return to. */
.raceback {
  display: inline-block;
  margin: 0 0 var(--space-gutter-min);
  color: var(--orange-inked);
  font-family: var(--type-eyebrow-family);
  font-weight: var(--type-eyebrow-weight);
  font-size: var(--type-eyebrow-size);
  letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
  text-decoration: none;
}
.raceback::before { content: "\2190\00a0"; }
.raceback:hover { text-decoration: underline; }
.raceback:focus-visible { outline: 3px solid var(--cyan); outline-offset: 3px; }

/* The kind of event, over its name — the same word the card on the front
   page carries, so arriving here confirms what was clicked. */
.raceeyebrow {
  margin: 0;
  color: var(--orange-inked);
  font-family: var(--type-eyebrow-family);
  font-weight: var(--type-eyebrow-weight);
  font-size: var(--type-eyebrow-size);
  letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
}

/* The display face, as on the cards: this is the name of the race, and it
   should read like one rather than like a form label. A step up from the old
   2.8rem ceiling, to the charter's own section-title level — beside a stage
   this wide it was reading as a caption. */
.racetitle {
  margin: 2px 0 4px;
  font-family: var(--type-section-title-family);
  font-weight: var(--type-section-title-weight);
  font-size: min(var(--type-section-title-size), 3.4rem);
  line-height: var(--type-section-title-line);
  letter-spacing: var(--type-section-title-tracking);
  text-transform: uppercase;
}

.facts { display: flex; gap: 26px; flex-wrap: wrap; margin: 14px 0 10px; }
.facts div { display: flex; flex-direction: column; gap: 1px; }
.facts dt {
  color: var(--orange-inked);
  font-family: var(--type-eyebrow-family);
  font-weight: var(--type-eyebrow-weight);
  font-size: var(--type-eyebrow-size);
  letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
}
.facts dd { margin: 0; font-size: 0.95rem; font-variant-numeric: tabular-nums; }

/* Context, not a warning: quieter than the facts it sits under, so it reads
   as part of what the board is rather than a caution stapled onto it. */
.racecaveat { color: var(--ink-70); font-size: 0.82rem; max-width: 60ch; margin: 0 0 18px; }

/* The entry column sits further off the stage than it did: at 18px it read
   as an edge of the picture rather than as its own panel. */
.racegrid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 17rem;
  gap: var(--space-title-to-grid-max);
  align-items: start;
}
@media (max-width: 760px) { .racegrid { grid-template-columns: minmax(0, 1fr); } }

/* ── the entry column ─────────────────────────────────────────────────── */

/* The vote button (when the event has one) and the entry note, stacked in
   the same 17rem grid column. Stretch is the flex default, so the note
   keeps the column's full width; only the button opts out, via
   .vote-button's align-self below, to sit right-aligned above it. */
.entry-col { display: flex; flex-direction: column; gap: 14px; }
.entry-col .vote-button { align-self: flex-end; }

/* The way to training: the page's one call to action now that there is no
   entry form, open by default and sitting above the guide link in the entry
   column. Same padding, ink border and sticker shadow as anything else taped
   to the wall. */
.train {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--cream);
  border: 2px solid var(--ink);
  box-shadow: var(--shadow-sticker-max) var(--shadow-sticker-max) var(--shadow-blur) var(--ink);
}
/* The disclosure itself, styled as the entry column's own primary button —
   the marker stripped so it reads as one rather than as a fold. */
.train__open {
  display: block;
  text-align: center;
  font: 400 0.95rem/1.15 var(--display);
  letter-spacing: var(--type-button-label-tracking);
  text-transform: uppercase;
  padding: 9px 10px;
  cursor: pointer;
  border: 2px solid var(--ink);
  background: var(--orange);
  color: var(--ink);
  box-shadow: var(--shadow-sticker-min) var(--shadow-sticker-min) var(--shadow-blur) var(--magenta);
  list-style: none;
}
.train__open::-webkit-details-marker { display: none; }
.train[open] .train__open { margin-bottom: 4px; }
.train__uv, .train__step, .train__note { margin: 0; font-size: 0.85rem; }
.train__note, .train__uv { color: var(--ink-70); }
.train__uv > summary { cursor: pointer; }
.train__uv > p { margin: 8px 0; }
/* A command line that wraps rather than scrolls: the panel's width is the
   entry column's, and a one-line launcher easily runs wider than it. It breaks
   at its spaces first, so `uv run` and the challenge's id stay whole. */
.train__line {
  margin: 0;
  padding: 7px 9px;
  background: var(--paper);
  border: 2px solid var(--ink);
  font: 600 0.78rem/1.4 var(--mono);
  letter-spacing: var(--mono-tracking-min);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.train__copy {
  align-self: flex-start;
  font: 400 0.85rem/1.15 var(--display);
  letter-spacing: var(--type-button-label-tracking);
  text-transform: uppercase;
  padding: 6px 10px;
  cursor: pointer;
  border: 2px solid var(--ink);
  background: var(--yellow);
  color: var(--ink);
  box-shadow: var(--shadow-sticker-min) var(--shadow-sticker-min) var(--shadow-blur) var(--cyan);
}
.train__copy:active {
  translate: var(--shadow-sticker-min) var(--shadow-sticker-min);
  box-shadow: none;
}

/* ── stage ────────────────────────────────────────────────────────────── */

/* The CRT on its trolley: deep ink behind a 5 px ink bezel, and no offset
   shadow. The club's own screen carries one, but at this size it reads as a
   slab under the picture rather than as a lift off the wall. The shadow pair
   keeps the places it earns — the entry note, and its two buttons. */
/* A frame around the picture, not just a border on it: the charter's offset
   shadow in cyan, which tokens.css assigns to "replay and cabinet shadows" —
   this stage is both. */
.stage {
  --fx-shadow-color: var(--cyan);
  position: relative;
  overflow: hidden;
  background: var(--deep-ink);
  border: 5px solid var(--ink);
}
.stage canvas { display: block; width: 100%; height: 44vh; min-height: 260px; }

/* ── the deck ─────────────────────────────────────────────────────────── */

/* The screen a replay plays on, and nothing under it. It used to be a chassis
   with a transport below the picture; a replay always plays and the names
   always ride the ducks, so that bar had only the follow key left to carry,
   and the key now rides the picture. `vote.js` builds the same `.stage` bare,
   without the shadow or the room under it. */
.deck { margin-bottom: 26px; }

/* ── the stage's vignette ─────────────────────────────────────────────── */

.stage::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none; /* the canvas still gets the drag for orbiting */
  /* The scanlines that used to sit over this are gone: on a small stage they
     read as scratches across the picture. The vignette stays, from 72%. */
  background: radial-gradient(130% 100% at 50% 50%, transparent 72%, rgba(0, 0, 0, 0.26) 100%);
}

/* ── the follow key ───────────────────────────────────────────────────── */

/* In the picture's corner, over the vignette, which would otherwise dim it.
   Hollow: a light grey rule, deep ink inside, the same grey lettering — the
   charter's quiet for labels over ink, so the key reads as part of the
   picture rather than as a slab over it. Full paper on hover. */
.deck #follow {
  position: absolute;
  z-index: 1;
  right: 18px;
  bottom: 14px;
  font: 400 0.8rem/1 var(--display);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 7px 12px;
  cursor: pointer;
  color: var(--paper-60);
  background: var(--deep-ink);
  border: 2px solid var(--paper-60);
  box-shadow: none;
}
.deck #follow:hover { color: var(--paper); border-color: var(--paper); }
.deck #follow:active { translate: 2px 2px; }

/* ── the orbit hint ───────────────────────────────────────────────────── */

/* The picture can be taken: the hand says so where the pointer lands. */
.stage canvas { cursor: grab; }
.stage canvas:active { cursor: grabbing; }

/* A hand dragging the camera side to side (deck.js offerOrbit), low in the
   picture so it never covers the leader the chase centres, over the line that
   says it in the follow key's quiet. Never in the drag's way: a visitor who
   reaches for the picture at once gets the canvas. */
.orbit-hint {
  position: absolute;
  z-index: 1;
  inset: auto 0 14% 0;
  display: grid;
  justify-items: center;
  gap: 8px;
  pointer-events: none;
}
.orbit-hint .symbol {
  width: 44px;
  height: 44px;
  color: var(--paper);
  filter: drop-shadow(2px 2px 0 var(--ink));
}
.orbit-hint p {
  margin: 0;
  padding: 6px 10px;
  font: 400 0.72rem/1 var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--paper);
  background: var(--deep-ink);
  border: 2px solid var(--paper-60);
}
.orbit-hint__touch { display: none; }
@media (pointer: coarse) {
  .orbit-hint__mouse { display: none; }
  .orbit-hint__touch { display: inline; }
}

/* ── the board ────────────────────────────────────────────────────────── */

/* The pegboard, hung by hand: warm paper inside a 3 px ink frame on its own
   ink shadow, one rule between rows, taped up. */
/* Straight, unlike the note beside it. The charter's alternating lean suits a
   flyer, which is one small thing pinned to a wall; this sheet grows a row per
   published run, and a long table tilted off true reads as a rendering fault
   rather than as paper. The tape stays — it is still stuck up. */
/* The frame belongs to the sheet now, not to the table: the toolbar and the
   results are one piece of paper, so one border and one shadow go round both. */
.boardsheet {
  position: relative;
  margin-top: 30px;
  background: var(--paper);
  border: 3px solid var(--ink);
  box-shadow: var(--shadow-sticker-max) var(--shadow-sticker-max) var(--shadow-blur) var(--ink);
}

table.board {
  width: 100%;
  border-collapse: collapse;
}

table.board th {
  text-align: left;
  color: var(--orange-inked);
  font-family: var(--type-eyebrow-family);
  font-weight: var(--type-eyebrow-weight);
  font-size: var(--type-eyebrow-size);
  letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
  padding: 10px 10px 8px;
  border-bottom: 3px solid var(--ink);
}
table.board td { padding: 11px 10px; border-top: 2px solid var(--ink); }
table.board tbody tr:first-child td { border-top: 0; }
/* No row hover: the row does not do anything when you click it any more, and
   a surface that lights up under the pointer is promising that it will. What
   is clickable here lights up on its own — the button, the two glyphs and the
   lane box. */

/* Two blocks with the slack between them: who and which policy on the left,
   then the measurements and the button that plays them on the right. Policy
   is the only column without a width, so it is where the table's spare width
   goes — everything after it closes up against the record button.
   Who hugs its own text, with a wider gutter than the table's so the handle
   and the policy name do not read as one column. */
table.board th:nth-child(3),
table.board td:nth-child(3) { width: 1%; white-space: nowrap; padding-right: 80px; }

/* The one way to a run's page, and it says so. Orange with the charter's
   offset shadow: the action colour, on the one action in the row that leaves
   the board. It hugs the button, so it sits against the seeds rather than
   drifting off to the right. */
table.board .record { width: 1%; white-space: nowrap; padding-left: 22px; }
.record-link {
  display: inline-block;
  padding: 6px 12px;
  background: var(--orange);
  color: var(--ink);
  border: 2px solid var(--ink);
  box-shadow: var(--shadow-sticker-min) var(--shadow-sticker-min) var(--shadow-blur) var(--ink);
  font: 400 0.78rem/1 var(--display);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
}
.record-link:hover { background: var(--yellow); }
.record-link:active {
  translate: var(--shadow-sticker-min) var(--shadow-sticker-min);
  box-shadow: none;
}
.record-link:focus-visible { outline: 3px solid var(--cyan); outline-offset: 2px; }

/* the lane tab: a filled bar for a duck on the stage, hollow otherwise */
table.board .tab { width: 34px; padding-left: 0; padding-right: 4px; }
table.board td.tab {
  border-left: 5px solid transparent;
  border-left-color: var(--lane, transparent);
}
table.board td.tab input { accent-color: var(--lane, var(--ink)); cursor: pointer; }

table.board .rank {
  width: 3.5rem;
  font: 400 1.1rem/1 var(--display);
  color: var(--ink-70);
  font-variant-numeric: tabular-nums;
}
table.board tbody tr:first-child .rank { color: var(--orange-inked); }
table.board .seeds { width: 5rem; text-align: right; color: var(--ink-70); font-variant-numeric: tabular-nums; }
/* The speed the sweep found for this policy. Deliberately quiet next to the
   time: it explains the time, it is not competing with it. */
table.board .speed {
  width: 4.5rem;
  text-align: right;
  color: var(--sky-inked);
  font-variant-numeric: tabular-nums;
}
table.board tr.dnf .speed { color: var(--ink-70); }

/* the one loud thing on the page */
table.board .time {
  width: 8.5rem;
  text-align: right;
  font: 400 1.7rem/1 var(--display);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}
table.board tbody tr:first-child .time { color: var(--orange-inked); }
table.board tr.dnf .time { font-size: 1.05rem; color: var(--orange-inked); }
table.board tr.dnf { color: var(--ink-70); }

/* A voted board's one loud column, in the exact slot .time takes on a timed
   one -- the two never appear together, so they share the same weight. */
table.board .votes {
  width: 8.5rem;
  text-align: right;
  font: 400 1.7rem/1 var(--display);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
}
table.board tbody tr:first-child .votes { color: var(--orange-inked); }

.empty { color: var(--ink-70); margin-top: 22px; }
/* Inside the frame it has no margin to sit on: the sheet's own padding is
   what keeps it off the border. */
.boardsheet .empty { margin: 0; padding: 18px var(--space-card-padding-min); }

/* A failed withdrawal, said under the board it was asked from. */
.board-note { margin: 8px 0 0; color: var(--orange-inked); font-weight: 600; }
.board-note:empty { display: none; }

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

/* ── which duck is which ──────────────────────────────────────────────── */

/* No key on the picture: the ring under each duck and the tab of its row on
   the board (above) wear the same lane colour, and that is the whole key. */
table.board tr.tier-ghost { color: var(--ink-70); }
table.board tr.tier-ghost .time { color: var(--ink-70); }
table.board tr.tier-ghost .votes { color: var(--ink-70); }

/* Download column. Quiet by default and orange on hover: it is an offer, not
   the point of the row. */
/* The three icons at the end are one row of buttons, so they keep one
   spacing: equal cells, each glyph centred in its own, rather than three
   widths chosen for three columns that happen to sit side by side. The
   download used to have half a cell to itself and the gaps stepped. */
table.board .env,
table.board .grab,
table.board .mine {
  width: 2.6rem;
  padding-left: 0;
  padding-right: 0;
  text-align: center;
}
table.board .grab a {
  /* inline-flex, so the glyph centres in the cell rather than sitting on the
     text baseline it no longer has. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  color: var(--ink-70);
  text-decoration: none;
}
table.board .grab a:hover,
table.board .grab a:focus-visible { color: var(--ink); background: var(--yellow); }

/* Take-off-the-board control. Only ever rendered on your own rows.
   It WAS transparent until the row was hovered, on the theory that a rare
   destructive action should not draw the eye. That produced a button nobody
   could find — the first person to look for it asked where it was. Quiet is a
   dim colour, not an invisible one: a control you cannot see is not discreet,
   it is missing. Its cell's width is set with the other two icons above. */
table.board .withdraw {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-70);
  cursor: pointer;
  transition: color 0.12s ease, background 0.12s ease;
}
table.board tr:hover .withdraw { color: var(--ink); }
table.board .withdraw:hover,
table.board .withdraw:focus-visible { color: var(--paper); background: var(--magenta); }

/* ── which runs the board shows ───────────────────────────────────────── */

/* The sheet's own header, inside its frame and above the column heads: these
   controls act on this table and nothing else, and floating them above the
   paper left two objects where there is one. */
.boardtop {
  display: flex;
  flex-wrap: wrap;
  gap: 10px var(--space-gutter-min);
  justify-content: space-between;
  align-items: center;
  padding: 11px var(--space-card-padding-min);
  border-bottom: 3px solid var(--ink);
}

/* Left of the header, over the column of lane boxes it ticks: one box that
   puts everything the table is SHOWING on the stage, or takes it all off.
   It was three — All, Mine, Top 5 — which said the same words as the filters
   across from them and meant something else, and nothing but their look kept
   the two apart. Anything between all and nothing is picked row by row now.
   Sized up from the row boxes: it commands them, so it is not smaller. */
.pickall {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}
/* The eyebrow the old fieldset's legend wore, and for the same reason: the
   column of boxes under it cannot say what ticking one does. It shares the
   face and the orange with the column heads directly beneath it. */
.pickall span {
  color: var(--orange-inked);
  font-family: var(--type-eyebrow-family);
  font-weight: var(--type-eyebrow-weight);
  font-size: var(--type-eyebrow-size);
  letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
}
.pickall-box {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--orange);
  cursor: pointer;
}

.modes { display: flex; gap: 8px; }

/* They were three stickers, each its own colour and its own lean, pressed on
   above the paper. On the sheet's own header that is three accents and three
   angles inside a frame that is straight, so they go quiet: hollow keys in
   ink, no colour, no shadow, no lean. The one that is on fills with ink —
   the one that is on fills with the action's orange, which is the same colour
   the Watch button in every row already carries. */
.modes button {
  padding: 6px 13px;
  border: 2px solid var(--ink);
  background: none;
  color: var(--ink);
  font: 400 0.8rem/1 var(--display);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
}
.modes button:hover { background: var(--warm-paper); }
.modes button[aria-pressed="true"] {
  background: var(--orange);
  border-color: var(--ink);
  color: var(--ink);
}
.modes button:active { translate: 1px 1px; }

/* The link to a policy's training environment. Same weight as the download
   glyph beside it: both are offers, neither is the point of the row. */
table.board .env a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  color: var(--ink-70);
  text-decoration: none;
}
table.board .env a:hover,
table.board .env a:focus-visible { color: var(--ink); background: var(--sky); }

/* The focus ring is cyan, on everything that takes focus — the club's rule. */
.deck #follow:focus-visible,
.modes button:focus-visible,
.pickall-box:focus-visible,
.train__open:focus-visible,
.train__copy:focus-visible,
.train__uv > summary:focus-visible,
.signin-button:focus-visible,
table.board .grab a:focus-visible,
table.board .env a:focus-visible,
table.board .withdraw:focus-visible,
.votehead__back:focus-visible {
  outline: 3px solid var(--cyan);
  outline-offset: 2px;
}

/* The voting booth. The colour buttons ARE the ballot, so they are the
   loudest thing under the stage — the opposite of the lane stripes, which
   stay quiet because the ring already identifies a lane in motion. */
.votehead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Quiet on purpose, next to the title's display face: this is the way out,
   not the point of the page. */
.votehead__back {
  font: var(--type-eyebrow-weight) var(--type-eyebrow-size) / var(--type-eyebrow-line) var(--type-eyebrow-family);
  letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
  color: var(--ink-70);
  text-decoration: none;
  white-space: nowrap;
}
.votehead__back:hover { color: var(--orange-inked); }

.vote-panel {
  margin: 1.25rem 0 2rem;
  text-align: center;
}

.vote-headrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
}

.vote-lead { margin: 0; font-size: 1.05rem; }

/* The signed-out booth: one link, nothing to fill with. */
.signin-button {
  display: block;
  text-align: center;
  font: 400 0.95rem/1.15 var(--display);
  letter-spacing: var(--type-button-label-tracking);
  text-transform: uppercase;
  padding: 9px 10px;
  border: 2px solid var(--ink);
  background: var(--orange);
  color: var(--ink);
  text-decoration: none;
  box-shadow: var(--shadow-sticker-min) var(--shadow-sticker-min) var(--shadow-blur) var(--magenta);
}
.signin-button:active {
  translate: var(--shadow-sticker-min) var(--shadow-sticker-min);
  box-shadow: none;
}

/* The speaker toggle for sound.js's three cues. Quiet, like the back link up
   in .votehead: this row is the ballot, not the sound. */
.vote-mute {
  padding: 2px 7px;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-70);
  background: none;
  border: 2px solid transparent;
}
.vote-mute:hover { color: var(--ink); border-color: var(--ink); }
.vote-mute:focus-visible {
  outline: 3px solid var(--cyan);
  outline-offset: 2px;
}

.vote-picks {
  display: flex;
  justify-content: center;
  gap: 0.9rem;
  flex-wrap: wrap;
}

/* The picks, as post-its stuck to the row rather than printed on it: square,
   a soft shadow, one corner folded, tilted a couple of degrees each way so
   the row reads as pressed on by hand. Heavy things sit square in this
   charter and paper tilts — a post-it is paper. */
.vote-pick {
  position: relative;
  width: 4.5rem;
  height: 4.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--ink);
  border-radius: 2px;
  background: var(--pick);
  box-shadow: var(--shadow-sticker-min) var(--shadow-sticker-min) var(--shadow-blur) var(--ink);
  cursor: pointer;
  transition: translate 0.12s ease;
}
.vote-picks button:nth-child(odd) { rotate: var(--rotate-2-5); }
.vote-picks button:nth-child(even) { rotate: calc(-1 * var(--rotate-2-5)); }

/* The folded corner: a triangle of shadow at the bottom right, as if the
   sticker were peeling there. */
.vote-pick::before {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  border-style: solid;
  border-width: 0 0 14px 14px;
  border-color: transparent transparent rgba(16, 16, 24, 0.35) transparent;
}

/* Every swatch in LANE_CSS is light (orange, sky, lime, lavender, yellow), so
   dark ink reads on all five without a per-colour exception — a deuteranope
   gets a shape and a number, not only a hue. Bigger than before: this is what
   makes a pick usable when its hue alone is not. */
.vote-pick__num {
  font: 700 1.9rem/1 var(--display);
  color: #12131a;
}

.vote-pick:hover,
.vote-pick:focus-visible {
  border-color: #fff;
  translate: 0 -2px;
}

.vote-pick:disabled {
  cursor: progress;
  opacity: 0.6;
  translate: 0 0;
}

.vote-count,
.vote-empty {
  margin: 1rem 0 0;
  opacity: 0.8;
}

.vote-status { margin: 0.75rem 0 0; color: var(--ink-70); font-size: 0.85rem; min-height: 1.2em; }
.vote-status.bad { color: var(--orange-inked); font-weight: 600; }

/* ── the ballot ceremony ──────────────────────────────────────────────── */

/* Everything below is `runCeremony`'s overlay (see vote.js): a post-it flies
   to the bottom centre, an envelope rises to meet it and waits open for the
   server, then either seals with a stamp and fires two "+1"s at the player
   chip, or slides away empty. It lives on `document.body`, not in the
   panel, so a vote that resolves fast can swap the panel out from under it
   without cutting the ceremony off. Every state below is a class `vote.js`
   adds by hand on real timers, not a CSS animation with a fixed length. */
.vote-ceremony {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
}

.vote-ceremony__postit {
  position: fixed;
  left: 50%;
  bottom: 46%;
  width: 3.4rem;
  height: 3.4rem;
  display: flex;
  align-items: center;
  justify-content: center;
  translate: -50% 0;
  scale: 1;
  opacity: 0;
  border: 2px solid var(--ink);
  border-radius: 2px;
  background: var(--pick);
  box-shadow: var(--shadow-sticker-min) var(--shadow-sticker-min) var(--shadow-blur) var(--ink);
  transition: bottom 0.15s var(--ease-hard), opacity 0.15s ease, scale 0.3s ease;
}
.vote-ceremony__postit .vote-ceremony__num {
  font: 700 1.5rem/1 var(--display);
  color: #12131a;
}
.vote-ceremony__postit--fly { opacity: 1; bottom: 18%; }
.vote-ceremony__postit--in {
  bottom: 6%;
  scale: 0.35;
  opacity: 0;
  transition-duration: 0.3s;
}

.vote-ceremony__envelope {
  position: fixed;
  left: 50%;
  bottom: 0;
  translate: -50% 100%;
  width: min(80vw, 20rem);
  height: 8rem;
  border: 3px solid var(--ink);
  border-bottom: 0;
  background: var(--cream);
  box-shadow: var(--shadow-card-max) var(--shadow-card-max) var(--shadow-blur) var(--ink);
  overflow: hidden;
  transition: translate 0.3s var(--ease-hard);
}
.vote-ceremony__envelope--up { translate: -50% 0; }
.vote-ceremony__envelope--out { translate: -50% 100%; }

/* The flap: hinged at the top, open until the vote is confirmed. */
.vote-ceremony__flap {
  position: absolute;
  inset: 0 0 auto 0;
  height: 4.5rem;
  background: var(--warm-paper);
  border-bottom: 3px solid var(--ink);
  transform-origin: top center;
  rotate: x -35deg;
  transition: rotate 0.24s var(--ease-hard);
}
.vote-ceremony__envelope--sealed .vote-ceremony__flap { rotate: x 0deg; }

@media (prefers-reduced-motion: reduce) {
  .vote-pick,
  .vote-ceremony__postit,
  .vote-ceremony__envelope,
  .vote-ceremony__flap {
    transition: none;
  }
}

/* ── a publication: the run page ─────────────────────────────────────── */

/* The one page in the club that does not scroll.
 *
 * Everything the reveal animates has to be on screen together, or the stars
 * fly to a chip nobody is looking at — which is exactly what happened when
 * the page was three windows long. So the page is the window tall, and of its
 * four rows the head and the seeds are as tall as their content, the board
 * shows four rows at most and scrolls for the rest, and the picture takes
 * what is left — never less than its floor, since the board gives first:
 * min-content here, and on the two grids inside, is what carries the floor
 * up from the stage. */
#app:has(> main.run) { height: 100vh; }

.hall.run {
  display: grid;
  grid-template-rows: auto minmax(min-content, 1fr) auto auto;
  gap: var(--space-gutter-min);
  min-height: 0;
  padding-block: 20px;
}

/* The strip stays put, in its own ink: a sticky bar needs an opaque
   background, and the paper it used to take turned the strip white halfway
   through the theatre. Only the scrolling fallback below can still move. */
.signage--pinned { position: sticky; top: 0; z-index: 2; background: var(--ink); }

/* The title is the way back to the event, so the page carries no back link. */
.run-head .racetitle a { color: inherit; text-decoration: none; }
.run-head .racetitle a:hover { color: var(--orange-inked); }

/* Who made it, when, and what you can take away: one dot-separated line.
   Plain inline flow, not a flex row: the separators are text, and flex would
   make each of them its own item with its own gaps. */
.run-byline { margin: 0; color: var(--ink-70); }
.run-byline a { color: var(--sky-inked); }
.run-byline .sticker { font-size: 0.7rem; padding: 2px 8px; }

/* Hidden, and still holding its place.
 *
 * Every box on this page stands there from the first frame — the scoreboard on
 * zeros, the board with you at the bottom, the receipts you have not earned
 * yet — and a beat unveils its content. `hidden` would take the box out of the
 * flow, the stage would take the height it gave up, and the picture would jump
 * in the middle of the theatre. Measured before this: the stage stepped from
 * 516 to 410 to 393 px across three beats. */
.veiled { visibility: hidden; }

/* The stage and the result, shoulder to shoulder: every moving thing is here,
   a short flight from the chip. The caption runs under both, the whole width
   of the column, so it never has to wrap into a second line it cannot afford. */
.run-top {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  grid-template-rows: minmax(min-content, 1fr) auto;
  gap: 6px var(--space-gutter-max);
  min-height: 0;
}
/* The stage gives the rest of the page its slack, down to a floor below which
   there is no picture left worth watching. It is the challenge page's deck. */
.run-top .deck { margin: 0; }
/* The floor is what leaves an open seed list its room on an 800 px window.
   `contain: size` keeps the canvas out of the arithmetic: its drawing buffer
   is sized to the box it was last given, and without this the box could
   never get smaller than that. */
.run-top .stage { min-height: 196px; contain: size; }
.run-top .stage canvas { height: 100%; min-height: 0; }
.run-facts { grid-column: 1 / -1; margin: 0; color: var(--ink-70); }

/* The result: the split-flap time at the top of the column, the stickers at
   its foot. There is no screen behind the stamp any more — a stamp wants a
   corner to be slapped on, not a panel of its own to be centred in, and the
   panel was the biggest thing on the page for the least it had to say. */
/* Each block as tall as it needs to be, stacked from the top: the column is
   deliberately shorter than the picture beside it, and the room left under it
   is where the next thing this page grows will go. */
.run-score {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  align-content: start;
  gap: 12px;
  margin: 0;
  min-height: 0;
}
/* One line: the label, the time, and the lane the stamp comes down in beside
   it. It was three lines and 172px at 1440x900; one line hands the column's
   foot the room the clip player needs. */
.run-score .scoreboard {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  gap: 12px;
  padding: 10px var(--space-card-padding-min);
}
.run-score .flip { font-size: clamp(1.4rem, 2.6vw, 2.2rem); }
/* The stamp keeps a lane of its own rather than hanging off a corner: the
   board shrinks with the window, and an absolute stamp landed on the digits
   at 780px of height and on the stickers when it cleared the edge. Tilting is
   a transform, so it costs the lane nothing. It is veiled, never hidden — the
   lane has to hold its height before the stamp arrives. */
.run-score .stamp {
  justify-self: end;
  font-size: clamp(0.8rem, 1.3vw, 1.1rem);
  rotate: calc(-1 * var(--rotate-4-0));
}

/* What the run earned, one banknote per receipt: the denomination on a panel
   in its act's colour at the left end, a rule, then what it paid for across
   the face — one line, no taller than the stickers it replaces. A note and
   not a sticker: a sticker is pressed, with a shadow to push it onto, and
   these do nothing. The magenta panel takes paper lettering, as the magenta
   sticker does. */
.run-receipts { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; }
.run-receipts .mono-label { color: var(--ink-70); }
.receipt {
  display: inline-flex;
  align-items: stretch;
  border: 2px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
}
.receipt-points {
  padding: 3px 6px;
  border-right: 2px solid var(--ink);
  font: 400 0.85rem/1.4 var(--display);
  letter-spacing: 0.02em;
}
/* The mono scale's tighter tracking: three notes have to share one row of
   the column, and at the label's usual tracking they were ten pixels over. */
.receipt .mono-label {
  align-self: center;
  padding: 0 6px;
  color: var(--ink);
  letter-spacing: var(--mono-tracking-min);
}
.receipt-lime .receipt-points { background: var(--lime); }
.receipt-cyan .receipt-points { background: var(--cyan); }
.receipt-lavender .receipt-points { background: var(--lavender); }
.receipt-magenta .receipt-points { background: var(--magenta); color: var(--paper); }

/* Share the run: the four networks as marks on one row, the two clips as cyan
   stickers on the next, and one line under them saying what a press did.
   Present from the first frame and veiled through a reveal, like everything
   in this column. */
.run-share { display: flex; flex-direction: column; gap: 10px; margin: 0; min-height: 0; }
.run-share-networks,
.run-share-clips { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.run-share .sticker-mark { padding: 6px 10px; line-height: 0; }
.run-share .sticker-logo { width: 18px; height: 18px; fill: currentColor; }
.run-share [aria-disabled="true"] { opacity: 0.55; cursor: default; box-shadow: none; }
.run-share-said { margin: 0; color: var(--ink-70); }

/* A clip is made by a machine that takes minutes, so the row says so while it
   works: the reel is on the sticker only while there is a render to show, and
   it turns — the one thing on this page that says the process is alive. */
.run-share [data-clip] { display: inline-flex; align-items: center; gap: 7px; }
.run-share [data-clip] .clip-reel { display: none; }
.run-share .sticker-working .clip-reel { display: block; }
.clip-reel { width: 15px; height: 15px; flex: none; fill: currentColor; animation: reel-turn 2.4s linear infinite; }

/* The landscape clip, small, in the room this column keeps under its stickers:
   the screen takes what is left of the column, the video fills its height at
   16:9 up to 22rem wide, and where the room is too short to watch anything
   the video steps aside rather than spill onto the board. The render panel is
   the same box at the same size, so the clip lands where it was being made. */
.run-share-screen { flex: 1 1 0; min-height: 0; container-type: size; }
.run-share-screen video,
.clip-render { display: block; height: 100%; max-height: 12.375rem; max-width: 100%; aspect-ratio: 16 / 9; background: var(--deep-ink); }
.run-share-screen video[hidden], .clip-render[hidden] { display: none; }
@container (max-height: 72px) { .run-share-screen video, .clip-render { display: none; } }

.clip-render {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 9px;
  padding: 0 clamp(12px, 5%, 22px);
  color: var(--paper);
}
.clip-render .clip-reel { width: 26px; height: 26px; }
.clip-render-say { margin: 0; font: 700 1rem/1.2 var(--display); }
.clip-render-step { margin: 0; color: var(--paper-60); }
.clip-render-bar { position: relative; height: 6px; overflow: hidden; background: rgba(250, 248, 242, 0.18); }
.clip-render-bar[hidden] { display: none; }
.clip-render-bar > span { display: block; width: 0; height: 100%; background: var(--cyan); transition: width 300ms linear; }
/* No frame count to report yet — the opening and the soundtrack take minutes
   of their own. A bar sweeping says working; a bar at zero says broken. */
.clip-render-bar-unknown > span { width: 34%; animation: reel-sweep 1.6s ease-in-out infinite; }
.clip-render-jammed .clip-reel { animation: none; opacity: 0.45; }
.clip-render-jammed .clip-render-step { color: var(--cyan); overflow-wrap: anywhere; }

@keyframes reel-turn { to { rotate: 360deg; } }
@keyframes reel-sweep { from { translate: -100% 0; } to { translate: 300% 0; } }

/* The reel and the sweep are the only things here that never end, so zeroing
   their duration would freeze them mid-turn. They stop instead; the bar's own
   width still moves, which is where the progress actually is. */
@media (prefers-reduced-motion: reduce) {
  .clip-reel, .clip-render-bar-unknown > span { animation: none; }
}

/* The board: the challenge page's sheet with the whole field on it, this run's
   row in lime. The sheet's toolbar stays put and the table scrolls under it,
   so the stage keeps its height whatever the field's size. Tighter rows than
   the challenge page's: this page is one window tall, and every row here is
   a row off the picture. The cap is the toolbar, the heading and four rows
   of 41 px, which is what the old pegboard showed; past it the field
   scrolls. */
.run-board { display: grid; min-height: 0; max-height: 254px; }
.run-board .boardsheet {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  margin-top: 0;
  min-height: 0;
}
.run-board #board { overflow-y: auto; min-height: 0; }
.run-board table.board th { padding: 6px 10px 5px; }
.run-board table.board td { padding: 5px 10px; }
.run-board table.board .time,
.run-board table.board .votes { font-size: 1.2rem; }
.run-board table.board .record { padding-left: 12px; }
.run-board table.board tr.you td { background: var(--lime); }

/* Too short or too narrow for one screen: the page scrolls, as the others do.
   Under 860 px of height the picture at its floor, the sheet's toolbar and
   four rows, the head and the seeds no longer share one window — the board
   was down to two rows and the picture to its floor — so a laptop gets the
   page at its natural size instead, and the reveal walks it with `bring`. */
@media (max-height: 860px), (max-width: 860px) {
  #app:has(> main.run) { height: auto; }
  .hall.run { display: block; }
  .hall.run > * + * { margin-top: var(--space-gutter-max); }
  .run-top .stage { height: 44vh; min-height: 260px; }
  .run-board { max-height: none; }
  .run-share-screen { flex: none; height: 12.375rem; }
}
/* Too narrow for two columns as well: the result goes under the picture. */
@media (max-width: 860px) {
  .run-top { grid-template-columns: minmax(0, 1fr); gap: var(--space-gutter-max); }
}

/* ── the seeds, folded away under their summary ───────────────────────── */

.run-seedlist { margin: 0; }
.run-seedlist summary { cursor: pointer; }
.run-seedlist ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin: 8px 0 0;
  padding: 0;
}
.seed { display: flex; align-items: baseline; gap: 8px; font-variant-numeric: tabular-nums; }
.seed strong { font: 400 1.35rem/1 var(--display); }
.seed.replayed strong { color: var(--orange-inked); }

/* ── points as a shower of stars ──────────────────────────────────────── */

/* The overlay is the viewport; points.js places every star in it. The glyph
   pops in (star-in) after its --spawn; the star itself waits --wait more,
   then flies the --dx/--dy the page measured to the chip (star-fly). Two
   elements because both beats fill both ways, and one element cannot hold
   two animations that disagree about its scale. */
.shower { position: fixed; inset: 0; z-index: 61; pointer-events: none; }
.star {
  position: absolute;
  left: var(--x);
  top: var(--y);
  font: 700 1.1rem/1 var(--display);
  color: var(--yellow);
  text-shadow: 1px 1px 0 var(--ink);
  animation: star-fly var(--t-roll) var(--ease-hard) calc(var(--spawn) + var(--wait)) both;
}
.star > span {
  display: block;
  animation: star-in var(--t-flip) var(--ease-drop) var(--spawn) both;
}
@keyframes star-in {
  from { opacity: 0; scale: 0.2; }
  to { opacity: 1; scale: 1; }
}
@keyframes star-fly {
  to { translate: var(--dx) var(--dy); scale: 0.5; opacity: 0.7; }
}

/* ── the way on: the other rounds ─────────────────────────────────────── */

/* A deep-ink band under the board, full width, with the front page's own
   cards dealt on it (cards.css). The chequer over it is the finish line — the
   one place on the site where a racing flag belongs, and it separates the
   paper above from the ink below without needing a rule. */
.rounds-on {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-top: 64px;
  padding: 58px max(var(--space-gutter-min), calc(50vw - var(--space-content-column) / 2)) 64px;
  background: var(--deep-ink);
  color: var(--paper);
}
.rounds-on::before {
  content: "";
  position: absolute;
  top: -14px;
  left: 0;
  right: 0;
  height: 14px;
  /* Two offset checker rows, drawn the way components.css draws the gym
     floor: gradients as a pattern, never a colour ramp. */
  background-color: var(--warm-paper);
  background-image:
    linear-gradient(45deg, var(--ink) 25%, transparent 25% 75%, var(--ink) 75%),
    linear-gradient(45deg, var(--ink) 25%, transparent 25% 75%, var(--ink) 75%);
  background-size: 14px 14px;
  background-position: 0 0, 7px 7px;
}

.rounds-on__head {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-gutter-min);
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-title-to-grid-max);
}
.rounds-on .eyebrow {
  margin: 0;
  color: var(--orange);
  font-family: var(--type-eyebrow-family);
  font-weight: var(--type-eyebrow-weight);
  font-size: var(--type-eyebrow-size);
  letter-spacing: var(--type-eyebrow-tracking);
  text-transform: uppercase;
}
.rounds-on__title {
  margin: 6px 0 0;
  font-family: var(--type-section-title-family);
  font-weight: var(--type-section-title-weight);
  font-size: min(var(--type-section-title-size), 3.4rem);
  line-height: var(--type-section-title-line);
  letter-spacing: var(--type-section-title-tracking);
  text-transform: uppercase;
  color: var(--paper);
}
