/* Givash Gals — "editorial stat sheet".
 *
 * Design system: design-system/givash-gals/MASTER.md
 * (UI UX Pro Max: Analytics Dashboard palette, Fira Code / Fira Sans,
 *  Data-Dense Dashboard style, density 8/10, motion 2/10.)
 *
 * Three deliberate deviations from MASTER.md, all for contrast — the
 * generated palette picks fills, and these are used as TEXT on a near-white
 * ground, where the raw values fail WCAG AA (4.5:1):
 *   accent  #D97706 -> #B45309 as text (3.4:1 -> 5.8:1); #D97706 kept for fills
 *   win     #22C55E -> #15803D (2.2:1 -> 5.4:1)
 *   penalty #DC2626 -> #B91C1C (4.8:1 -> 6.4:1, comfortable at 12px)
 */

:root {
  /* Palette (MASTER.md) */
  --primary: #1e40af;
  --secondary: #3b82f6;
  --accent-fill: #d97706;
  --accent: #b45309;
  --bg: #f8fafc;
  --card: #ffffff;
  --ink: #0f172a;
  --ink-display: #1e3a8a;
  --muted: #475569;
  --line: #dbeafe;
  --line-strong: #94a3b8;
  --rule: #cbd5e1;
  --win: #15803d;
  --win-tint: #f0fdf4;
  --pen: #b91c1c;
  --warn-bg: #fffbeb;
  --warn-line: #d97706;
  --warn-ink: #78350f;
  --ring: #1e40af;

  /* Spacing (density 8/10) */
  --space-xs: 0.125rem;
  --space-sm: 0.25rem;
  --space-md: 0.5rem;
  --space-lg: 0.75rem;
  --space-xl: 1rem;
  --space-2xl: 1.5rem;
  --space-3xl: 2rem;

  /* Type scale: 12 13 14 16 18 24 32 */
  --t-3xs: 0.6875rem;
  --t-2xs: 0.75rem;
  --t-xs: 0.8125rem;
  --t-sm: 0.875rem;
  --t-base: 1rem;
  --t-lg: 1.125rem;
  --t-xl: 1.5rem;
  --t-2xl: 2rem;

  --radius: 6px;
  --shadow-sm: 0 1px 2px rgb(15 23 42 / 0.05);

  --font-body: "Fira Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-data: "Fira Code", ui-monospace, "SFMono-Regular", Consolas, monospace;

  --measure: 68ch;
  --page: 64rem;
  --nav-h: 45px; /* sticky tab bar: 44px target + 1px rule */
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 400 var(--t-base) / 1.6 var(--font-body);
  -webkit-font-smoothing: antialiased;
}

:where(a) {
  color: var(--primary);
  text-underline-offset: 2px;
}

/* Every interactive element gets the same ring, and it is never removed. */
:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: 3px;
}
:focus:not(:focus-visible) {
  outline: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--space-md);
  top: -3rem;
  z-index: 20;
  background: var(--primary);
  color: #fff;
  padding: var(--space-md) var(--space-xl);
  border-radius: var(--radius);
  font-size: var(--t-sm);
  font-weight: 600;
  text-decoration: none;
  transition: top 150ms ease;
}
.skip-link:focus {
  top: var(--space-md);
}

/* --- Masthead ------------------------------------------------------ */

/* The masthead scrolls away; only the tab bar sticks. Sticking both would
 * eat ~145px of a phone viewport and, worse, the leaderboard's own sticky
 * <th> would slide underneath it. --nav-h is that bar's height, and the
 * leaderboard header offsets by exactly it. */
.masthead {
  background: var(--bg);
}

.masthead-inner {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--space-2xl) var(--space-xl) var(--space-lg);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-xl);
  flex-wrap: wrap;
}

.brand h1 {
  margin: 0;
  font-family: var(--font-data);
  font-size: var(--t-xl);
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--ink-display);
  line-height: 1.1;
}

.tagline {
  margin: var(--space-sm) 0 0;
  font-size: var(--t-2xs);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
}

/* Only shown when the page has recomputed the live week, or on load error. */
.freshness {
  margin: 0;
  font-size: var(--t-2xs);
  color: var(--muted);
  font-family: var(--font-data);
}

.freshness.live {
  display: inline-flex;
  align-items: center;
  gap: var(--space-md);
  color: var(--win);
  background: var(--win-tint);
  border: 1px solid #bbf7d0;
  border-radius: 999px;
  padding: var(--space-xs) var(--space-lg);
  font-weight: 500;
}

.freshness.live::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--win);
  animation: pulse 2s ease-in-out infinite;
}

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

/* --- Tab navigation ------------------------------------------------ */

.tabs-nav {
  position: sticky;
  top: 0;
  z-index: 10;
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 var(--space-xl);
  background: var(--bg);
  border-bottom: 1px solid var(--rule);
}

