/*
  Through Ball's shell.

  Two rules the layout exists to hold. The drag starts ANYWHERE on screen, so nothing overlaying the
  pitch may swallow a pointer — the HUD is `pointer-events: none` and only the summary card takes
  input back. And the game is landscape with goals left and right, so the stage is the whole viewport
  and the canvas letterboxes inside it rather than cropping the pitch.
*/

:root {
  --surround: #0d1f17;
  --card: #10281c;
  --ink: #eef4f0;
  --dim: rgba(238, 244, 240, 0.62);
  /* A third step below `--dim`: the side of a stat row that is NOT the one to read, and the line of
     the player's own numbers under it. Present but not competing. */
  --faint: rgba(238, 244, 240, 0.34);
  /* The live dot on the pause cover. Amber rather than red for the same reason no kit is red. */
  --live: #e7b93c;
  --panel: rgba(9, 24, 17, 0.82);
  --home: #37c9ff;
  --warn: #ff6b6b;
  --good: #5ee08a;
  --font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

* {
  box-sizing: border-box;
}

/*
  A class that sets `display` outranks the UA stylesheet's `[hidden] { display: none }`, so a panel
  laid out as grid or flex stays on screen with the attribute set. The summary card was visible over
  a live match for exactly that reason; state it once here rather than per component.
*/
[hidden] {
  display: none !important;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--surround);
  color: var(--ink);
  font-family: var(--font);
  /* A drag is the only gesture; the browser must not turn one into a scroll or a page zoom. */
  overscroll-behavior: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.stage {
  position: fixed;
  inset: 0;
  touch-action: none;
}

.pitch {
  display: block;
  width: 100%;
  height: 100%;
}

.hud {
  position: absolute;
  inset: 0;
  pointer-events: none;
  padding: max(0.5rem, env(safe-area-inset-top)) max(0.75rem, env(safe-area-inset-right))
    max(0.5rem, env(safe-area-inset-bottom)) max(0.75rem, env(safe-area-inset-left));
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/*
  The status strip: the imported `In-Game Status Bar` design's 1A, as two capsules rather than one.

  **The design's single frame is not stretched to the viewport.** It is drawn 1240px wide with a
  spacer holding the controls out to its right edge, and a spacer is an instruction to fill whatever
  it is given — so on a real screen it became a dark band across the whole width of the pitch, which
  is the top of the game gone. The readings take the width they need; the controls keep the
  top-right corner they have had since v1, which is where a thumb reaches for them and about the
  only place on this screen the ball never is. What is between the two is pitch.

  **Written against ONE font size, everything below it an `em`** — the rule the stat sheet and the
  goal banner are both written to, and for the same reason: this has to live at desktop size and on
  a 390px-tall phone in landscape, and a screen too short for it should change one number rather
  than drop a cell. That number lives here, on the strip, so the two capsules cannot come out at two
  scales. It is the EYEBROW, which is `1em`: the smallest and most fragile thing in the bar, so the
  type that is hardest to read is what every other length is measured from. The design's own pixels
  divided by its 9.5px eyebrow are the ratios below — its 64px capsule is 6.737em, its 25px figures
  2.632em, its 36px crest 3.789em, its 42px controls 4.421em.
*/
.hud-top {
  display: flex;
  align-items: flex-start;
  gap: 1.263em;
  font-size: 0.6rem;
  font-variant-numeric: tabular-nums;
}

/*
  `position: relative` and `overflow: hidden` are both load-bearing: the time bar is a child laid
  along the bottom edge, and it has to be clipped by the capsule's own radius rather than square off
  its corners. No `flex-grow`, so the capsule is exactly as wide as its cells.
*/
.hud-bar {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: stretch;
  min-width: 0;
  height: 6.737em;
  background: var(--panel);
  border-radius: 1.263em;
  box-shadow:
    0 1.474em 4.211em -1.895em rgba(0, 0, 0, 0.9),
    inset 0 0 0 1px rgba(238, 244, 240, 0.1);
}

/*
  The overlay gets out of the ball's way.

  Reported from play: the bar covers the top of the pitch, and a ball that goes up there goes behind
  it. `main.js` projects the ball through `render.js`'s own `screenPoint` every frame and asks each
  capsule whether the ball is inside its box, so this is the drawing answering the game rather than a
  region typed here that the camera could move out from under.

  **The resting state is the visible one**, which is why this is a transition and the banner's
  entrance is not: a hidden automated tab creates no transition at all, so what it renders here is
  the fully opaque bar it would have rendered anyway. Written the other way round — ducked at rest,
  transitioning up — it would have read as a permanently invisible HUD in every automated screenshot.
*/
.hud-bar,
.hud-controls {
  transition: opacity 0.16s ease;
}

.hud-ducked {
  opacity: 0.1;
}

/*
  One labelled cell. The divider is the cell's own left edge rather than an element between two of
  them, so a cell that is hidden takes its divider with it.
*/
.hud-group {
  position: relative;
  flex: none;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.421em;
  padding: 0 2.526em;
}

.hud-group + .hud-group::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.474em;
  bottom: 1.474em;
  width: 1px;
  background: rgba(238, 244, 240, 0.1);
}

/*
  The exhibition cell hides the three before it, so it is only ever the FIRST thing in the bar and a
  divider drawn against a hidden neighbour would be a rule with nothing on the other side of it.
*/
#watch-group::before {
  display: none;
}

/* The identity cell is a crest beside a two-line stack, and the only one allowed to shrink. */
.hud-identity {
  flex-direction: row;
  align-items: center;
  gap: 1.263em;
  flex-shrink: 1;
  padding: 0 2.105em;
}

.hud-stack {
  display: flex;
  flex-direction: column;
  gap: 0.316em;
  min-width: 0;
}

/*
  The design's own exhibition row is a line rather than a stack: two clubs, two crests, two arrows
  and a score, which have nothing to label each other with.
*/
#watch-group {
  flex-direction: row;
  align-items: center;
  gap: 0.85em;
}

/*
  `.hud-label` is the card's, at card sizes; `.hud-eyebrow` is the bar's, at the bar's own one font
  size. They wear the same treatment because they say the same kind of thing.
*/
.hud-label {
  font-size: 0.62rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--dim);
}

.hud-eyebrow {
  font-size: 1em;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
}

.hud-value {
  font-size: 2.632em;
  font-weight: 700;
  line-height: 1;
}

.hud-score .hud-value {
  color: var(--home);
}

/* The clock takes the amber the celebration's score, the contested marker and the order clock all
   wear — one amber on this pitch — filled from `palette.js` rather than written a second time. */
#match-clock {
  color: var(--celebration-score);
}

/*
  The scoreline. The leader's figure is the bright one and a nought leads nothing — the stat sheet's
  own rule, applied here because the design's dim side, taken literally, would dim a goal against,
  which is the half this cell exists to make visible.
*/
.hud-goals {
  display: flex;
  align-items: baseline;
  gap: 0.28em;
  color: var(--faint);
}

.hud-goals .lead {
  color: var(--ink);
}

.hud-goals-sep {
  font-size: 0.6em;
  font-weight: 400;
  color: var(--faint);
}

/*
  A scoreline and a clock do not wrap: an en dash is a break opportunity. The run's own scoreline is
  not here because it is a flex row of three parts now rather than one string with a dash in it, and
  a flex container does not wrap unless it is asked to.
*/
#watch-score,
#match-clock {
  white-space: nowrap;
}

/* The kit swatch beside a club in the exhibition HUD. Filled from `palette.js` at runtime. */
.kit {
  width: 0.9em;
  height: 0.9em;
  border-radius: 50%;
  flex: none;
}

/*
  The crest. The design's placeholder is a monogram in a ringed circle and its own note says a real
  badge slots straight in — so it does, and the circle goes with it: these are authored shields, and
  a circular crop would take their corners off.
*/
.hud-emblem {
  width: 2.2em;
  height: 2.2em;
  object-fit: contain;
  flex: none;
  align-self: center;
}

.hud-identity .hud-emblem {
  width: 3.789em;
  height: 3.789em;
}

/* Which way that club attacks. A second, non-colour cue for the same fact the swatch carries. */
.way {
  font-size: 1.3em;
  color: var(--dim);
}

/* `max-width` in `em` resolves against this element's own size, so the cap travels with the type. */
.hud-name {
  font-size: 1.684em;
  font-weight: 500;
  color: var(--ink);
  max-width: 10em;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/*
  How much of the match is left. The clock beside it says the number; this says the shape, which is
  what makes the last minute readable without reading anything. Both come off one `world.clock`.
*/
.hud-match-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0.316em;
  background: rgba(238, 244, 240, 0.1);
}

.hud-match-fill {
  display: block;
  height: 100%;
  background: var(--celebration-score);
}

/*
  The controls' own capsule, in the corner. `margin-left: auto` is what puts it there — the strip is
  full width and its two children are not, which is the whole point of the split.
*/
.hud-controls {
  /*
    Above the two covers that are the GAME's own screens, and below the three a player opened.

    Measured with `elementFromPoint` at 1424x805: the team sheet's card sat over the pause and the
    mute, and the pause cover sat over all three — so the mute had never been clickable except
    during live play, on a control this game's doc describes as being on every screen. (The bar exam
    read it as working because it dispatched `element.click()`, which skips hit testing entirely;
    that is as much the lesson as the fix.) `z-index: 1` lifts the capsule over `.summary` and
    `.paused`, which carry none; the board, How to play and the replay carry 2 and stay on top,
    because a sound control floating over a panel somebody asked for is furniture in front of the
    answer.
  */
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.842em;
  flex: none;
  margin-left: auto;
  padding: 0.6em;
  background: var(--panel);
  border-radius: 1.263em;
  box-shadow:
    0 1.474em 4.211em -1.895em rgba(0, 0, 0, 0.9),
    inset 0 0 0 1px rgba(238, 244, 240, 0.1);
}

/*
  The ten clubs. Named rather than numbered, because a bare 1-10 is a difficulty setting and this is
  a fixture list; auto-fit rather than a fixed count so the same markup is four across a card and two
  across a narrow one.
*/
.opponents {
  display: grid;
  gap: 0.3rem;
  /*
    The minimum is MEASURED, not chosen. "Kingsfall Wanderers" renders 140px bold at this chip's own
    font, and the chip's furniture — padding, border, rank, two gaps and a crest — is 82px, so a
    column narrower than 222px cannot spell the longest club and an ellipsis is what comes out
    instead. 14.1rem is that number with a little slack, which is what lets `auto-fit` be trusted: it
    can now only ever land on a count whose columns are wide enough to name a club.

    It was 8.5rem, from before the crests were added, and the extra 27px they cost per chip is
    exactly what turned "wide enough for the longest club" into `Kingsfa…` on every layout. Re-measure
    it after ANY change to the chip's type or crest: those three numbers and the card's width are one
    decision, and a test holds the arithmetic between them.
  */
  grid-template-columns: repeat(auto-fit, minmax(14.1rem, 1fr));
}

.opponent {
  pointer-events: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  text-align: left;
  border: 1px solid rgba(238, 244, 240, 0.14);
  background: rgba(238, 244, 240, 0.03);
  color: var(--dim);
  border-radius: 0.6rem;
  padding: 0.55rem 0.65rem;
  font: inherit;
  /*
    A club is the thing being chosen here, so it is set at reading size rather than at label size.
    The width this costs is bought from the card — see `.opponents` and `.card`, which are one
    decision with this line: raise it and the column minimum and the card grow with it.
  */
  font-size: 0.88rem;
  line-height: 1.2;
  cursor: pointer;
  transition:
    border-color 0.18s ease,
    background 0.18s ease,
    transform 0.18s ease;
}