.tabs-nav-inner {
  display: flex;
  gap: var(--space-2xl);
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs-nav-inner::-webkit-scrollbar {
  display: none;
}

.tabs-nav button {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  font: 500 var(--t-sm) / 1 var(--font-body);
  /* 44px touch target via padding, not height, so the rule sits at the edge. */
  padding: var(--space-lg) 0 var(--space-lg);
  min-height: 44px;
  white-space: nowrap;
  cursor: pointer;
  transition: color 150ms ease, border-color 150ms ease;
}

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

.tabs-nav button.active {
  color: var(--ink-display);
  font-weight: 600;
  border-bottom-color: var(--accent-fill);
}

.tabs-nav button:focus-visible {
  outline-offset: -2px;
}

/* --- Page body ----------------------------------------------------- */

main {
  max-width: var(--page);
  margin: 0 auto;
  padding: var(--space-2xl) var(--space-xl) var(--space-3xl);
}

main:focus {
  outline: none;
}

.banner {
  margin: 0 0 var(--space-2xl);
  padding: var(--space-lg) var(--space-xl);
  border: 1px solid var(--warn-line);
  border-left-width: 3px;
  background: var(--warn-bg);
  color: var(--warn-ink);
  border-radius: var(--radius);
  font-size: var(--t-sm);
}

.empty {
  color: var(--muted);
  padding: var(--space-3xl) 0;
  text-align: center;
  font-size: var(--t-sm);
}

/* Wide tables scroll rather than break the layout — but only when they have
 * to. `overflow-x: auto` makes this a scroll container, and a scroll
 * container is what `position: sticky` on the leaderboard's <th> resolves
 * against, so switching it on unconditionally would silently disable the
 * sticky header at every width. Above 52rem every column fits inside the
 * 64rem page, so the wrapper stays transparent and the header sticks. */
.table-wrap {
  -webkit-overflow-scrolling: touch;
}

/* --- Standings ----------------------------------------------------- */

table.standings {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-sm);
}

.standings caption {
  text-align: left;
  font-size: var(--t-2xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  padding-bottom: var(--space-md);
}

.standings thead th {
  text-align: left;
  font: 500 var(--t-3xs) / 1.3 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  padding: var(--space-md) var(--space-md);
  border-bottom: 2px solid var(--ink-display);
  vertical-align: bottom;
  white-space: nowrap;
}

/* The team cell is a row header (scope="row"), so it must not inherit any
 * of the column-header treatment above. */
.standings td,
.standings tbody th {
  padding: var(--space-lg) var(--space-md);
  border-bottom: 1px solid var(--line);
  text-align: left;
  font-weight: 400;
  color: inherit;
}

.standings tbody tr {
  transition: background-color 150ms ease;
}
.standings tbody tr:hover {
  background: #f1f5f9;
}

/* The leader, said visually. The tint goes on the <tr>, not the cells: the
 * team cell is a <th scope="row"> and a per-cell background would stop dead at
 * it.
 *
 * Keyed to a .leader class rather than tr:first-child, which was only ever the
 * same thing while the table sat in standings order. Once the headers became
 * sortable, :first-child put the leader's tint on whichever team happened to
 * top the current sort — announcing the fewest-penalties team as the league
 * leader. */
.standings tbody tr.leader {
  background: #fffbeb;
}
.standings tbody tr.leader:hover {
  background: #fef3c7;
}
.standings tbody tr.leader .team {
  color: var(--ink-display);
}
.standings tbody tr.leader .rank {
  color: var(--accent);
  font-weight: 700;
}

.standings .num,
.standings th.num {
  text-align: right;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

.standings .rank {
  color: var(--muted);
  width: 2.75rem;
  font-family: var(--font-data);
  font-size: var(--t-xs);
}

.standings .team {
  font-weight: 600;
  font-size: var(--t-base);
}

.standings .record {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-xs);
}

.standings .muted {
  color: var(--muted);
}

/* --- Sortable standings --------------------------------------------
 *
 * The header is the sort control at every width. The Players board needs a
 * separate <select> on a phone because its <thead> is hidden there; this table
 * keeps its header row, so the headers themselves stay tappable and there is
 * no second control to keep in sync.
 */
.standings th.sortable { cursor: pointer; }

/* The tap target is the padded row, not the glyph: the tappable area would
 * otherwise be the height of 9px uppercase text, which on a phone is a coin
 * toss. Padding rather than min-height, and block rather than inline-flex,
 * because an inline-flex button made every SORTABLE header a 44px box while
 * the plain "#" header - the one column with no button - aligned to the middle
 * of the taller row and sat a full line below every other label. */
.standings th.sortable .th-btn {
  display: block;
  width: 100%;
  padding: 0.7rem 0;
  text-align: inherit;
}
.standings thead th { vertical-align: middle; }

/* The sorted column, marked down the whole body rather than in the header
 * alone. On a phone the header scrolls out of sight the moment you look at the
 * fifth row, and a column you cannot see the heading of is just numbers. */
.standings td.sorted,
.standings th.team.sorted {
  background: var(--accent-tint, #fffbeb);
  font-weight: 600;
}

/* Full labels by default; the phone block below swaps to the short ones. */
.standings .lbl-short { display: none; }


/* Adjusted PF is the tiebreak everyone actually reads. */
.standings .adjpf {
  font-weight: 600;
  color: var(--ink-display);
}

.gate-note {
  font-size: var(--t-2xs);
  color: var(--muted);
  margin: var(--space-md) 0 0;
}

.hint {
  display: block;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: var(--t-3xs);
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
  opacity: 0.85;
}

/* --- Weeks & matchup slips ----------------------------------------- */

.week {
  margin: 0 0 var(--space-3xl);
}

.week h2 {
  font: 600 var(--t-2xs) / 1 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--muted);
  margin: 0 0 var(--space-lg);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--rule);
}

/* Hairline-separated slips, not boxes: the rule that divides them is also what
 * carries the week's state, so no extra chrome is spent saying it. */
.card {
  position: relative;
  display: block;
  background: var(--card);
  border: 0;
  border-top: 1px solid var(--rule);
  padding: var(--space-lg);
  transition: background 200ms ease;
}
.card:last-of-type { border-bottom: 1px solid var(--rule); }