.opponent:hover {
  border-color: rgba(55, 201, 255, 0.55);
  transform: translateY(-1px);
}

/* The rank IS the difficulty, so it stays on the chip however tight the layout gets. */
.opponent .rank {
  flex: none;
  min-width: 0.9rem;
  font-size: 0.85em;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

/* A crest is a picture and reads at picture size; kept in step with the club beside it so raising
   one without the other never quietly turns the chip into a badge with a caption. */
.opponent-emblem {
  width: 1.8rem;
  height: 1.8rem;
  object-fit: contain;
  flex: none;
}

.opponent[aria-pressed="true"] .rank {
  color: var(--home);
}

.opponent .club {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/*
  Your best match against this club, on a line of its own under the name — the number a rematch is
  trying to beat. A full-width flex item, so it wraps under the row rather than widening it, and the
  column minimum the chip-width test derives is untouched by it. Indented past the rank and the
  crest so it hangs under the club's own name.
*/
.opponent .best {
  flex-basis: 100%;
  margin-top: -0.25rem;
  padding-left: calc(0.9rem + 1.8rem + 1rem);
  font-size: 0.72em;
  font-weight: 500;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/*
  What that best is worth in zaps, beside it: the zapjam mark and a number in brackets, the same mark
  a board row wears. The MARK carries the unit, so nothing here spells it — the coin is the only
  thing on the chip that is not this game's own colour, which is what makes it read as the portal's
  currency rather than as a second football figure. `.board-zap-coin` sizes itself in `em`, so it
  follows `.best`'s 0.72em on its own and the pair can never fall out of step.

  PLAIN INLINE, deliberately. This was an inline-flex box with `align-items: center` and it sat 0.8px
  high against the score beside it: an inline-flex box takes its baseline from its FIRST flex item,
  that item is a masked span with no text in it, so the browser synthesises a baseline from the
  coin's box and the whole group — number included — is placed by the mark rather than by the text.
  Inline text already has the baseline the score is on; the coin joins it through `vertical-align`
  on the coin itself, which is how Hidden Flamingo places the same mark and is why that board never
  had this. So this rule sets spacing only, and the alignment lives on `.board-zap-coin`.
*/
.opponent .best-zaps .board-zap-coin {
  margin-right: 0.2em;
}

.opponent[aria-pressed="true"] .best {
  color: var(--dim);
}

.opponent[aria-pressed="true"] {
  border-color: var(--home);
  background: rgba(55, 201, 255, 0.14);
  color: var(--ink);
  font-weight: 700;
}

/*
  Pause and mute. Square-ish, ringed and filled rather than outlined, because at the design's size
  they are the only things on the bar a thumb has to find.
*/
.hud-btn {
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 4.421em;
  height: 4.421em;
  flex: none;
  border: 0;
  border-radius: 0.947em;
  background: rgba(238, 244, 240, 0.06);
  box-shadow: inset 0 0 0 1px rgba(238, 244, 240, 0.14);
  color: rgba(238, 244, 240, 0.82);
  padding: 0;
  font: inherit;
  cursor: pointer;
  transition: background 0.15s ease;
}

.hud-btn:hover:not(:disabled) {
  background: rgba(238, 244, 240, 0.14);
}

/* Pause acts on a running match; while the setup card is up it says so rather than just failing. */
.hud-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.hud-icon {
  width: 1.8em;
  height: 1.8em;
  fill: currentColor;
}

/*
  One glyph, two states, swapped off the `aria-pressed` the runtime already sets — so the picture
  cannot become a second answer to the state. Toggled by a RULE rather than by the `hidden`
  attribute, which on an SVG child would be a meaningless expando that hides nothing.
*/
.hud-icon-off {
  display: none;
}

.hud-btn[aria-pressed="true"] .hud-icon-on {
  display: none;
}

.hud-btn[aria-pressed="true"] .hud-icon-off {
  display: block;
}

/* The speaker is drawn in line rather than filled, so it needs the opposite paint. */
/*
  How loud, beside whether. Written in the capsule's own `em` like everything else in the strip, so
  a short landscape phone steps the one font size and this steps with it rather than needing a
  breakpoint of its own.

  `pointer-events: auto` is its own declaration rather than inherited: the strip is `none` so the
  pitch under it is still draggable, and the rule that turns it back on is scoped to `.hud-btn` —
  which this is not. Without it the slider is a picture.

  The fill is a background whose WIDTH is `--fill`, written by the runtime on every paint and every
  input, because the native `value` is invisible to CSS: left to a static background the fill would
  sit still while the knob travelled, which reads as a control that is not connected to anything.
*/
.hud-vol {
  pointer-events: auto;
  -webkit-appearance: none;
  appearance: none;
  flex: none;
  width: 7.6em;
  height: 0.95em;
  margin: 0;
  border: 0;
  border-radius: 0.5em;
  background-color: rgba(238, 244, 240, 0.14);
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-size: var(--fill, 100%) 100%;
  color: rgba(238, 244, 240, 0.82);
  cursor: pointer;
}

.hud-vol::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 1.55em;
  height: 1.55em;
  border-radius: 50%;
  border: 0;
  background: rgba(238, 244, 240, 0.92);
}

.hud-vol::-moz-range-thumb {
  width: 1.55em;
  height: 1.55em;
  border-radius: 50%;
  border: 0;
  background: rgba(238, 244, 240, 0.92);
}

/* A muted game still shows the level it will come back to, dimmed rather than gone — a control that
   vanished would make un-muting a guess about where it had been left. */
.hud-controls:has(#mute[aria-pressed="true"]) .hud-vol {
  opacity: 0.4;
}

.hud-icon--stroke {
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/*
  The line that says what just happened: the imported `Goal Celebration` design's pill.

  A capsule with a near-white ring on it, rather than the soft panel chip it was, because at a goal
  it now has to hold its own against a 62px number punching out of the pitch behind it. It drops in
  from above the frame on an overshoot and slides back up to leave — the design's own two keyframes,
  and the only two curves in this stylesheet that are not linear-ish.

  **Animations rather than a transition, and the resting state is the VISIBLE one.** A transition
  needs two rendered states to run between, and a backgrounded tab never starts one at all — so a
  banner whose visibility WAS the transition renders at nothing there, which would quietly take the
  line out of every screenshot an automated driver takes. Written as keyframes off a resting style
  that is simply the line in place, a tab that runs no animation shows exactly what it always did.

  `--banner-out` is set by `main.js` from `BANNER` in `sim/constants.js`, because the frame loop has
  to wait the exit out before it may put the element back to `hidden`, and a duration written in two
  places is a duration that drifts. The entrance's own numbers have one consumer — this file — so
  they are here with every other length in it. Unset, `animation` is invalid at computed-value time
  and the line simply appears and goes, which is the reduced-motion picture anyway.

  Every length below the font size is an `em`, so the pill is one decision at any type size — the
  rule the stat sheet is written to for the same reason.
*/
.banner {
  margin: 0;
  align-self: center;
  display: flex;
  align-items: center;
  gap: 0.55em;
  background: var(--panel);
  border: 2px solid rgba(238, 244, 240, 0.9);
  border-radius: 999px;
  box-shadow: 0 0.6em 2.1em -0.75em rgba(0, 0, 0, 0.8);
  padding: 0.67em 1.21em;
  font-size: 1.03rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  animation: banner-in 0.5s cubic-bezier(0.2, 1.5, 0.4, 1) both;
}

/*
  The beat before the bar arrives belongs to the CELEBRATION, not to the banner. It is there so the
  score punches at the ball first and the breakdown lands after it, so it is set on the same
  condition as the longer hold — a line raised over a world that a goal has stopped — rather than
  delaying every turnover message in the game by a quarter of a second.
*/
.banner--celebrating {
  animation-delay: 0.25s;
}

/*
  On its way out. `forwards` holds it off-screen for the few frames between the end of the exit and
  the runtime setting `hidden`, and swapping the animation NAME is also what restarts the entrance
  when a new banner interrupts an exit — from `hidden` the entrance restarts on its own, because an
  element that becomes rendered starts its animations again.
*/
.banner--leaving {
  animation: banner-out var(--banner-out) ease-in forwards;
}

@keyframes banner-in {
  from {
    opacity: 0;
    transform: translateY(-150%);
  }
}

@keyframes banner-out {
  to {
    opacity: 0;
    transform: translateY(-150%);
  }
}

/* The amber is the score's, the orange is the fireball's, and both are filled from `palette.js`. */
.banner-score {
  color: var(--celebration-score);
}

.banner-flame {
  color: var(--celebration-flame);
}

/* The dots between the parts. The banner draws them, so no line has to type its own. */
.banner-sep {
  color: var(--faint);
}

/*
  What the goal was worth, punched out at the ball that scored it and floating away.

  Positioned by the runtime from where the ball actually crossed the line, every frame, because the
  camera may still be travelling back out of a penalty while this is on screen. The two children
  centre themselves on that point with their own transforms, which is also what the float rides on.
*/
.goal-pop {
  position: absolute;
  pointer-events: none;
}

.goal-pop-ring {
  position: absolute;
  left: 0;
  top: 0;
  width: 7.5rem;
  height: 7.5rem;
  border: 4px solid var(--celebration-score);
  border-radius: 50%;
  opacity: 0;
  animation: goal-ring 0.55s ease-out both;
}

/*
  The number. A pixel keyline in eight directions rather than a stroke, because this game's whole
  art direction is authored pixels and a 1px vector outline reads as a caption over them; the soft
  shadow under it is what lifts it off the grass.
*/
.goal-pop-number {
  position: absolute;
  left: 0;
  top: 0;
  font-size: 3.85rem;
  font-weight: 700;
  line-height: 1;
  color: var(--celebration-score);
  font-variant-numeric: tabular-nums;
  text-shadow:
    3px 0 0 var(--celebration-keyline),
    -3px 0 0 var(--celebration-keyline),
    0 3px 0 var(--celebration-keyline),
    0 -3px 0 var(--celebration-keyline),
    2px 2px 0 var(--celebration-keyline),
    -2px 2px 0 var(--celebration-keyline),
    2px -2px 0 var(--celebration-keyline),
    -2px -2px 0 var(--celebration-keyline),
    0 6px 22px rgba(0, 0, 0, 0.6);
  transform: translate(-50%, -54%);
  animation: goal-pop var(--goal-pop) cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

/* Out of the ball, over the top, and away. The design's curve, beat for beat. */
@keyframes goal-pop {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.35) rotate(-4deg);
  }
  10% {
    opacity: 1;
    transform: translate(-50%, -52%) scale(var(--goal-pop-punch)) rotate(2deg);
  }
  20% {
    opacity: 1;
    transform: translate(-50%, -54%) scale(1) rotate(0deg);
  }
  62% {
    opacity: 1;
    transform: translate(-50%, -105%) scale(1);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -185%) scale(0.92);
  }
}

@keyframes goal-ring {
  from {
    opacity: 0.9;
    transform: translate(-50%, -50%) scale(0.2);
  }
  to {
    opacity: 0;
    transform: translate(-50%, -50%) scale(1);
  }
}

/*
  Asked not to move, nothing moves: the line simply arrives and stays for exactly as long, and the
  score stands at the ball rather than flying out of it. Both still LAST the same time — reduced
  motion is a request about movement, not about being given less time to read something.
*/
@media (prefers-reduced-motion: reduce) {
  .banner,
  .banner--leaving,
  .goal-pop-number {
    animation: none;
  }

  /* A ring whose whole content is an expanding scale has nothing left to say standing still. */
  .goal-pop-ring {
    display: none;
  }
}

/* The atlas failed to load: the one boot failure the card reports, in the warn colour. */
.boot-note {
  color: var(--warn);
}

.hint {
  margin: auto 0 0;
  align-self: center;
  background: var(--panel);
  border-radius: 0.5rem;
  padding: 0.3rem 0.75rem;
  font-size: 0.82rem;
  color: var(--dim);
}

/*
  The pause. Opaque on purpose, and it is the only thing in this stylesheet doing design work rather
  than layout: a see-through pause would be an unlimited decision window — stop the clock, read every
  projected trail, resume and release. Pause buys rest, never sight.
*/
.paused {
  position: absolute;
  inset: 0;
  pointer-events: auto;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.35rem;
  background: var(--surround);
  cursor: pointer;
  /*
    Centred while it FITS, and scrollable when it does not. `place-content: center` on its own
    overflows a too-tall cover equally off the top AND the bottom, and under this page's
    `overflow: hidden` neither end can be brought back — which is how a 6px overshoot at 844x390 cut
    the status word off the top and both controls off the bottom at once. `safe` falls back to
    `start` at exactly the moment centring would push something out of reach, so nothing can ever be
    above the top edge, and `overflow: auto` makes the rest reachable.

    This is the third time this cover has been measured over its screen — the sheet's font size was
    stepped 0.72rem -> 0.68 to buy the penalties row twelve pixels — and the point of doing it this
    way is that there is no fourth. A tenth row, a longer club name or a bigger system font now
    costs a scroll rather than a control.
  */
  align-content: safe center;
  overflow: auto;
}

/*
  Pitch markings behind the cover — halfway line, centre circle, the mown banding. They are a
  BACKDROP, not the position: nothing here moves, nothing here is a player, and at 5% of the ink
  they are a texture rather than a diagram. Drawn as two pseudo-elements so the cover stays three
  elements of markup, and kept away from the sheet itself, which also has to live inside a card.

  The alphas are the imported design's, without the 0.5 opacity it multiplied them by: at 2.5% of
  the ink over this green the line and the circle were a rounding error rather than a marking, and
  a decoration nobody can see is dead weight, not restraint.
*/
/*
  `fixed`, not `absolute`, because the cover scrolls now: a backdrop pinned to the padding box
  travels with the content, so the halfway line would slide up the screen as a player scrolled to
  the buttons. Fixed to the viewport it stays where a pitch marking belongs. Root `lessons.md`:
  backdrop layers in an overlay need `position: fixed` AND an explicit z-index, which `.paused > *`
  below already assumes.
*/
.paused::before,
.paused::after {
  content: "";
  position: fixed;
  z-index: 0;
  pointer-events: none;
}

.paused::before {
  inset: 0;
  background:
    linear-gradient(
      90deg,
      transparent calc(50% - 0.5px),
      rgba(238, 244, 240, 0.05) calc(50% - 0.5px) calc(50% + 0.5px),
      transparent calc(50% + 0.5px)
    ),
    repeating-linear-gradient(
      90deg,
      rgba(255, 255, 255, 0.012) 0 90px,
      rgba(0, 0, 0, 0.02) 90px 180px
    );
}

.paused::after {
  left: 50%;
  top: 50%;
  width: min(35rem, 78vmin);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border: 1px solid rgba(238, 244, 240, 0.05);
  border-radius: 50%;
}

/* The markings are positioned, so the cover's own content has to be positioned to stay above them. */
.paused > * {
  position: relative;
  z-index: 1;
}

/*
  The status line, in the broadcast treatment: a live dot and a widely tracked word. It is the only
  place either screen says which moment this is — the card is titled instead — so the sheet below
  never repeats it.
*/
.paused-word {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.8em;
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--dim);
}