/* Dashed while the week can still move, solid once every game is final. */
.card.live { border-top-style: dashed; }
.card.live:last-of-type { border-bottom-style: dashed; }

/* The chevron gutter belongs to the cards that draw a chevron. An archived
 * week emits none, and used to pay for one by sitting off-centre anyway. */
.card.clickable { cursor: pointer; padding-right: var(--space-3xl); }
.card.clickable:hover { background: #fbfdff; }

/* The stretched link.
 *
 * The card used to BE the button (div[role="button"] tabindex="0"), which made
 * every score inside it un-interactive: nothing may nest inside an interactive
 * element. Moving the click onto a link laid over the card frees the scores to
 * be real buttons, and hands back middle-click, right-click and open-in-new-tab
 * that the div never had.
 *
 * inset:0 resolves against the card's PADDING box, so the outline sits just
 * inside the card's own rules — the whole card visibly takes focus, as it did
 * when it was the button. */
.card-open {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.card-open:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }

/* A tooltip must escape its own card. Cards are all position:relative with no
 * z-index, so the next card in the DOM paints over this one's overflow; raise
 * whichever card is currently showing a tooltip. :hover covers mouse, and it
 * also fires on a tap in every mobile browser this is read on. */
.card:hover,
.card:focus-within { z-index: 2; }
.card:has(.score.open) { z-index: 2; }

.chev {
  position: absolute;
  /* Decoration sitting on top of the stretched link. Without this a click on
   * the chevron itself — the one part of the card that LOOKS like the
   * control — would land on the glyph and do nothing. */
  pointer-events: none;
  right: var(--space-md);
  top: 50%;
  transform: translateY(-50%);
  color: var(--line-strong);
  font-size: var(--t-xl);
  line-height: 1;
}

.card-state {
  font: 400 var(--t-3xs) / 1 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}
.card-state .pip { width: 6px; height: 6px; border-radius: 50%; background: var(--muted); }
.card.live .card-state .pip { background: transparent; border: 1px solid var(--muted); }

.teams,
.lrow {
  display: grid;
  grid-template-columns: 1fr minmax(5.5rem, auto) 1fr;
  align-items: baseline;
  gap: var(--space-sm) var(--space-md);
}
.teams { margin-bottom: var(--space-md); }

.tname { font-weight: 600; font-size: var(--t-sm); min-width: 0; overflow-wrap: anywhere; }
.tname.l { text-align: right; }
.tname.r { text-align: left; }
.tname.win { color: var(--win); }
.tname .lead {
  display: block;
  font: 400 var(--t-3xs) / 1.4 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--win);
}

/* Hollow ring, so the live state is carried by SHAPE and not only by colour. */
.lead-ring {
  display: inline-block;
  width: 0.62em;
  height: 0.62em;
  border: 1.6px solid var(--win);
  border-radius: 50%;
  vertical-align: 0.02em;
}
.win-mark { flex: none; width: 1em; height: 1em; color: var(--win); }

/* The pill, unchanged: the upcoming-fixtures list below still draws it, and
 * that view is not what this task redrew. */
.vs {
  align-self: center;
  font: 500 var(--t-3xs) / 1 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  text-align: center;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: var(--space-sm) var(--space-lg);
  white-space: nowrap;
}

/* On a hairline slip the divider between two names is a label, not a chrome
 * element — so the card strips the pill back off. Scoped to .teams rather
 * than folded into the rule above, which would change a view this task never
 * touched. align-self returns to baseline so the label sits on the same line
 * as the names, which is what .teams' own align-items asks for. */
.teams .vs {
  align-self: baseline;
  font: 500 var(--t-3xs) / 1 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  text-align: center;
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
}

.lrow { padding: var(--space-sm) 0; }
.lrow + .lrow { border-top: 1px solid var(--line); }
.lrow .n {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-base);
  color: var(--muted);
}
.lrow .n.l { text-align: right; }
.lrow .n.r { text-align: left; }
.lrow .lbl { font-size: var(--t-2xs); text-align: center; color: var(--muted); white-space: nowrap; }
.lrow .lbl .cap { display: block; font-size: var(--t-3xs); color: var(--muted); }

/* The deciding row takes ink and weight, never a larger size: low wins here, so
 * a bigger number must never read as a better one. */
.lrow.decides { background: #fcfdff; }
.lrow.decides .n { color: var(--ink); font-weight: 600; }
.lrow.decides .lbl { color: var(--ink); font-weight: 600; }

.pool-row { grid-column: 1 / -1; text-align: center; padding-top: var(--space-sm); }

/* The chips average to the ADJUSTED line, which while games are running is not
 * the emphasised row directly above them. Juxtaposition would assert the wrong
 * provenance, so the row says in words which reading it belongs to. */
.pool-row .pool-cap { display: block; font-size: var(--t-3xs); color: var(--muted); }

/* .pool is a flex container, so it lays its chips from flex-start and the
 * parent's text-align never reaches them. */
.pool-row .pool { justify-content: center; }

/* --- One score line per team ----------------------------------------
 *
 * The same three-column track as .teams above, so each score sits under the
 * name it belongs to. The middle column stays empty: it is the gutter the
 * "vs" occupies one row up. */
/* align-items is START, not baseline, and that is load-bearing.
 *
 * `.score` is a <button>, which is an inline-block, and an inline-block's
 * baseline is the baseline of its LAST line box. On a phone each side of this
 * grid is about 116px, which "213.24 (233.24)" cannot fit on one line — so the
 * bracket wrapped, the button's baseline became that second line, and baseline
 * alignment hoisted the whole button up by a line to meet the one-line side
 * opposite. That dragged the real score ABOVE this rule's own border-top, and
 * the hairline drew straight through the number.
 *
 * Aligning the tops instead cannot be moved by how a cell wraps. When both
 * sides are a single line — every desktop case, and most phone ones — it is
 * pixel-identical to the baseline it replaces. */
.scores {
  display: grid;
  grid-template-columns: 1fr minmax(5.5rem, auto) 1fr;
  align-items: start;
  gap: var(--space-sm) var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--line);
}
/* Shrink-wrapped to the button by justify-self, NOT stretched across the
 * column. Stretched, the tooltip below anchored to the column's outer edge and
 * opened half a card away from the number it explains. */
.score-cell { position: relative; min-width: 0; }
.score-cell.l { grid-column: 1; justify-self: end; text-align: right; }
.score-cell.r { grid-column: 3; justify-self: start; text-align: left; }

/* A button, because it answers to hover, to focus AND to a tap — and on the
 * phone this is read on during games, only the last of those exists.
 * The dotted underline is the affordance: something is behind this number. */
.score {
  position: relative;   /* above .card-open, so the tap lands here */
  z-index: 1;
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px dotted var(--line-strong);
  padding: var(--space-sm) var(--space-xs);
  font: 600 var(--t-lg) / 1.2 var(--font-data);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  cursor: help;
  text-align: inherit;
}
.score:hover { border-bottom-color: var(--primary); }

/* The parenthesised IN-PLAY score - where this team would land if every game
 * stopped now. Recessive on purpose: it is context for the real score beside
 * it, not a competitor to it, and it is drawn at all only when it would print
 * a different number (see scoreLine). */
.score .alt {
  font-size: var(--t-xs);
  font-weight: 400;
  color: var(--muted);
}

/* The caption under the score: what it has been charged, and how much football
 * is left to charge more.
 *
 * The WRAPPER is the block, not the notes — .score-tip sits between these and
 * the button in the DOM (see toPlayNote on why it must) and is absolutely
 * positioned, so without a block of their own the notes would hang on the
 * button's inline-block line. One wrapper instead of two blocks keeps the
 * caption to a single line on a phone.
 */
.score-notes {
  display: block;
  font: 400 var(--t-3xs) / 1.4 var(--font-data);
  padding-top: var(--space-xs);
}
.score-cell.l .score-notes { text-align: right; }
.score-cell.r .score-notes { text-align: left; }

/* How many +20s this side has actually been charged. --pen is the same colour
 * the drill-down's locked tag uses, so the two views agree on what a charged
 * penalty looks like — but the colour is never carrying it alone: the note
 * literally reads "3 × +20", and a screen reader gets "penalties charged". */
.pen-count { color: var(--pen); }

/* How many starters have not kicked off. Muted and two sizes down: a caveat
 * about the score, never a second score. */
.to-play { color: var(--muted); }

/* Neither note ever breaks inside itself. Without this the caption wrapped as
 * "4 × +20 · 2 to" / "play", splitting a three-word phrase across two lines
 * while the middot sat mid-sentence. Breaking BETWEEN the notes is the only
 * break either of them should ever take. */
.pen-count,
.to-play { white-space: nowrap; }

/* The separator exists only when both notes do, so neither one has to know
 * whether the other is there. */
.pen-count + .to-play::before {
  content: "·";
  margin: 0 0.45em;
  color: var(--line-strong);
}

/* In the DOM at all times so aria-describedby resolves to real text; CSS is
 * the only thing hiding it. Anchored to its own side rather than centred, so
 * neither edge of a phone screen clips it. */
.score-tip {
  position: absolute;
  z-index: 5;
  top: calc(100% + var(--space-sm));
  width: max-content;
  max-width: min(15rem, 68vw);
  display: grid;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  background: var(--card);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: 0 6px 20px rgb(15 23 42 / 0.14);
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transition: opacity 150ms ease;
}
/* Each one opens toward the middle of the card, from the edge of its own
 * number: the left score's tooltip is right-aligned to it, the right score's
 * left-aligned. Both stay inside the card at any width above the breakpoint. */
.score-cell.l .score-tip { right: 0; }
.score-cell.r .score-tip { left: 0; }

/* Reads before the rows, and is the answer to the question the captions kept
 * provoking: the three numbers share their points and differ only in +20s. */
.score-tip .tip-head {
  font-size: var(--t-3xs);
  line-height: 1.4;
  color: var(--muted);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--line);
}

.score:hover + .score-tip,
.score:focus-visible + .score-tip,
.score.open + .score-tip { opacity: 1; visibility: visible; }

.tip-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 var(--space-lg);
  align-items: baseline;
}
.tip-row + .tip-row { border-top: 1px solid var(--line); padding-top: var(--space-sm); }
.tip-row b {
  font: 600 var(--t-3xs) / 1.5 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink);
}
.tip-row .tip-v {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-xs);
  color: var(--ink);
  text-align: right;
}
.tip-row em {
  grid-column: 1 / -1;
  font-style: normal;
  font-size: var(--t-3xs);
  line-height: 1.4;
  color: var(--muted);
}

.week-status {
  font-size: var(--t-2xs);
  color: var(--muted);
  border-left: 2px solid var(--line-strong);
  padding-left: var(--space-md);
  margin: 0 0 var(--space-lg);
}
.week-status b { color: var(--ink); font-weight: 600; }