.paused-word::before {
  content: "";
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: var(--live);
  animation: stat-pulse 2s ease-in-out infinite;
}

.paused-hint {
  margin: 0;
  font-size: 0.85rem;
  color: var(--dim);
}

.paused-hint kbd {
  font: inherit;
  font-size: 0.78em;
  letter-spacing: 0.08em;
  border: 1px solid rgba(238, 244, 240, 0.22);
  border-radius: 0.3rem;
  padding: 0.15em 0.5em;
  color: var(--ink);
}

/*
  The way out of the match. Quieter than the card's own buttons on purpose: resume is what the cover
  is for and what a tap anywhere already does, so this is the deliberate second choice rather than
  the loud one. It still has to be a real target on a phone, hence the padding rather than the HUD's
  furniture sizing.
*/
.paused-leave {
  margin-top: 1rem;
  border: 1px solid rgba(238, 244, 240, 0.25);
  border-radius: 0.45rem;
  background: transparent;
  color: var(--dim);
  font: inherit;
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  padding: 0.45rem 1.15rem;
  cursor: pointer;
}

.paused-leave:hover,
.paused-leave:focus-visible {
  color: var(--ink);
  border-color: rgba(238, 244, 240, 0.5);
}

/* The cover centres its content, so the sheet has to say how wide it wants to be — and it is the
   screen's whole subject here, which is what buys it the larger type. */
.paused .stats {
  width: min(52rem, 92vw);
  font-size: 0.95rem;
  margin: 1.1rem 0 1rem;
}

/*
  The match stat sheet, as a broadcast overlay. One block, two homes — the pause cover and the card a
  match ends on — so everything is written against the block rather than against either screen.

  ONE font size drives all of it. Every length below is in `em`, so the same markup is a full-screen
  overlay on the cover and half a card at full time, and the two can never disagree about anything
  except how big they are.
*/
.stats {
  width: 100%;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  display: grid;
  /*
    A floor, not decoration. An implicit `auto` column is sized to the largest min-content in the
    sheet and is free to grow PAST the box: two club names at `nowrap` gave the scoreline a 505px
    min-content inside a 345px column, and every row stretched to it and ran over the pickers
    beside them. `width: 100%` bounded this element and nothing inside it. The `0` is the whole
    point of the `minmax` — it is what lets the names reach the `min-width: 0` they already carry.
  */
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5em;
}

/*
  The scoreline. The slash beside each name is the kit swatch the sheet used to carry above the
  table — one mark doing both jobs, which is what lets the heads go entirely.
*/
.stat-score {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2.2em;
  animation: stat-rise 0.5s ease both;
}

/*
  The two sides take equal width and hang off the score from opposite directions, so the goals sit on
  the PANEL's centre rather than on the row's. A run names one side and not the other — "You" against
  a club and its crest — and centring the row alone put the score well left of the middle.
*/
.stat-team {
  display: flex;
  align-items: center;
  gap: 0.9em;
  min-width: 0;
  flex: 1 1 0;
}

.stat-team-home {
  justify-content: flex-end;
  text-align: right;
}

.stat-team-away {
  justify-content: flex-start;
  text-align: left;
}

.stat-team-name {
  font-size: 1.05em;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  /* Club names run to nineteen characters in an exhibition; the scoreline is not allowed to grow a
     second line for one of them. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* The crest beside the scoreline, at the size the scoreline is read at rather than at label size:
   on the two screens that report a match, who you played is the second thing after how it went. In
   `em` like everything else here, so the cover, the card and a short phone all step together. */
.stat-team-emblem {
  width: 3.4em;
  height: 3.4em;
  object-fit: contain;
  flex: none;
}

.stat-slash {
  flex: none;
  width: 0.3em;
  height: 2.6em;
  transform: skewX(-14deg);
}

.stat-goals {
  font-size: 4.6em;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
  /* A score does not wrap. The separator is an en dash, which is a break opportunity, so a
     scoreline squeezed by a pixel put the away goals on a line of their own under the home ones. */
  white-space: nowrap;
}

.stat-dash {
  padding: 0 0.14em;
  color: rgba(238, 244, 240, 0.24);
}

/*
  Possession, as the one bar that spans the panel. It is a share of a whole rather than a count,
  which is exactly what a row of two numbers cannot say, so it is the headline instead of the ninth
  row.
*/
.stat-hero {
  display: grid;
  gap: 0.7em;
  animation: stat-rise 0.5s ease 0.1s both;
}

.stat-hero-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1em;
}

.stat-hero-value {
  font-size: 1.9em;
  font-weight: 700;
}

.stat-hero-label {
  font-size: 0.88em;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--dim);
}

.stat-hero-bar {
  display: flex;
  gap: 0.35em;
  height: 0.85em;
  transform: skewX(-14deg);
}

.stat-hero-fill {
  flex: none;
  transform-origin: left;
  animation: stat-grow 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) 0.2s both;
}

/* The away half simply takes what the home half leaves, so the pair cannot round to a gap. */
.stat-hero-rest {
  flex: 1;
  transform-origin: right;
}

/* No ball has belonged to anybody yet: a track, not two halves. */
.stat-hero-empty {
  background: rgba(238, 244, 240, 0.1);
}

.stat-rows {
  display: grid;
  gap: 0.9em;
}

/*
  A row is a tug-of-war: both bars start at the label and grow outwards, so the picture is which way
  the row is going rather than two numbers with a divider under them.
*/
.stat-row {
  display: grid;
  grid-template-columns: 3.6em minmax(0, 1fr) minmax(0, 11em) minmax(0, 1fr) 3.6em;
  align-items: center;
  gap: 1em;
  /* `--order` is set per row by the builder; the whole sequence is timed here, in one place. */
  animation: stat-rise 0.45s ease calc(0.25s + var(--order) * 0.05s) both;
}

.stat-value {
  font-size: 1.3em;
  font-weight: 700;
  color: var(--faint);
}

.stat-value-away {
  text-align: right;
}

/* Whoever is ahead in this row is the number to read; a nought leads nothing. */
.stat-lead {
  color: var(--ink);
}

.stat-name {
  text-align: center;
  font-size: 0.92em;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--dim);
}

.stat-lane {
  display: flex;
  height: 0.6em;
  transform: skewX(-14deg);
}

.stat-lane-home {
  justify-content: flex-end;
}

.stat-lane-away {
  justify-content: flex-start;
}

/*
  The fill takes its colour from `palette.js` at runtime, so every accessibility colour change
  reaches the sheet instead of the sheet keeping a second copy of the kits.
*/
/*
  `--stat-delay` is where the sheet starts. The pause cover is the only thing on screen when it
  opens, so it keeps the default; the card a match ends on sets it later, so the three figures above
  the sheet count up first and the eye reads the screen top to bottom rather than all at once.
*/
.stat-fill {
  animation: stat-grow 0.6s cubic-bezier(0.2, 0.8, 0.2, 1)
    calc(var(--stat-delay, 0.25s) + var(--order) * 0.05s) both;
}

.stat-lane-home .stat-fill {
  transform-origin: right;
}

.stat-lane-away .stat-fill {
  transform-origin: left;
}

/* Nothing has happened on this side of the row, so it shows a track rather than a kit. */
.stat-fill-empty {
  background: rgba(238, 244, 240, 0.12);
}

.stat-extra {
  margin: 0;
  text-align: center;
  font-size: 0.95em;
  letter-spacing: 0.06em;
  color: var(--faint);
  animation: stat-rise 0.5s ease calc(var(--stat-delay, 0.25s) + 0.35s) both;
}