@media (max-width: 34rem) {
  .teams, .lrow, .scores { grid-template-columns: 1fr minmax(4.6rem, auto) 1fr; }
  .score { font-size: var(--t-base); }
  /* About 116px a side is not enough for "213.24 (233.24)" on one line, so the
   * bracket was wrapping anyway — but only on whichever side happened to
   * overflow, which left the two sides of a card built differently. Making the
   * break explicit puts the projection under its own score on BOTH sides, so a
   * reader compares two numbers that sit in the same place. */
  .score .alt { display: block; }

  /* The same width that defeats the score defeats the caption: "4 × +20 · 2 to
   * play" does not fit 116px either. So the two notes stack instead of sharing
   * a line, and the middot goes with them — a separator earns its place only
   * between two things actually side by side. */
  .pen-count { display: block; }
  .pen-count + .to-play::before { content: none; }
  /* Half a phone screen is not enough room for a tooltip to hang under its own
   * number without running off the side, so each one anchors to the card edge
   * nearest it and grows inward instead. */
  .score-cell.l, .score-cell.r { justify-self: stretch; }
  .score-cell.l .score-tip { left: 0; right: auto; }
  .score-cell.r .score-tip { right: 0; left: auto; }
}

/*
 * The three penalty phases, as three shapes rather than three colours: solid
 * fill, dashed outline, dotted outline. Colour is never the sole carrier of
 * meaning here - someone who cannot tell red from grey must still be able to
 * tell a locked penalty from a pending or a not-yet-started one by outline
 * alone. Replaces the single flat "red badge" .pen this task 8 superseded:
 * that one rule could not represent three states without colour doing all
 * the work.
 */
.pen {
  font: 500 var(--t-3xs) / 1.4 var(--font-data);
  padding: 0.04em 0.3em;
  border-radius: 3px;
  white-space: nowrap;
  flex: none;
}
.pen.locked { background: var(--pen); color: var(--card); }
.pen.pending { border: 1px dashed var(--pen); color: var(--pen); }
.pen.waiting { border: 1px dotted var(--line-strong); color: var(--muted); }

.pool {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
  margin-top: var(--space-md);
}

.pool span {
  font-family: var(--font-data);
  font-size: var(--t-3xs);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0 var(--space-md);
}

/* The two scores that were actually averaged into the line. */
.pool span.used {
  color: var(--ink-display);
  background: #eff6ff;
  border-color: var(--secondary);
  font-weight: 600;
}

/* --- Upcoming weeks ------------------------------------------------ */

.upcoming-label {
  margin: 0 0 var(--space-lg);
  font: 500 var(--t-3xs) / 1 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent);
}

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

.fixture {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: var(--space-lg);
  align-items: center;
  padding: var(--space-lg) var(--space-xl);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: var(--space-md);
  font-size: var(--t-sm);
}

.fixture .side-name { font-weight: 600; min-width: 0; }
.fixture .side-name:last-child { text-align: right; }
.fixture.median .side-name:last-child { color: var(--muted); font-weight: 500; }

/* Carried over from the old shared `.vs { justify-self: start }`, which lived
 * in a 40rem block written for the three-column card and was deleted with it.
 * The card no longer wants it; this list always did — without it the pill
 * stretches the full row width once .fixture collapses to one column below. */
@media (max-width: 40rem) {
  .fixture .vs { justify-self: start; }
}

@media (max-width: 34rem) {
  .fixture { grid-template-columns: 1fr; gap: var(--space-sm); text-align: left; }
  .fixture .side-name:last-child { text-align: left; }
}

/* Sorting for a screen with no header row to tap. Hidden wherever the real
 * <th> buttons are on show, which are the better control: they say which
 * column is sorted in place, rather than naming it somewhere else. */
.mobile-sort { display: none; }

@media (max-width: 40rem) {
  .mobile-sort {
    display: flex;
    align-items: stretch;
    gap: var(--space-md);
    width: 100%;
  }
  .mobile-sort select { flex: 1; min-width: 0; }
  .mobile-sort button {
    flex: none;
    min-height: 44px;
    padding: 0 var(--space-lg);
    background: var(--card);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    color: var(--ink);
    font: 500 var(--t-2xs) / 1 var(--font-data);
    white-space: nowrap;
    cursor: pointer;
  }
}

/* --- Results week picker -------------------------------------------- */

.controls .weeks a {
  min-width: 2.25rem;
  padding: var(--space-md) var(--space-sm);
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* A week with results reads differently from one that has not happened. */
.controls .weeks a.played { color: var(--ink); border-color: var(--line-strong); }
.controls .weeks a.on { color: var(--card); background: var(--primary); border-color: var(--primary); }

/* An arrow with nowhere to go is a span, not a disabled link: a link that
 * navigates nowhere is worse than no link. It keeps the same footprint so the
 * picker does not shift as the week moves. */
.controls .step.off {
  color: var(--muted);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--space-md) var(--space-lg);
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  font: 400 var(--t-xs) / 1 var(--font-body);
  opacity: 0.4;
}

/* --- Players tab --------------------------------------------------- */

.controls {
  display: flex;
  gap: var(--space-md);
  flex-wrap: wrap;
  align-items: center;
  margin: 0 0 var(--space-lg);
}

.controls .tabs {
  display: flex;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.controls .spacer {
  flex: 1;
  min-width: 0;
}

.controls button,
.controls a,
.controls select,
.controls input {
  font: 400 var(--t-xs) / 1 var(--font-body);
  text-decoration: none;
  color: var(--muted);
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--space-md) var(--space-lg);
  min-height: 32px;
  cursor: pointer;
  transition: color 150ms ease, border-color 150ms ease, background-color 150ms ease;
}

.controls button:hover:not(:disabled),
.controls a:hover,
.controls select:hover {
  color: var(--ink);
  border-color: var(--line-strong);
}

/* Links have no padding box of their own until they are told to have one. */
.controls a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.controls button.on {
  color: var(--card);
  background: var(--primary);
  border-color: var(--primary);
  font-weight: 500;
}

.controls button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.controls input {
  cursor: text;
  min-width: 12rem;
  flex: 1 1 12rem;
  max-width: 18rem;
  color: var(--ink);
}

.controls input::placeholder {
  color: var(--muted);
}

.controls input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgb(30 64 175 / 0.12);
}

.controls .seasons button,
.controls .stepper button {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
}

.stepper {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
}

.stepper .readout {
  font-family: var(--font-data);
  font-size: var(--t-xs);
  color: var(--muted);
  min-width: 6rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.count {
  font-family: var(--font-data);
  color: var(--muted);
  font-size: var(--t-2xs);
  margin-bottom: var(--space-md);
}

.note {
  color: var(--warn-ink);
  background: var(--warn-bg);
  border-left: 3px solid var(--warn-line);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: var(--t-xs);
  margin: 0 0 var(--space-lg);
  padding: var(--space-md) var(--space-lg);
}

table.leaderboard {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-xs);
}

table.leaderboard th {
  position: sticky;
  top: var(--nav-h);
  z-index: 1;
  background: var(--bg);
  text-align: left;
  font: 500 var(--t-3xs) / 1.3 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  padding: var(--space-md) var(--space-md);
  border-bottom: 2px solid var(--ink-display);
  white-space: nowrap;
}

/* The header button is the keyboard-operable control; the th is only the
 * cell. Both are clickable — the button's click bubbles to the th handler. */
table.leaderboard th .th-btn,
table.standings th .th-btn {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  transition: color 150ms ease;
}

table.leaderboard th .th-btn:hover,
table.standings th .th-btn:hover {
  color: var(--primary);
}

table.leaderboard th[aria-sort] .th-btn,
table.standings th[aria-sort] .th-btn {
  color: var(--ink-display);
  font-weight: 700;
}

table.leaderboard th.num .th-btn,
table.standings th.num .th-btn {
  width: 100%;
  text-align: right;
}