@keyframes stat-grow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

@keyframes stat-rise {
  from {
    opacity: 0;
    transform: translateY(0.6em);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes stat-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.35;
  }
}

/* The sheet on the card comes after the three figures above it; the pause cover's does not wait.
   1.5s is where the third cell's figure lands, so the bars start under a row that has settled. */
#summary-stats {
  --stat-delay: 1.5s;
}

/*
  The scrim does not take presses; the card on it does.

  It is painted over the whole stage, so while it was hit-testable it also covered the corner capsule
  — measured with `elementFromPoint`, a press on the mute or the volume while the team sheet was up
  landed on this element and nothing happened. The sound is the one thing a player may reasonably
  want to set BEFORE kicking off, so a capsule that is drawn there and inert there is worse than one
  that is not drawn at all. Nothing listens on the scrim itself, so letting presses through costs
  nothing and gives the two controls back on the screen the game starts on.
*/
.summary {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: grid;
  place-items: center;
  background: rgba(6, 17, 12, 0.78);
  padding: 1rem;
}

.card {
  pointer-events: auto;
  /*
    Wide enough for FIVE named clubs and all five shapes on one row. At 30rem the picker was four
    rows of chips and two of shapes, and the button that starts the run fell off the bottom; at 44rem
    it was four clubs across, and with a crest on each chip it could no longer spell them.

    Five is arithmetic rather than taste, and it is the arithmetic in `.opponents`: a column that can
    name a club is 14.1rem, so five of them plus four 0.3rem gaps want 71.7rem of content, and 75rem
    of card less 2.5rem of padding gives 72.5. Six would want 85.8rem, which is well clear. Ten clubs
    therefore read as the two rows of five the design draws them in, spelled out.

    The card is wide because the clubs are set at reading size. That is the trade, and it is the one
    worth making on a screen whose subject is which of ten teams you are about to play: below roughly
    a 1230px window the card is simply the window, and `auto-fit` steps down to four across rather
    than shrinking anybody's name.

    One width for both modes: the card a run ends on used to widen to this from 44rem, and a card
    that changed size between its two modes was only ever paying for the second column.
  */
  width: min(75rem, 100%);
  max-height: 100%;
  overflow: auto;
  background: var(--card);
  border: 1px solid rgba(238, 244, 240, 0.14);
  border-radius: 0.9rem;
  /* No bottom padding of its own, because `.card-foot` sticks to this scrollport's edge and any
     padding below that edge is a channel the content scrolls through in plain sight. The columns
     carry the breathing room instead, so whatever ends up last still clears the card's border. */
  padding: 1.1rem 1.25rem 0;
}

/*
  The masthead: which moment this is and what the game is called on one end, the standing personal
  best on the other. `flex-end` because the two sides are different shapes — a stack of up to three
  lines against a label over a number — and what has to agree is the bottom of each.
*/
/*
  The masthead starts BELOW the corner capsule, which is drawn over this card so the sound controls
  are reachable on every screen (`.hud-controls`).

  There is no clear corner for the capsule to sit in — it is 214px wide and this card is centred at
  `min(75rem, 100%)`, so it overhangs the card by 114px at 1424x805 and 210px at 390x844 — and the
  bite is vertical rather than horizontal: 27px, exactly the eyebrow line, measured at every viewport
  but the short landscape phone, where the strip's own font size makes it 13. So the row is pushed
  down by that much rather than squeezed sideways, and nothing in it is covered.
*/
.card-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
  padding-top: 1.7rem;
  animation: stat-rise 0.5s ease both;
}

.card-head-main {
  min-width: 0;
}

/*
  The status eyebrow, in the pause cover's own treatment — a live dot and a widely tracked word — so
  the game's two covers say "which moment is this" in one voice. `Team sheet`, `Full time`, `Run
  over`; the title under it is the game's name in every mode, which is what keeps the moment said
  exactly once per screen. The dot is the home kit rather than the cover's amber: this screen is the
  player's own team sheet, and the cover is a stopped match.
*/
.moment {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.8em;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--dim);
}

.moment::before {
  content: "";
  width: 0.5em;
  height: 0.5em;
  border-radius: 50%;
  background: var(--home);
  animation: stat-pulse 2s ease-in-out infinite;
}

/*
  The standing personal best. A fact about the PLAYER rather than about this run, so it reads the
  same on the screen the game starts on and the screen a run ends on — which is what leaves the line
  in the left column free to say what this run just did instead of printing the number twice.

  The rail is the same 14° mark the stat sheet's scoreline already leans at, and the block is hidden
  entirely until there is a best: a dash under the words is a label with nothing to label.
*/
.best-block {
  position: relative;
  flex: none;
  padding-left: 0.95rem;
  text-align: right;
}

.best-block::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.15rem;
  bottom: 0.15rem;
  width: 0.22rem;
  background: var(--live);
  transform: skewX(-14deg);
}

.best-label {
  display: block;
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--dim);
}

.best-value {
  display: block;
  margin-top: 0.35rem;
  font-size: 1.9rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.best-note {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

/*
  The hairline under the masthead. It starts at the home kit and fades out across the card, which is
  the direction the player attacks on the pitch showing behind it. `opacity` on the whole rule rather
  than an alpha baked into the stop, so the kit is not written down a second time here.
*/
.card-rule {
  height: 1px;
  margin-top: 0.9rem;
  opacity: 0.55;
  background: linear-gradient(90deg, var(--home), rgba(238, 244, 240, 0.16) 45%, transparent);
  animation: stat-rise 0.5s ease 0.04s both;
}

/* Half a card rather than a whole screen, so the one size the sheet is written against steps down
   and every length in it follows. */
.card .stats {
  font-size: 0.7rem;
  margin-top: 0.9rem;
}

/*
  One column on the screen the game starts on, two at full time.

  The card is tuned to exactly fill a 390px-tall phone in landscape, and a nine-row stat sheet does
  not fit under the pickers at any type size. Beside them it does: what the match WAS sits in one
  column and what the next one will be sits in the other, so the sheet costs the ten clubs, the five
  shapes and the button that starts the next run no height at all.

  Two columns need room for BOTH halves, and that is two different situations at two different
  widths. On a desktop the pickers are at full size and want about 37rem of their own for five
  shapes in a row, which the card's 66rem has. On a short landscape phone the pickers have already
  shrunk to fit that screen — names off, crests off, diagrams down — so the same card is wide enough
  as it stands. In between, a narrow tall window, one column and a scroll is the honest answer
  rather than two cramped ones. The card no longer changes width between its modes: the width it
  used to grow to for the second column is the width naming five clubs already needs.
*/
.card-columns {
  display: grid;
  gap: 0.25rem 1.5rem;
  padding-bottom: 1.25rem;
}

/* A two-step entrance rather than the design's five. The card is rebuilt every time a club or a
   shape is picked, and a cascade of five that replayed on every click would be the opposite of what
   a one-time fade-up is for. */
.card-main {
  animation: stat-rise 0.5s ease 0.08s both;
}

.card-side {
  animation: stat-rise 0.5s ease 0.14s both;
}

@media (min-width: 62rem), (max-height: 470px) and (min-width: 46rem) {
  /* The pickers take the larger share: theirs is the half with a hard minimum — five shapes across
     — while the sheet's rows simply set narrower. */
  .card--result .card-columns {
    grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
    align-items: start;
  }

  /* The pickers head the right-hand column, so their first heading must not push it down past the
     score the left-hand one opens with. */
  .card--result .card-side > .section-head:first-child {
    margin-top: 0.2rem;
  }

  /*
    There are five shapes, so the row asks for five columns rather than for a minimum width and the
    hope that auto-fit lands on five. In half a card it did not: the fifth wrapped onto a row of its
    own and took the collapsed exhibition line off the bottom with it. A count is what this row
    actually is; a minimum width was a guess about how wide the card would be.
  */
  .card--result .formations {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }

  /*
    In HALF a card the scoreline is a vertical scorebug: home club, the score, away club. One line
    needs about 45em for two club names either side of the goals and this column is about 31em, so
    the one-line form has to ellipsise both clubs down to `BRACKE…` and `KINGSFA…` — which names
    nobody, on the one screen whose whole job is saying who played. Dropping the tracking and
    shrinking the goals was measured and still left it short; the width simply is not there.

    Only the card, and only in two columns. The pause cover is 52rem wide and keeps the one-line
    scoreline it was designed as, and a one-column card has the full width for it too.
  */
  .card--result .stat-score {
    flex-direction: column;
    gap: 0.15em;
  }

  /* Stacked, the two sides are rows rather than halves of one, so the equal-width rule that centres
     the score in the line form would only stretch them down the column. */
  .card--result .stat-team {
    flex: none;
    max-width: 100%;
  }
}

.card h1 {
  margin: 0.5rem 0 0;
  font-size: 2rem;
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.02em;
}

/*
  A section label with its caption on the other end of the same line, so the caption costs the card
  no height at all. The label is `flex: none` and the caption is the one allowed to shrink: in a
  narrow picker column something has to give, and it is not the word that names the section.
*/
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin: 1.1rem 0 0.5rem;
}

.card h2 {
  margin: 0;
  flex: none;
  font-size: 0.68rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--dim);
}

.section-note {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 0.72rem;
  color: var(--faint);
}

/*
  The full-time card's three figures, from the imported `Full Time Card` canvas' variant 1B:
  labelled cells in the status strip's own treatment — the same panel, the same 1px inset ring, the
  same hairline between cells — because they are the same kind of reading and a second treatment for
  them would be a second design in one game.

  **Written against ONE font size, everything below it an `em`**, which is the rule the strip, the
  stat sheet and the goal banner are all already written to. This row lives in half a 75rem card at
  desktop and in half an 844px phone in landscape, so a screen too narrow for it steps that one
  number rather than dropping a cell. The design's own pixels divided by its 10.5px label are the
  ratios: its 52px score is 4.95em, its 30px figures 2.857em, its 13px notes 1.238em, its 26px play
  ring 2.476em and its 12px radius 1.143em.

  Two of those ratios are DELIBERATELY not the canvas'. It draws this block 880px wide and it lives
  here in half a card — 434px at desktop, 290px on a short landscape phone — so holding both its
  proportions and a readable label is not available: at the canvas' own scale the label would be
  5.2px. The figures give instead (the score 4.95em → 3.4em, the rest 2.857em → 2.05em) and the
  label keeps the register the strip's eyebrow reads at, because a label nobody can read is not a
  labelled cell. Measured at the widest content this game can produce — a five-figure score, a
  five-figure gap and the club best beside it — 0.6rem holds all three cells on ONE row in 434px
  and 0.4rem in 290px, which is where those two numbers come from.
*/
.result-cells {
  position: relative;
  /* Load-bearing, and not merely for the radius: the divider below is drawn one pixel OUTSIDE each
     cell's own left edge, so the one at the start of a line falls outside the panel and is clipped.
     That is what makes a cell that is hidden — an exhibition has no standing, a 0-0 no goal to
     replay — take its rule with it, and what lets the row wrap on a narrow card without a stray
     tick appearing down the left of the second line. */
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  margin-top: 0.2rem;
  border-radius: 1.143em;
  background: var(--panel);
  box-shadow: inset 0 0 0 1px rgba(238, 244, 240, 0.1);
  font-size: 0.6rem;
  font-variant-numeric: tabular-nums;
}

.result-cell {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 0.6em;
  padding: 1.2em 1.3em;
  /* A cell that is a control is still a cell: no border, no background, no button metrics of its
     own — what says it can be pressed is the ring and the word inside it. */
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
}

.result-cell::before {
  content: "";
  position: absolute;
  left: -1px;
  top: 1.1em;
  bottom: 1.1em;
  width: 1px;
  background: rgba(238, 244, 240, 0.1);
}

.result-cell--action {
  pointer-events: auto;
  cursor: pointer;
  transition: background 0.18s ease;
}

.result-cell--action:hover,
.result-cell--action:focus-visible {
  background: rgba(55, 201, 255, 0.07);
}

/*
  Full time's entrance: the cells arrive ONE AFTER ANOTHER in the stat sheet's own gait, and the
  sheet under them follows (see `--stat-delay` on the card's container). Ordered by position rather
  than by a property the runtime sets, because a hidden cell leaving its place in the count costs
  nothing anybody can see.

  450ms apart, not the 90ms this shipped with. Ninety was authored as "one after another" and is not
  read as one — three cells 90ms apart arrive as a block, and the whole row was settled inside 1.5s
  while the eye was still on the whistle. Each cell's figure starts climbing as its own cell lands
  (`CARD_COUNT.stagger` in `main.js` is the same 0.45), so a section arriving and its number moving
  are one event rather than two.

  It restarts on every full time rather than once per page: `#result` is hidden on the team sheet,
  so the cells are re-displayed with each match, which is what starts an animation again.
*/
.result-cell {
  animation: stat-rise 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) calc(var(--order, 0) * 0.45s) both;
}

.result-cell:nth-child(2) {
  --order: 1;
}

.result-cell:nth-child(3) {
  --order: 2;
}

/* The strip's own eyebrow, at the card's size rather than the bar's. */
.result-label {
  font-size: 1em;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
}

/* A figure and whatever sits beside it — the play ring and its word, or the number the gap is
   measured from. */
.result-line {
  display: flex;
  align-items: center;
  gap: 0.75em;
  min-width: 0;
}

/* A score does not wrap, and an en dash — the exhibition's scoreline — is a break opportunity. */
.result-figure {
  font-size: 2.05em;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
  white-space: nowrap;
}

/* The one the card exists to show, so it is the only figure on it at the score's own size and in
   the home kit's own colour. */
.result-figure--score {
  font-size: 3.4em;
  color: var(--home);
}

/* The goal of the match is the amber the celebration's number, the contested marker and the order
   clock already share, so the figure and the goal it is about are one colour. */
.result-figure--gold {
  color: var(--live);
}

/* Where the news is, on a card whose figures are cells: the margin a new best went past its own
   old number by. The sentence under the cells used to carry this green, and it can no longer be a
   new best — that state is a figure now. */
.result-figure--good {
  color: var(--good);
}

.result-figure:empty {
  display: none;
}

.result-note {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 1.15em;
  color: var(--dim);
}

/*
  Drawn rather than typed, for the reason the pause and mute controls are: `▶` is a different
  picture on every platform and on some of them a colour photograph, over a card whose whole art
  direction is authored pixels. A triangle is three borders and a ring is a radius.
*/
.result-play {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.2em;
  height: 2.2em;
  border: 1px solid rgba(55, 201, 255, 0.5);
  border-radius: 50%;
}

.result-play::before {
  content: "";
  margin-left: 0.2em;
  border-left: 0.68em solid var(--home);
  border-top: 0.43em solid transparent;
  border-bottom: 0.43em solid transparent;
}

.detail,
.best {
  margin: 0.5rem 0 0;
  font-size: 0.88rem;
  color: var(--dim);
}

/*
  The club you picked, at the size a crest is worth looking at rather than recognising. The chips
  carry the ladder; this line carries the one choice that has been made, and it stays the only place
  the club is named once a short phone has taken the names off the chips.
*/
.pick-line {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-top: 0.7rem;
}

/* The one crest on the card drawn to be looked at rather than recognised, so it is the largest club
   art on the screen by a clear margin — the chips are a list, this is the pick. */
.pick-emblem {
  width: 3.4rem;
  height: 3.4rem;
  object-fit: contain;
  flex: none;
}

.pick-line .detail {
  margin: 0;
  min-width: 0;
  font-size: 1rem;
}

.formations {
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
}

.formation {
  pointer-events: auto;
  text-align: left;
  border: 1px solid rgba(238, 244, 240, 0.14);
  background: rgba(238, 244, 240, 0.03);
  color: var(--ink);
  border-radius: 0.7rem;
  padding: 0.6rem;
  font: inherit;
  cursor: pointer;
  transition:
    border-color 0.18s ease,
    background 0.18s ease,
    transform 0.18s ease;
}

.formation:hover {
  border-color: rgba(55, 201, 255, 0.5);
  transform: translateY(-2px);
}

.formation[aria-pressed="true"] {
  border-color: var(--home);
  background: rgba(55, 201, 255, 0.12);
}

/*
  The diagram is drawn from the same slots the simulation uses — see `formation-art.js`.

  The four you did not pick are held back rather than hidden: five pitches at equal strength is a
  list, and one of these is a choice that has already been made.
*/
.formation .shape {
  display: block;
  width: 100%;
  height: auto;
  margin-bottom: 0.45rem;
  border: 1px solid rgba(238, 244, 240, 0.1);
  border-radius: 0.35rem;
  opacity: 0.62;
}

.formation[aria-pressed="true"] .shape {
  opacity: 1;
}

.formation strong {
  display: block;
  font-size: 1rem;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
}

.formation span {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.74rem;
  line-height: 1.4;
  color: var(--dim);
}

/*
  The exhibition picker. Collapsed by default: the card already exactly fills a 390px-tall phone, and
  the score is the one line it exists to show, so a sideshow pays for its own space only when asked.
*/
.watch {
  margin-top: 1rem;
  border-top: 1px solid rgba(238, 244, 240, 0.14);
  padding-top: 0.6rem;
}

/* The disclosure's own caret, because the UA marker cannot be made to lean or rotate on a clock we
   choose. `list-style: none` covers Firefox and the `::-webkit-details-marker` rule covers Safari. */
.watch > summary {
  pointer-events: auto;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: var(--dim);
  list-style: none;
}

.watch > summary::-webkit-details-marker {
  display: none;
}

.watch > summary::before {
  content: "▶";
  flex: none;
  font-size: 0.62em;
  color: var(--home);
  transition: transform 0.2s ease;
}

.watch[open] > summary::before {
  transform: rotate(90deg);
}

.watch > summary:hover {
  color: var(--ink);
}

/*
  Two sides of a fixture, laid out side by side, because that is the shape of the thing being picked.

  The minimum is two of the picker's own columns plus their gap, so the two sides split exactly when
  each half can still hold two named clubs and stack the moment it cannot — which is what happens in
  the narrow picker column of the full-time card. Deriving it from the chip's width rather than from
  a viewport breakpoint is what makes it right in both homes.
*/
.watch-sides {
  display: grid;
  gap: 0.7rem 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(28.5rem, 1fr));
}

.watch-label {
  margin: 0.6rem 0 0.4rem;
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.85rem;
}

/* Each side wears the kit it will play in. Filled from `palette.js` in `main.js`, never from a
   colour written down a second time here. */
.watch-rail {
  flex: none;
  width: 0.2rem;
  height: 0.95rem;
  transform: skewX(-14deg);
}

/*
  Everything that starts something on this card leans 14° — the angle the stat sheet's bars and
  slashes already lean at. The lean is on a pseudo-element rather than on the button, so the label
  stays upright without the markup growing an inner span the runtime would have to write through.
*/
.primary-btn {
  pointer-events: auto;
  position: relative;
  isolation: isolate;
  margin-top: 1rem;
  width: 100%;
  border: none;
  background: transparent;
  padding: 0.75rem;
  color: #06232f;
  font: inherit;
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  cursor: pointer;
}

.primary-btn::before {
  content: "";
  position: absolute;
  /* Inset horizontally by the overhang the skew adds, so the parallelogram stays inside the card. */
  inset: 0 0.45rem;
  z-index: -1;
  border-radius: 0.25rem;
  background: var(--home);
  transform: skewX(-14deg);
  transition: transform 0.18s ease;
}

.primary-btn:hover::before {
  transform: skewX(-14deg) translateY(-2px);
}

/* The exhibition's own button, after `.primary-btn` so it can quiet it down: two solid buttons
   stacked read as two equal choices, and watching is the sideshow. */
.ghost-primary {
  color: var(--home);
  font-size: 0.92rem;
}

.ghost-primary::before {
  background: transparent;
  border: 1px solid var(--home);
}

/*
  What Kick off is about to start. Neither picker says the pair — one names a club and the other a
  shape — so this is the line the fixture is read back on.
*/
.ready {
  margin: 0.55rem 0 0;
  text-align: center;
  font-size: 0.76rem;
  color: var(--faint);
}