/* The only header with a tooltip; the dots are the affordance that says so. */
table.leaderboard th[title] .th-btn,
table.leaderboard th[title] {
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

table.leaderboard th.num,
table.leaderboard td.num {
  text-align: right;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
}

table.leaderboard td {
  padding: var(--space-md) var(--space-md);
  border-bottom: 1px solid var(--line);
}

table.leaderboard tbody tr {
  transition: background-color 120ms ease;
}
table.leaderboard tbody tr:hover {
  background: #f1f5f9;
}

table.leaderboard td.rank {
  color: var(--muted);
  width: 3rem;
  font-family: var(--font-data);
}
table.leaderboard td.name {
  font-weight: 600;
  font-family: var(--font-body);
  font-size: var(--t-sm);
}
table.leaderboard td.team,
table.leaderboard td.pos,
table.leaderboard td.owner {
  color: var(--muted);
  font-size: var(--t-2xs);
}
table.leaderboard td.pos {
  font-family: var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
table.leaderboard td.owner.fa {
  color: var(--accent);
  font-weight: 600;
}
/* Adj Total and PPG are what the board exists to show. Red is reserved for
 * an actual +20 badge in Results; a count of them is data, not a warning. */
table.leaderboard td.metric {
  color: var(--ink-display);
  font-weight: 700;
}
table.leaderboard th .dir,
table.standings th .dir {
  color: var(--accent);
}

/* --- Rules prose --------------------------------------------------- */

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

.rules h2 {
  font: 600 var(--t-2xs) / 1.3 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-display);
  margin: var(--space-3xl) 0 var(--space-md);
  padding-bottom: var(--space-md);
  border-bottom: 1px solid var(--rule);
}

.rules h2:first-child {
  margin-top: 0;
}

.rules p {
  margin: 0 0 var(--space-lg);
  color: #1f2937;
}

.rules strong {
  font-weight: 600;
  color: var(--ink);
}

/* --- Responsive ---------------------------------------------------- */

@media (max-width: 52rem) {
  .table-wrap {
    overflow-x: auto;
  }

  /* Owner already says which roster a player is on; the NFL team is the
   * cheapest column to lose first. */
  table.leaderboard td.team,
  table.leaderboard th:nth-child(3) {
    display: none;
  }

  table.leaderboard {
    min-width: 38rem;
  }

  /* Not sticky here: the wrapper above is the scroll container now. */
  table.leaderboard th {
    position: static;
  }
}

@media (max-width: 34rem) {
  :root {
    --page: 100%;
  }

  .masthead-inner {
    padding: var(--space-xl) var(--space-lg) var(--space-md);
  }

  .tabs-nav {
    padding: 0 var(--space-lg);
  }

  .tabs-nav-inner {
    gap: var(--space-xl);
  }

  main {
    padding: var(--space-xl) var(--space-lg) var(--space-3xl);
  }

  /* The standings stay a TABLE on a phone.
   *
   * They used to collapse to one labelled card per team below this width, on
   * the reasoning that seven columns will not fit 375px. Measured rather than
   * assumed, that is wrong: at --t-2xs all eight columns come to 366px inside
   * a 390px viewport, and they fit from 360px up. What the collapse cost was
   * the whole point of a standings table - four captioned rows per team and
   * five screens of scrolling, so comparing two teams' Adj PF meant holding
   * one number in your head while you scrolled to the other.
   *
   * Below 360px the table exceeds the page and .table-wrap's overflow-x takes
   * over, scrolling the TABLE rather than the document. That is the fallback
   * for a 320px phone, and it is still a table.
   */
  table.standings {
    font-size: var(--t-2xs);
  }

  .standings thead th,
  .standings td,
  .standings tbody th {
    padding: var(--space-md) var(--space-sm);
  }

  /* "1-0-0" is one token. Left to wrap it broke as "1-0-" / "0", which reads
   * as a different number entirely. */
  .standings .record,
  .standings .med {
    white-space: nowrap;
  }

  /* The full header labels are what overflow, not the numbers: "vs Median" is
   * three times the width of the "0-0-0" beneath it. Each header ships both
   * spellings and the width picks one, so there is no second render path. */
  .standings .lbl-full { display: none; }
  .standings .lbl-short { display: inline; }

  /* The team name is the only elastic column, so it is the one that wraps -
   * but at word boundaries only. `anywhere` snapped "LilDaveIII" into
   * "LilDaveI" / "II" and "Nsanders10" into "Nsande" / "rs10", turning a
   * manager's name into two nonsense fragments. A name with no space in it
   * stays whole and the column widens to take it; every number column has a
   * fixed width and gives up nothing. */
  .standings .team {
    font-size: var(--t-sm);
    overflow-wrap: normal;
  }

  /* 44px of rank column is most of a team name's worth of space, for at most
   * two characters. */
  .standings .rank {
    width: 1.5rem;
    font-size: var(--t-3xs);
  }
}

/*
 * The leaderboard becomes cards at 40rem, not at the 34rem the rest of the
 * phone layout uses, because this breakpoint is arithmetic rather than taste:
 * the table's own min-width is 38rem (608px) and `main` spends 1rem of padding
 * a side above 34rem, so the table only fits from 640px = 40rem up. Set at
 * 34rem the band from 545px to 639px got the table back before there was room
 * for it - 608px of columns in 513px of page, side-scrolling and as cramped as
 * the layout this replaced. Matching the breakpoint to the fit means the table
 * appears exactly when it can be read.
 *
 * Eleven columns in a 390px scroll box put the +20 column off-screen
 * entirely and wrapped every name to three lines. Cards instead - but not
 * the Standings' one-labelled-row-per-field treatment, which suits five
 * teams and would be five screens of scrolling for thirty-two players.
 * Each card is a name, an identity line, and the six numbers as a 3-up
 * grid of label-over-value cells: about five lines a player, not eleven.
 */
@media (max-width: 40rem) {
  /* Every selector below carries `table.leaderboard` rather than the shorter
   * `.leaderboard`: the desktop rules are written that way, and a selector one
   * class lighter loses to them on specificity no matter which media query it
   * sits in. Matching their shape lets source order decide, which is what a
   * later override should be deciding on. */
  table.leaderboard,
  table.leaderboard tbody,
  table.leaderboard tr,
  table.leaderboard td {
    display: block;
    width: auto;
  }

  table.leaderboard thead,
  table.leaderboard caption { display: none; }

  /* The row rule is the card border now; a per-cell underline would draw a
   * hairline under every field inside it. */
  table.leaderboard td {
    border-bottom: 0;
    padding: 0;
  }

  /* Undoes the 52rem rule above: the NFL team is the cheapest column to drop
   * while this is a table, but a card has room for it on the identity line. */
  table.leaderboard td.team { display: block; }

  /* min-width and the scroll container both go: there is nothing left to
   * scroll sideways to. */
  table.leaderboard { min-width: 0; }
  .table-wrap { overflow-x: visible; }

  table.leaderboard tbody tr {
    position: relative;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-md) var(--space-lg);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: var(--space-lg) var(--space-xl) var(--space-lg) 2.9rem;
    margin-bottom: var(--space-md);
  }

  /* Out of the grid entirely and into the gutter the padding above reserves,
   * so it reads as a rank badge beside the name rather than as a first field. */
  table.leaderboard td.rank {
    position: absolute;
    left: var(--space-md);
    top: var(--space-lg);
    width: 2.1rem;
    text-align: right;
    font: 500 var(--t-2xs) / 1.5 var(--font-data);
    color: var(--muted);
  }

  table.leaderboard td.name {
    grid-column: 1 / -1;
    font-size: var(--t-sm);
    font-weight: 600;
  }

  /* The identity line: position, NFL team, owner, one per column. */
  table.leaderboard td.pos,
  table.leaderboard td.team,
  table.leaderboard td.owner {
    font: 400 var(--t-2xs) / 1.4 var(--font-data);
    color: var(--muted);
  }
  table.leaderboard td.owner { text-align: right; }
  table.leaderboard td.pos { text-align: left; }
  table.leaderboard td.team { text-align: left; }

  /* The six numbers auto-flow into two rows of three, each captioning itself
   * from the same header string the <th> uses. */
  table.leaderboard td.num {
    display: flex;
    flex-direction: column;
    gap: 1px;
    text-align: left;
    padding: var(--space-sm) 0 0;
    border-top: 1px solid var(--line);
    font-size: var(--t-xs);
  }
  table.leaderboard td.num::before {
    content: attr(data-label);
    font: 500 var(--t-3xs) / 1.3 var(--font-data);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
  }
  table.leaderboard td.num.metric { color: var(--ink-display); font-weight: 700; }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

@media print {
  .masthead {
    position: static;
  }
  .tabs-nav,
  .skip-link,
  .controls,
  .freshness {
    display: none;
  }
  body {
    background: #fff;
  }
  .card {
    break-inside: avoid;
    box-shadow: none;
  }
  /* On paper there is no hover, so the explanation would never appear —
   * print the numbers alone rather than an empty gap where a tooltip went. */
  .score-tip,
  .chev {
    display: none;
  }
  .score { border-bottom: 0; }
}

/* --- Matchup detail ------------------------------------------------ */

.back {
  display: inline-block;
  padding: var(--space-md) 0;
  margin-bottom: var(--space-lg);
  font: 500 var(--t-sm) / 1 var(--font-body);
  color: var(--primary);
  text-decoration: none;
}
.back:hover { text-decoration: underline; }

.detail-head {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-xl);
  padding-bottom: var(--space-lg);
  border-bottom: 2px solid var(--ink-display);
  margin-bottom: var(--space-lg);
}