/*
  A short landscape phone. The summary card gained an opponent picker and now overflows a 390px
  viewport, which pushes the score — the one line the card exists to show — above the fold. Tighten
  it rather than let it scroll.
*/
@media (max-height: 470px) {
  /*
    The status strip is one font size and everything in it is an `em`, so a screen this short
    changes that number and keeps every cell. At the design's own scale the capsule is 65px of a
    390px viewport; here it is 46px, and the crest, the figures, the controls and the time bar all
    step with it.
  */
  .hud-top {
    font-size: 0.43rem;
  }

  .card {
    padding: 0.7rem 0.9rem 0.9rem;
  }

  /*
    The moment goes BESIDE the game rather than over it. Two stacked header lines is a line this
    screen does not have, and the eyebrow and the title are short enough to share one.
  */
  .card-head-main {
    display: flex;
    align-items: baseline;
    gap: 0.7rem;
  }

  .card-head {
    align-items: center;
    gap: 0.9rem;
  }

  .moment {
    font-size: 0.56rem;
    letter-spacing: 0.28em;
  }

  .card h1 {
    margin: 0;
    font-size: 1.05rem;
  }

  /*
    The masthead becomes ONE line: moment, game, and the best on the end of it. The stacked form is
    26px of a screen the card already fills exactly, and every one of those pixels comes off the
    bottom of the stat sheet at full time.
  */
  .best-block {
    display: flex;
    align-items: baseline;
    gap: 0.45rem;
  }

  .best-label {
    font-size: 0.52rem;
  }

  .best-value {
    margin-top: 0;
    font-size: 1.1rem;
  }

  /* A hairline is decoration, and this is the one screen with no room for any. */
  .card-rule {
    display: none;
  }

  .section-head {
    margin: 0.6rem 0 0.35rem;
  }

  /* The caption is the design's flourish on a label that already says the thing, so it is the first
     thing to go when the height runs out. */
  .section-note {
    display: none;
  }

  /*
    The chips are ranks alone here and their crests are already off, so this crest would be the only
    club art left on the card — beside the one line whose job is the NAME.
  */
  .pick-emblem {
    display: none;
  }

  .pick-line {
    margin-top: 0.3rem;
  }

  /* Beats `.pick-line .detail` on specificity or the one club named here would be the largest type
     on a screen that has already given up its own hook to fit. */
  .pick-line .detail {
    font-size: 0.8rem;
  }

  /*
    The club is named by the style line and the shape by the card that is pressed, so the ready line
    would be a second place for both — on the one screen that has no room for a second place.
  */
  .ready {
    display: none;
  }

  /*
    The cell row is one font size and everything in it is an `em`, so a screen this short changes
    that number and keeps every cell — the strip's own rule, on the strip's own treatment. At the
    canvas' scale the label is 10.5px; here it is 6.4px, and the score, the figures, the notes, the
    play ring and the padding all step with it. Measured at this game's widest content, that is
    what holds three cells on one row in the 290px this column has at 844 x 390.
  */
  .result-cells {
    font-size: 0.4rem;
  }

  /*
    Three of the canvas' own measurements give here, and each is bought for width rather than for
    height: the cells sit closer to their own edges, the labels keep less air between their letters,
    and the standing cell puts the number it is measured against UNDER the figure rather than beside
    it. Measured at every reading this game can produce — a five-figure score, a five-figure gap and
    the club best with it — that is what holds three cells on ONE row in the 290px this column has
    at 844 x 390, where the row is 49px against the 53px of the block it replaced.
  */
  .result-cell {
    padding: 1.2em 0.9em;
  }

  .result-label {
    letter-spacing: 0.1em;
  }

  #standing .result-line {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.15em;
  }

  .detail,
  .best {
    margin: 0.3rem 0 0;
    font-size: 0.8rem;
  }

  /* The status line is beside a card that has already given up its captions, its crest and its
     hairline; the dot is the smallest thing on it and steps with the words. */
  .rank-status {
    margin: 0.25rem 0 0.4rem;
    font-size: 0.8rem;
  }

  .rank-status::before {
    width: 0.36rem;
    height: 0.36rem;
  }

  /*
    Five shapes and ten opponent chips do not fit a 390px-tall phone at full size, and the control
    that starts the next run must not be below the fold. The diagram survives the squeeze and the
    blurb does not: the picture is the part that cannot be said in the label.
  */
  .formations {
    gap: 0.35rem;
    /* Narrow enough that all five sit on ONE row: a second row costs more height than the pause. */
    grid-template-columns: repeat(auto-fit, minmax(5rem, 1fr));
  }

  /*
    Ten named chips are four rows where numbers were one, and there is no font size at which
    "Kingsfall Wanderers" and a score and five shapes and the button all fit a 390px-tall phone.
    The chips fall back to the ranks alone — the ladder still readable at a glance — and the club is
    named by the style line directly underneath, which is why that line keeps the name on it.
  */
  .opponents {
    gap: 0.25rem;
    grid-template-columns: repeat(auto-fit, minmax(2.2rem, 1fr));
  }

  /*
    Reading size is bought with width, and here there is none: what is left on the chip is a rank,
    which needs a fraction of it.

    HEIGHT is the other axis and it is bought differently. Stripped to a rank the chip came out at
    75 x 23 px — the smallest button in the game, on the only screen a phone player starts on,
    choosing the one thing that most changes the match — and the bar exam measured it three attempts
    running. 2.75rem is 44px, which is the size a thumb is actually aimed at; the rank centres in it
    on both axes, so what grows is the target rather than the type.

    What it costs is scroll, and that is only affordable because `.card-foot` is sticky: measured at
    844x390 this card already runs 46px past the viewport with Kick off pinned, so the row growing
    once takes it to about 67 and the control the screen exists to reach does not move. Before the
    footer was pinned this was 21px that would have come off the bottom of the card, which is why
    the note in `_doc.md` about this screen being tuned to fill a 390px phone EXACTLY is the reading
    that predates it.
  */
  .opponent {
    justify-content: center;
    min-height: 2.75rem;
    padding: 0.25rem 0.2rem;
    font-size: 0.8rem;
  }

  .opponent .club {
    display: none;
  }

  .opponent .best {
    display: none;
  }

  /* The narrow fallback keeps the documented rank-only ladder readable on a small phone. */
  .opponent-emblem {
    display: none;
  }

  .formation {
    padding: 0.4rem 0.45rem;
  }

  .formation .shape {
    width: auto;
    height: 2.1rem;
    margin-bottom: 0.25rem;
  }

  .formation span {
    display: none;
  }

  .primary-btn {
    margin-top: 0.6rem;
    padding: 0.5rem;
  }

  /* The collapsed exhibition line has to FIT, or the last thing on the card reads as clipped. */
  .card {
    padding-bottom: 0.4rem;
  }

  .watch {
    margin-top: 0.5rem;
    padding-top: 0.4rem;
  }

  /* The hook reads on a desktop start card; on a short phone the pickers are worth more. */
  #intro {
    display: none;
  }

  /*
    Every row stays. The sheet is written against one size, so a short landscape phone changes that
    size and the scoreline, the possession bar, the eight rows and their bars all step down together
    — there is no row here that is a second reading of another one and could be dropped instead.
  */
  /*
    ONE number again, and it moved for a measured reason.

    Measured with the entrance animation disabled — a hidden automated tab freezes it at a 0.6em
    translate that reads as permanent overflow — the full-time card at 844 x 390 was 23px over
    BEFORE this screen was redesigned: an exhibition's sheet under an exhibition's two long club
    names, with the saves row scrolled off the bottom. The one-line masthead here costs three more.

    Every length in the sheet is expressed against this font size, so the size is what moves, and
    18px of the 26 come from it alone. The giant result number above it is untouched at 1.9rem: put
    back to 1.9 on its own, the card still measures 0 over, so shrinking it would have been a change
    that bought nothing.
  */
  .card .stats {
    font-size: 0.5rem;
    gap: 0.6em;
    margin-top: 0.35rem;
  }

  .card .stat-goals {
    font-size: 2.9em;
  }

  .card .stat-slash {
    height: 2.1em;
  }

  .card .stat-hero {
    gap: 0.5em;
  }

  .card .stat-rows {
    gap: 0.42em;
  }

  /* The label is what a narrow column runs out of room for first, and tracking is the cheapest part
     of it to give back. */
  .card .stat-name {
    letter-spacing: 0.06em;
  }

  /*
    Here — and only here — a crest costs height twice over: this is the one screen whose scoreline is
    STACKED, so each club's crest is a row rather than something standing beside a 4.6em score. At
    3.4em the pair put the card 10px over and scrolled the saves row off again. The pause cover on
    the same phone keeps the full size, because its scoreline is still a line.
  */
  .card .stat-team-emblem {
    width: 2.4em;
    height: 2.4em;
  }

  /*
    The capsule is drawn at this screen's own strip size and the masthead is one line here rather
    than two, so it reaches 19px into it rather than 27 — measured, and worth having back on the
    screen with the least height to spare.
  */
  .card-head {
    padding-top: 1.2rem;
  }

  .paused-word {
    font-size: 0.78rem;
  }

  /*
    ONE number, which is the whole reason the sheet is written in `em`.

    A short landscape phone is the screen this cover was tuned to exactly fill, so a row added to
    the sheet is a row bought with height that is not there: measured at 844 x 390, the penalties
    row put the cover 12px over and made it scroll, taking the button at the bottom off the screen.
    Dropping a row would be answering a layout problem with an editorial one — there is no row here
    that is a second reading of another — so the font size moves instead, which is what every length
    below it is expressed against.
  */
  .paused .stats {
    font-size: 0.68rem;
    gap: 0.85em;
    margin: 0.25rem 0 0.15rem;
  }

  .paused .stat-goals {
    font-size: 3.2em;
  }

  .paused .stat-rows {
    gap: 0.6em;
  }

  .paused-hint {
    font-size: 0.78rem;
  }

  .paused-leave {
    margin-top: 0.3rem;
    font-size: 0.72rem;
    padding: 0.35rem 0.9rem;
  }

  /*
    Two controls on that row now instead of one, and the cover was already tuned to fill this
    screen exactly: measured at 844 x 390, the row's bottom sat 3px (DPR 1) to 10px (DPR 2) below
    the viewport, which clipped both buttons. The gaps above it give the pixels back — nothing on
    the cover is dropped, and the stat sheet keeps its one font size.

    These gaps are still worth having, because a cover that FITS needs no scroll at all. They are no
    longer the thing standing between a player and the controls: the cover scrolls and pins its own
    action row now (see `.paused` and `.paused-actions`), so the next row to arrive costs a
    scrollbar rather than a button.
  */
  .paused {
    gap: 0.2rem;
  }

  .paused-actions {
    gap: 0.45rem;
  }
}

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

  Two surfaces, and they are deliberately different weights. The RANK block lives in the card and is
  the board's foot there: a status line, the pitch when there is one to make, and the button through
  to the board itself. The BOARD PANEL is its own cover over the pitch, in the pause cover's
  treatment, because the card is tuned to exactly fill a 390px-tall phone and ten ranked rows do not
  fit in it at any type size.

  The row markup is the shared one every ZapJam board renders (`li.board-row`, `.viewer`, and the
  cells by class), so a board is the same THING in every game while the colours here stay Through
  Ball's own.
*/
/*
  The card's board foot, addressed by its ID. `rank` as a bare CLASS is spelled by two other things
  on these screens — the shared board row's first cell (`.board-row .rank`, whose name this game
  does not own) and the club chip's difficulty digit — and a bare `.rank` rule here duly pushed both
  of them 12px down inside their own centred rows.
*/
#rank {
  margin-top: 0.75rem;
}

.rank-status {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  margin: 0.35rem 0 0.55rem;
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--home);
}

/*
  The canvas' dot beside the sentence. It takes the sentence's OWN colour rather than the canvas'
  fixed amber: this line is already tone-coded — the home kit for a plain verification, `--good` for
  a ranked match, `--warn` for a refusal — and a dot pinned to one colour beside a sentence that
  changes colour would be a second, wrong answer to the same question.
*/
.rank-status::before {
  content: "";
  flex: none;
  align-self: center;
  width: 0.44rem;
  height: 0.44rem;
  border-radius: 50%;
  background: currentColor;
}

/* One tone left: the line speaks only when something went wrong — a match the board refused, a
   board that could not be reached, a host that cannot sign anybody in. The states that reported
   success were saying what the cells above them already say. */
.rank-status.warn { color: var(--warn); }

/*
  The controls and, on the far end of the same line, the handle the match would be ranked under.

  The auto margin is on the item that must stay RIGHT rather than on the one before it: pushed from
  the left, the handle sits right only while the two share a line, and the moment a narrow card
  wraps it onto its own row it would land back on the left with nothing pushing it.
*/
.rank-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem 1rem;
}

/* Who a match would be ranked as. Quiet: it is a reassurance, not a headline. */
.rank-who {
  margin: 0;
  margin-left: auto;
  font-size: 0.8rem;
  color: var(--dim);
}

.rank-who b {
  color: var(--ink);
}

/* The pitch, shown only to a signed-out player who has just finished a match worth claiming. */
.rank-pitch {
  margin: 0 0 0.6rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid rgba(238, 244, 240, 0.14);
  border-radius: 0.6rem;
  background: rgba(238, 244, 240, 0.04);
}

.rank-headline {
  margin: 0;
  font-size: 0.92rem;
  font-weight: 800;
}

.rank-benefits {
  margin: 0.5rem 0 0;
  padding-left: 1.1rem;
  display: grid;
  gap: 0.25rem;
  font-size: 0.78rem;
  color: var(--dim);
}

.rank-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.7rem;
}

.rank-foot {
  margin: 0.55rem 0 0;
  font-size: 0.72rem;
  color: var(--faint);
}