.lineup-head {
  font: 600 var(--t-3xs) / 1 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
  margin: var(--space-2xl) 0 var(--space-md);
}

.lineup-row {
  display: grid;
  grid-template-columns: 1fr 4rem 1fr;
  align-items: baseline;
  gap: var(--space-md);
  padding: var(--space-md) 0;
  border-bottom: 1px solid var(--line);
}

.lineup-slot {
  font: 500 var(--t-3xs) / 1 var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  text-align: center;
}

.lineup-side { display: flex; align-items: baseline; gap: var(--space-md); min-width: 0; }
.lineup-side.right { flex-direction: row-reverse; }

.lineup-name {
  font-size: var(--t-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lineup-name.empty { color: var(--muted); font-style: italic; }

.lineup-pts {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--t-xs);
  font-weight: 600;
  flex: none;
}

/*
 * Why a penalised row was hit, named in words. Same quiet-caption idiom as
 * .lrow .lbl .cap and .pool-row .pool-cap: the smallest type size the file
 * has, not a new one, and the muted colour instead of the pen tag's - it
 * reads after the +20/not started tag, subordinate to it rather than
 * competing with it.
 */
.pen-reason {
  font-size: var(--t-3xs);
  color: var(--muted);
  flex: none;
}

/* --- The drill-down on a phone --------------------------------------
 *
 * .detail-head's 1fr 1fr is the single worst rule in this file at 390px. The
 * left half holds .teams, itself a three-column grid, so the name track
 * collapsed to roughly 40px - and .tname carries overflow-wrap:anywhere, so
 * "LilDaveIII" broke into one fragment per line while the ladder in the right
 * half ran off the screen. Stacking the two halves is the whole fix: .tname
 * gets the full width back and never reaches for `anywhere` at all.
 */
@media (max-width: 40rem) {
  .detail-head {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
  }
}

/*
 * The lineup row stays two columns on a phone, because the two columns ARE the
 * point: this is a head-to-head, and a manager reads it by comparing his QB to
 * the other guy's QB on one line. An earlier pass stacked the two sides to buy
 * width, which turned every row into a card holding two unrelated players and
 * lost the comparison the screen exists for. Same shape as Sleeper's own
 * scoreboard, and as this site's desktop layout: name and points either side of
 * a slot label, points innermost so the two columns of numbers flank the middle.
 *
 * What actually made 390px unreadable was never the two columns - it was
 * cramming FOUR things into each ~160px side: a name, a score, a +20 tag and a
 * "scored 0" caption. Names ellipsised to "Kim..." and the tag spilled into the
 * slot column as "RBscored 0". So the row keeps its shape and the side wraps
 * instead: name and points on the first line, tag and caption on a second,
 * which costs a line only on the rows that carry a penalty.
 */
@media (max-width: 34rem) {
  .lineup-row {
    grid-template-columns: 1fr 2.1rem 1fr;
    gap: var(--space-sm);
    padding: var(--space-md) 0;
  }

  .lineup-slot { font-size: var(--t-3xs); }

  .lineup-side {
    flex-wrap: wrap;
    gap: 0 var(--space-sm);
  }

  /* An empty flex item 100% wide forces a line break exactly where it sits in
   * the `order` sequence, which is how the tag and its caption get a line of
   * their own without wrapping them in a container the markup does not have.
   * It is zero-height, so a row with no penalty pays nothing for it. */
  .lineup-side::after {
    content: "";
    flex-basis: 100%;
    height: 0;
    order: 3;
  }
  .lineup-side .lineup-name { order: 1; }
  .lineup-side .lineup-pts { order: 2; }
  .lineup-side .pen { order: 4; }
  .lineup-side .pen-reason { order: 5; }

  /* The name takes the slack so the points sit against the slot column, and
   * wraps rather than ellipsising - two lines of a real name beat one line of
   * "Washington Comm...". */
  .lineup-name {
    /* flex-basis 0, not auto: on `auto` the basis is the name's own content
     * width, so "Washington Commanders" claimed the whole line and shoved its
     * score onto the next one while the opposing side kept both on the first.
     * From 0 the name grows into whatever is left and wraps INSIDE its own
     * box, which keeps every score on the row's first line. */
    flex: 1 1 0;
    min-width: 0;
    font-size: var(--t-2xs);
    line-height: 1.35;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }

  .lineup-pts { font-size: var(--t-2xs); }
}