.rank-btn {
  pointer-events: auto;
  border: 1px solid rgba(238, 244, 240, 0.22);
  border-radius: 0.35rem;
  background: transparent;
  padding: 0.42rem 0.75rem;
  color: var(--ink);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease;
}

.rank-btn:hover,
.rank-btn:focus-visible {
  border-color: var(--home);
  background: rgba(55, 201, 255, 0.12);
}

/* The one that is the offer, so the pair does not read as two equal choices. */
.rank-btn--go {
  border-color: var(--home);
  color: #06232f;
  background: var(--home);
}

.rank-btn--go:hover,
.rank-btn--go:focus-visible {
  background: #5ed6ff;
}

/* Full width of its column, but a button rather than a bar: on the screen the game starts on the
   card is ONE column, and 75rem of Leaderboard reads as a banner. */
/* An inline text control inside a sentence — the way out of an account, which is a quiet action
   that belongs beside the handle rather than as a third button under it. */
.rank-link {
  pointer-events: auto;
  margin-left: 0.5rem;
  border: none;
  border-bottom: 1px solid rgba(238, 244, 240, 0.35);
  border-radius: 0;
  background: none;
  padding: 0;
  color: var(--dim);
  font: inherit;
  font-size: inherit;
  cursor: pointer;
}

.rank-link:hover,
.rank-link:focus-visible {
  color: var(--ink);
  border-bottom-color: var(--home);
}

/*
  The sign-in INSIDE the guest's friends line, drawn there by the shared client.

  `.rank-link` is built for a control that FOLLOWS text — hence its left margin — and this one opens
  the sentence, so a copy of its look without that margin is the honest way to spell it rather than
  a context override that would have to know where the words put it.
*/
.friends-signin {
  border: none;
  border-bottom: 1px solid rgba(238, 244, 240, 0.35);
  border-radius: 0;
  background: none;
  padding: 0;
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.friends-signin:hover,
.friends-signin:focus-visible {
  border-bottom-color: var(--home);
}

/* The doors. A button rather than a bar: on the screen the game starts on the card is ONE
   column, and 75rem of Leaderboard reads as a banner — and on the canvas' controls row they are the
   pair on the left with the handle on the right, which is a width they cannot take. */
.rank-btn--wide {
  /* Wide enough that the pair reads as a pair and no wider: the handle shares this row, and 434px
     of column less two doors is what it has to sit in. */
  min-width: 7rem;
  max-width: 16rem;
  text-align: center;
}

/*
  The canvas' ghost doors, leaning at the same 14° the stat sheet's scoreline and the Kick off
  button already lean at.

  The SHAPE is skewed, not the control: `transform` on the button tips its label too, and un-tipping
  the label needs an inner span the runtime then has to write through — which is how the one line
  that sets a button's word stops being the one line that sets it. So the parallelogram is a
  pseudo-element carrying the border, exactly as `.primary-btn` already draws its own, and the
  button's own border stands down for it.
*/
.rank-btn--skew {
  position: relative;
  isolation: isolate;
  /* The canvas' own 0 30px, less the overhang the skew takes back out of it. */
  padding: 0.5rem 1.15rem;
  border-color: transparent;
}

.rank-btn--skew::before {
  content: "";
  position: absolute;
  /* Inset by the overhang the skew adds, so the parallelogram stays inside its half of the row. */
  inset: 0 0.35rem;
  z-index: -1;
  border: 1px solid rgba(238, 244, 240, 0.28);
  border-radius: 0.2rem;
  transition: border-color 0.18s ease, background 0.18s ease;
  transform: skewX(-14deg);
}

/* The hover lives on the shape now, so the square one underneath has to stop drawing its own. */
.rank-btn--skew:hover,
.rank-btn--skew:focus-visible {
  border-color: transparent;
  background: transparent;
}

.rank-btn--skew:hover::before,
.rank-btn--skew:focus-visible::before {
  border-color: rgba(238, 244, 240, 0.5);
  background: rgba(238, 244, 240, 0.08);
}

/*
  Above the corner capsule, which sits above the card and the pause cover (see `.hud-controls`).
  These three — the board, How to play, the replay — are panels a player deliberately OPENED, and a
  sound control floating over one of them would be furniture on top of the thing it was asked for.
  The help panel already carried this z-index for the neighbouring reason.
*/
.board-panel {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  background: rgba(6, 17, 12, 0.86);
  padding: 1rem;
}

/* ---------- Goal of the match ---------- */

/* A transparent cover: the pitch under it IS the picture. Only the strip is furniture. */
.highlight {
  position: absolute;
  inset: 0;
}

.highlight-strip {
  position: absolute;
  left: 1rem;
  bottom: 1rem;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 0.45rem 0.8rem;
  background: var(--panel);
  border: 1px solid rgba(238, 244, 240, 0.14);
  border-radius: 0.6rem;
  pointer-events: auto;
}

.highlight-strip .moment {
  margin: 0;
}

.highlight-caption {
  margin: 0;
  font-size: 0.85rem;
  color: var(--ink);
}

/*
  The replay's two controls, as ONE capsule in the corner opposite the caption strip — the status
  bar's own treatment worn by a pair of actions rather than two buttons dropped on the pitch: the
  panel, its 1.263em radius, its hairline inset ring, its long soft shadow, cells divided by a rule
  drawn on the second one's own left edge, and the words in the bar's uppercase letter-spaced
  register. The two ends of one line then carry the two halves of a replay: what it is, and what can
  be done with it.

  `overflow: hidden` is load-bearing here for the same reason it is on the bar and on the result
  cells: the divider is drawn one pixel outside a cell's left edge, so the one at the start of the
  row falls outside the capsule and is clipped.

  The STATE is the word's colour, which is the bar's own habit — its clock goes amber, its score
  blue — rather than a control that changes shape under the finger.

  None of it is in a saved clip: the file is the pitch canvas plus the two overlays `clipOverlays`
  describes, and this is DOM, like the bar it is dressed as.
*/
.highlight-menu {
  position: absolute;
  right: 1rem;
  bottom: 1rem;
  overflow: hidden;
  display: flex;
  align-items: stretch;
  height: 5.263em;
  border-radius: 1.263em;
  background: var(--panel);
  box-shadow:
    0 1.474em 4.211em -1.895em rgba(0, 0, 0, 0.9),
    inset 0 0 0 1px rgba(238, 244, 240, 0.1);
  font-size: 0.6rem;
  font-variant-numeric: tabular-nums;
  pointer-events: auto;
}

.highlight-btn {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 2.4em;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 1.474em;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease;
}

.highlight-btn + .highlight-btn::before {
  content: "";
  position: absolute;
  left: 0;
  top: 22%;
  bottom: 22%;
  width: 1px;
  background: rgba(238, 244, 240, 0.1);
}

.highlight-btn:hover,
.highlight-btn:focus-visible {
  background: rgba(55, 201, 255, 0.09);
}

/* The keyboard ring sits INSIDE the capsule and takes a corner: a default outline round a cell of a
   pill draws a rectangle across the shape, which is exactly what the two-button menu used to look
   like. */
.highlight-btn:focus-visible {
  outline: 2px solid var(--home);
  outline-offset: -4px;
  border-radius: 0.7em;
}

.highlight-btn:disabled {
  cursor: default;
  color: var(--faint);
  background: none;
}

/* A showing is being recorded, and the word is counting it. Written at a specificity that clears
   `:disabled`, which is what the button is while a recording runs. */
.highlight-btn.highlight-btn--recording {
  color: var(--warn);
}

/* The file exists: the next tap writes it, so the word takes the offer's own blue. */
.highlight-btn--ready {
  color: var(--home);
}

/* It is on the disk. */
.highlight-btn--saved {
  color: var(--good);
}

/*
  The pause and mute capsule goes while a replay is up, even though the menu no longer wants its
  corner: pausing a replay would stop the hold's clock while the tick loop kept stepping, so the
  control would be a half-freeze rather than a pause.
*/
.replaying #status-controls {
  display: none;
}

@media (max-height: 470px) {
  .highlight-menu {
    right: 0.6rem;
    bottom: 0.6rem;
    height: 4.8em;
    font-size: 0.55rem;
  }

  .highlight-btn {
    padding: 0 1.9em;
    font-size: 1.35em;
  }
}

/* ---------- How to play ---------- */

/*
  The board's cover and card, worn by the standing explanation. Wider than the board — the legend's
  rows want their sentences on one line beside the picture — and it scrolls inside itself on a short
  phone exactly as the board does. Nothing on it is a CSS animation, so the reduced-motion block
  below still has nothing to say about it; the one thing on it that moves is the curve-shot clip,
  and a `<video>` is stilled by not being played rather than by a stylesheet — `armClip` in
  `legend.js` reads the preference itself.
*/
.help-card {
  width: min(44rem, 100%);
}

/*
  The pause cover's own content is positioned at z-index 1 to stay above its backdrop markings, so
  a panel opened OVER the pause cover has to sit above that too, or the score and the stat labels
  paint through the card. Measured 2026-09-05: with no z-index here the pause cover's "0 – 0" and
  possession bar drew across the help text.
*/
.help-panel {
  z-index: 2;
}

.help-facts {
  margin: 0.8rem 0 0;
  padding-left: 1.2rem;
  display: grid;
  gap: 0.45rem;
  font-size: 0.95rem;
  line-height: 1.4;
}

.help-heading {
  margin: 1.1rem 0 0.45rem;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dim);
}

.help-card .detail {
  margin: 0.35rem 0 0;
  line-height: 1.45;
}

.legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}

.legend li {
  display: grid;
  grid-template-columns: 4rem minmax(0, 1fr);
  align-items: center;
  gap: 0.75rem;
  font-size: 0.95rem;
  line-height: 1.4;
}

/* The marker is drawn on grass, as it is met: the pitch's own green under the pitch's own drawing. */
.legend-mark {
  width: 4rem;
  height: 4rem;
  border-radius: 0.45rem;
  background: #3d8844;
}

.legend-meter,
.legend-rail {
  display: block;
  margin: 0.55rem 0 0;
  max-width: 100%;
  border-radius: 0.35rem;
  background: #3d8844;
}

/*
  The curve-shot clip, in the meter's and the rail's own treatment — the same radius and the same
  grass behind it, so the card's one recording reads as a sibling of its drawings rather than as an
  embed dropped into it, and left-aligned with them rather than spanning the card.

  The WIDTH is what is capped, in both directions, because the height then follows the clip's own
  shape rather than being cropped or letterboxed into a box of ours. A full-width block on a 44rem
  card would be 37rem tall and the card would be mostly video, so 24rem across — 20.3rem tall, about
  the meter's own footprint doubled — is the desktop reading.

  The second cap is the one a short landscape phone needs. Measured at 844 x 390, where the card has
  356px of visible height, the desktop size left the clip filling 325 of it and pushed its own
  caption off the bottom: a picture whose explanation you have to scroll to find. `52vh` through the
  clip's own ratio brings it to 240 x 203 there and leaves the caption on screen with it, and it is
  written as that arithmetic rather than as a second number so the two cannot disagree about the
  shape. On any screen tall enough the 24rem is the smaller of the two and nothing changes.

  `aspect-ratio` with `height: auto` is what keeps that box the same size before and after the file
  arrives — a `<video>` with nothing loaded has a 2:1 intrinsic ratio, so without it the caption
  would jump the moment the poster decoded, and under `prefers-reduced-motion` nothing is fetched at
  all until the player asks. The `width`/`height` attributes in the markup say the same thing to a
  browser reading the document before this stylesheet.
*/
.help-clip {
  margin: 0.7rem 0 0;
}

.help-clip-video {
  display: block;
  width: min(100%, 24rem);
  max-width: calc(52vh * 852 / 720);
  height: auto;
  aspect-ratio: 852 / 720;
  border-radius: 0.35rem;
  background: #3d8844;
}

.help-clip figcaption {
  margin-top: 0.5rem;
}

.help-card .primary-btn {
  margin-top: 1.1rem;
}

/* The doors, on one row of the card: the board and the explanation side by side, joined at full
   time by the dare about the match that has just ended. It wraps rather than shrinking a word. */
.rank-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Content-width inside the controls row, so the handle beside them keeps the far end of the line. */
.rank-controls .rank-row {
  flex: 0 1 auto;
  min-width: 0;
}

/* The pause cover's two controls, side by side: the way out, and the explanation. */
/*
  The two ways off this cover, stuck to its bottom edge.

  The same rule as the two cards' `.card-foot` and for the same reason: this cover scrolls when its
  sheet is taller than the screen, and a control that scrolls with the sheet is one a player has to
  find. Sticky here rather than a footer element because the cover's children ARE its grid rows —
  there is no card to sit at the bottom of — so the row pins itself. The background is the cover's
  own, since the sheet scrolls under it.
*/
.paused-actions {
  position: sticky;
  bottom: 0;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
  padding-bottom: 0.2rem;
  background: var(--surround);
}

.board-card {
  pointer-events: auto;
  width: min(38rem, 100%);
  max-height: 100%;
  overflow: auto;
  background: var(--card);
  border: 1px solid rgba(238, 244, 240, 0.14);
  border-radius: 0.9rem;
  /* No bottom padding of its own: `.card-foot` owns it, because a sticky footer sticks to the
     SCROLLPORT's edge and any padding below it is a channel the content scrolls through in plain
     sight. Measured before the move: two legend marks showed under the Back button. */
  padding: 1.1rem 1.25rem 0;
  text-align: left;
}

/*
  The way out of a card that scrolls, stuck to the bottom of the scroller.

  These two cards are taller than a phone and taller than most laptops — the help card's content ran
  a thousand pixels past the fold — and a Back button that is merely the LAST child of the scroller
  is an exit that scrolls away with everything else. Escape and a tap on the scrim both close them,
  so nothing was ever trapped; what was missing is a control a player can SEE.

  Full-bleed against the card's own side and bottom padding, the way Path Racers' editor header is
  against its panel's, so the button sits on the card's edge rather than in a channel. It carries
  the card's background because content scrolls UNDER it, and a hairline in the `card-rule`
  treatment so the join reads as an edge rather than as a gap. It stays INSIDE `.board-card`: help
  closes only on a tap whose target is the section itself, so a footer spanning the cover rather
  than the card would swallow every scrim tap along the bottom of the screen.
*/
.card-foot {
  position: sticky;
  bottom: 0;
  margin: 1.1rem -1.25rem 0;
  padding: 0.75rem 1.25rem;
  background: var(--card);
  border-top: 1px solid rgba(238, 244, 240, 0.14);
}

/*
  The cue that there is more under it.

  The footer is opaque and its hairline is a straight edge across the card, which is exactly what
  the card's own bottom border looks like — so content passing beneath it does not read as content
  passing beneath it, it reads as the end. Measured at 1280x720, five formation blurbs sit under
  this edge cut mid-word, and the screen that exists to explain which shape to pick had nothing on
  it saying they were there.

  A fade says it in the one way that needs no words: the last line dissolves instead of stopping.
  It hangs ABOVE the footer rather than inside it, so the footer's own hairline still reads as an
  edge and the button is never drawn through.

  `--card` is spelled as its own rgba here rather than handed to the gradient as a token, because a
  gradient to `transparent` interpolates through transparent BLACK in every engine and leaves a
  grey bruise across the middle of a fade on a colour this dark. Keep the two in step by hand; the
  token is three lines from the top of this file.
*/
.card-foot::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 1.4rem;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(to top, #10281c, rgba(16, 40, 28, 0));
}

/*
  Drawn only while there IS more, and the class is the RUNTIME's because CSS cannot see a scroll
  position — the same reason the volume slider's `--fill` is written on every paint. A fade left on
  at the end of the scroll would say something is under the footer at the one moment the footer has
  already released and nothing is, which is a worse lie than the hard edge it replaces.
*/
.has-more .card-foot::before {
  opacity: 1;
}

/* On the two panels the footer is the LAST child, so it owns the card's bottom padding as well.
   On the summary card it is not — the exhibition disclosure sits after it — so there the bottom
   padding belongs to the columns, and the footer stops at the scrollport's edge. */
.board-card .card-foot {
  padding-bottom: 1.25rem;
}

/* The button was spaced from the content by its own margin; the footer owns that spacing now. */
.card-foot .primary-btn {
  margin-top: 0;
}

.board-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.board-title {
  margin: 0.4rem 0 0;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.board-columns {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr) 3.5rem 5rem;
  gap: 0 0.6rem;
  margin-top: 0.9rem;
  padding: 0 0.6rem 0.35rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: 0.6rem;
  font-weight: 800;
  color: var(--faint);
}

.board-columns span:nth-child(3),
.board-columns span:nth-child(4) {
  text-align: right;
}

.board-rows {
  display: grid;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.board-row {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr) 3.5rem 5rem;
  gap: 0 0.6rem;
  align-items: center;
  padding: 0.5rem 0.6rem;
  border-radius: 0.4rem;
  background: rgba(238, 244, 240, 0.05);
  font-size: 0.86rem;
}

/* The viewer's own row, which every board shows even when they sit past the visible page. */
.board-row.viewer {
  background: rgba(55, 201, 255, 0.16);
  outline: 1px solid rgba(55, 201, 255, 0.45);
}

.board-row .rank {
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  font-weight: 800;
}

.board-row.viewer .rank {
  color: var(--home);
}

.board-row .player {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-weight: 700;
}

.board-row .clubs {
  text-align: right;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
}

.board-row .score {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 1px;
  text-align: right;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-weight: 800;
}

/* What the score is worth in ZapJam points. The sub-line is the shared renderer's — a cell declares
   `zaps` and `zapjam-board.js` builds it — so it is the same shape on every board in the catalogue;
   only these colours are this game's. The coin is the ZapJam mark drawn as a CSS MASK: the mark
   wears a gradient, an `<img>` cannot be recoloured, and a script is a thing that can fail
   mid-render. It is inline data, so nothing is fetched. */
.board-zaps {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  color: var(--dim);
  font-size: 0.62rem;
  font-weight: 800;
}

/* `display` and `vertical-align` are what put the mark on a text baseline where it is used INLINE —
   the team-sheet chip's best line. Inside `.board-zaps` the coin is a flex item, which blockifies
   the display and ignores the vertical-align, so the board is unaffected and one rule still
   describes one mark. Hidden Flamingo's copy of this rule carries the same pair. */
.board-zap-coin {
  display: inline-block;
  vertical-align: -0.15em;
  width: 0.95em;
  height: 0.95em;
  flex: none;
  background: linear-gradient(135deg, #e0f050, #f4d344 42%, #f88b50);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 640'%3E%3Cpath d='M 515.4 142.5 A 264 264 0 1 1 440.9 85.3' fill='none' stroke='%23000' stroke-width='47' stroke-linecap='round'/%3E%3Cpath d='M -84 -120 L 156 -120 L -12 48 L 156 48 L 84 120 L -156 120 L 12 -48 L -156 -48 Z' fill='%23000' transform='translate(320 310)'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 640'%3E%3Cpath d='M 515.4 142.5 A 264 264 0 1 1 440.9 85.3' fill='none' stroke='%23000' stroke-width='47' stroke-linecap='round'/%3E%3Cpath d='M -84 -120 L 156 -120 L -12 48 L 156 48 L 84 120 L -156 120 L 12 -48 L -156 -48 Z' fill='%23000' transform='translate(320 310)'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}

/* The word, for a screen reader that would otherwise be handed a bare number under a score. */
.board-zap-unit {
  position: fixed;
  width: 1px;
  height: 1px;
  clip: rect(0 0 0 0);
  overflow: hidden;
}

.board-status {
  margin: 0.7rem 0 0;
  font-size: 0.8rem;
  color: var(--dim);
}

/* The pitch follows the board's status line, which carries no bottom margin of its own, so the box
   would otherwise sit directly on the sentence above it. On the CARD the status line's own
   `margin-bottom` already does this, and that card has no height to spare — so the space is bought
   here, on the surface that has it. */
.board-card .rank-pitch {
  margin-top: 0.9rem;
}

@media (prefers-reduced-motion: reduce) {
  /* The sheet's and the card's entrance are the whole of their motion, and none of it carries
     meaning: every bar, every row and every picker is readable at rest. Asked for stillness, they
     simply arrive. The two live dots go with them — a pulse is decoration, and the word beside each
     one is what actually says which moment this is. */
  .stat-score,
  .stat-hero,
  .stat-hero-fill,
  .stat-row,
  .stat-fill,
  .stat-extra,
  .result-cell,
  .paused-word::before,
  .card-head,
  .card-rule,
  .card-main,
  .card-side,
  .moment::before {
    animation: none;
  }

  /* Hover lift and the caret's swing are transitions rather than animations, so they need saying
     separately. Nothing is lost: the pressed state and the open panel are what carry the meaning. */
  .opponent,
  .formation,
  .primary-btn::before,
  .rank-btn--skew::before,
  .result-cell--action,
  .watch > summary::before {
    transition: none;
  }
}

/* ---------- you & friends, and a dare ----------

   The friends section sits above the global rows on the board. A friend row IS a board row — the
   shared renderer draws it with the same cells — plus a fifth, auto-sized column for the Remove
   control, which is why the friends list carries its own template. */
.board-friends {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin-top: 0.9rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid rgba(238, 244, 240, 0.14);
}

.board-friends[hidden],
.board-friends-head[hidden],
.board-friends-rows[hidden] {
  display: none !important;
}

.board-friends-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}

.board-friends-heading {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
}

.friends-btn {
  font-size: 0.7rem;
  padding: 0.35rem 0.7rem;
}

.board-friends-rows .board-row {
  grid-template-columns: 2.4rem minmax(0, 1fr) 3.5rem 5rem auto;
}

.board-row.friend {
  background: rgba(238, 244, 240, 0.08);
}

.board-row.unranked { opacity: 0.72; }

/* A stub's last cell carries words rather than a figure. */
.board-row.unranked .score {
  font-size: 0.68rem;
  font-weight: 600;
  color: var(--dim);
}

.friend-remove {
  justify-self: end;
  font-size: 0.68rem;
}

.board-friends-note,
.board-friends-status,
.board-friends-links {
  margin: 0;
}

/* The dare card on the team sheet: the detail line's voice, a headline in it. */
.dare-card {
  margin-top: 0.6rem;
  padding: 0.55rem 0.75rem;
  border: 1px solid rgba(238, 244, 240, 0.18);
  border-radius: 0.5rem;
  background: rgba(238, 244, 240, 0.06);
}

.dare-headline {
  display: block;
  margin-bottom: 0.15rem;
  color: var(--ink);
  font-weight: 800;
}
/* Armed: the next press removes. */
.friend-remove.confirming {
  color: var(--ink);
  font-weight: 800;
}
