:root {
  /* Eagles kelly green. Surfaces are lifted well off black and tinted green
     so the page reads brighter without losing the dark-UI contrast that a
     dense stats table needs. */
  --bg: #15231c;
  --panel: #1f3228;
  --panel-2: #294034;
  --line: #3b5a49;
  --line-soft: #2f4a3b;
  --text: #f2faf5;
  --muted: #b3cbbe;
  --dim: #83a08d;

  /* Kelly green (Pantone 348 family) for brand and interaction. */
  --accent: #00b04f;
  --accent-deep: #046a38;
  --accent-soft: #0d3a24;

  /* Positive/negative stay semantic. `--green` is deliberately limier than
     the brand kelly so a "good number" never reads as mere chrome. */
  --green: #5ee08a;
  --red: #ff6f66;
  --amber: #e8b13f;

  --mlb: #e0664a;
  --nba: #d9963a;
  --nfl: #00b04f;
  --radius: 10px;

  /* ---------- type scale ----------
     A blind review counted fourteen font sizes and thirty-four text styles on
     one page. Some of that was genuine variety; most of it was 11 beside 11.5,
     12 beside 12.5, 13 beside 13.5 — differences too small to read as
     hierarchy and big enough to read as sloppiness.

     Worse, the sheet ran two unit systems at once. Fourteen sizes were in px
     and seventeen more in rem or em, so `0.86em` inside a 14px cell produced
     12.04px and nobody chose that number. Two of them compounded off their
     parent, which is why the page carried sizes like 17.415px.

     Seven steps, all px, all named. A new rule picks a step; it does not
     invent a size. */
  --fs-micro: 10px;   /* badges, sub-figures, the smallest marks */
  --fs-label: 11px;   /* uppercase labels, table micro-copy */
  --fs-small: 12px;   /* secondary text, hints, metadata */
  --fs-table: 13px;   /* table body, tabs, controls */
  --fs-body: 14px;    /* running text */
  --fs-lead: 16px;    /* a name or lede that leads a card */
  --fs-title: 26px;   /* the page's own name — outranks everything on it */
  --fs-h2: 21px;      /* section titles inside a page */

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }

/* ARIAL IN THE MOST PROMINENT CONTROL ON THE SITE. Form elements do not
   inherit font-family, so the header search box and the MLB/NBA/NFL switcher
   rendered in the UA default while all 212 other runs of text used the system
   stack. Nobody chose Arial; nobody had said not to. */
input, select, textarea, button { font-family: inherit; }

/* THE LAST RAW BROWSER CONTROLS ON THE SITE. Two checkboxes — "show near
   misses" and "hot bats" — shipped at appearance:auto, 13x13, in system blue,
   inside a 20px-tall label. Every other control had been dressed; these were
   missed because a checkbox looks deliberate even when nothing has styled it.

   The native input is KEPT rather than replaced with a styled span: it is the
   one control here that a screen reader, a keyboard and a form all already
   understand, and re-implementing that to change a colour would be a bad
   trade. accent-color does the colour, and the label carries the tap target —
   20px was under any reasonable minimum for something meant to be tapped. */
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--accent);
  width: 15px;
  height: 15px;
  margin: 0;
  cursor: pointer;
}
label:has(> input[type="checkbox"]),
label:has(> input[type="radio"]) {
  min-height: 32px;
  cursor: pointer;
}

/* HEADINGS HAD NO SIZE OF THEIR OWN. Not one rule in this sheet sized a bare
   h1, h2 or h3, so they fell through to the browser default — which is stated
   in `em` and therefore multiplies whatever it is nested in. An h2 inside a
   13px card computed 1.5 x 13 = 19.5px; the same tag in a 14px context
   computed something else again. That is the whole source of 17.415px and
   20.25px, and it is why removing the two `em` rules did not finish the job:
   the compounding was in the defaults, not in the sheet.

   Sized from the scale, so a heading is the same heading wherever it lands.
   Weight and margins left to the UA and to the class rules that already
   override these. */
h1 { font-size: var(--fs-h2); }
h2 { font-size: var(--fs-lead); }
h3 { font-size: var(--fs-body); }

/* ---------- top bar ---------- */

.topbar {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 12px 20px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 50;
}

.brand { display: flex; align-items: center; gap: 9px; flex: none; }
/* The mark is the logo now, not a drawn square. object-fit keeps it
   from stretching if the source is ever not square. */
.brand-mark {
  width: 28px; height: 28px; flex: none;
  object-fit: contain; display: block;
}
.brand-name { font-weight: 650; letter-spacing: -0.2px; }

.search-wrap { position: relative; flex: 1; max-width: 620px; }

#search {
  width: 100%;
  padding: 9px 34px 9px 13px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--text);
  font-size: var(--fs-body);
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}
#search:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0, 176, 79, .18);
}
#search::-webkit-search-cancel-button { -webkit-appearance: none; }

.search-hint {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  font: 11px var(--mono); color: var(--dim);
  border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px;
  pointer-events: none;
}
#search:focus + .search-hint { opacity: 0; }

.search-results {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 16px 44px rgba(0, 0, 0, .55);
  max-height: 62vh; overflow-y: auto; z-index: 60;
  padding: 6px;
}

.sr-group { padding: 7px 10px 4px; font-size: var(--fs-label); letter-spacing: .07em;
  text-transform: uppercase; color: var(--dim); }
.sr-item {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; border-radius: 7px; cursor: pointer;
}
.sr-item:hover, .sr-item.sel { background: rgba(0, 176, 79, .18); }
.sr-name { flex: 1; }
.sr-meta { color: var(--muted); font-size: var(--fs-small); }
.sr-empty { padding: 16px; color: var(--muted); text-align: center; }
/* On the published site a result does not go anywhere -- it IS the answer.
   A pointer cursor there promises a click that does nothing. */
.sr-flat { cursor: default; }

.leagues { display: flex; gap: 4px; flex: none; }
.leagues button {
  background: transparent; border: 1px solid transparent; color: var(--muted);
  padding: 6px 11px; border-radius: 7px; cursor: pointer;
  font-size: var(--fs-small); font-weight: 550; letter-spacing: .02em;
}
.leagues button:hover { color: var(--text); background: var(--panel-2); }
.leagues button.active { color: var(--text); background: var(--panel-2); border-color: var(--line); }

/* ---------- tabs ---------- */

.tabs {
  display: flex; gap: 2px; padding: 0 20px;
  background: var(--panel); border-bottom: 1px solid var(--line);
  position: sticky; top: 57px; z-index: 40; overflow-x: auto;
}
/* ONE TAB, NOT TWO. `.tabs a` (league nav: 13px, weight 550, 2px rule) and
   `.match-tab` (section nav inside Matchups: 15px, weight 600, 3px rule) are
   the same control doing the same job — choose which view you are looking at —
   drawn at two sizes with two underline weights. The in-page one was the
   LARGER of the pair, so a section outranked the league containing it.

   A blind review counted four navigation components on one page. These are
   two of them, now one. */
.tabs a,
.match-tab {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-table);
  font-weight: 600;
  padding: 10px 14px;
  white-space: nowrap;
  cursor: pointer;
  transition: color .12s, border-color .12s;
}
/* The cue that there are more tabs off the edge. A mask rather than an
   overlaid gradient, because the bar is sticky and scrolls under the header:
   an absolutely positioned strip would have to track both. Applied only on the
   side that actually has more, so reaching the end removes it. */
.tabs.more-right {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent);
}
.tabs.more-left {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 34px), transparent);
  mask-image: linear-gradient(to left, #000 calc(100% - 34px), transparent);
}
.tabs.more-left.more-right {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 34px,
                      #000 calc(100% - 34px), transparent);
  mask-image: linear-gradient(to right, transparent, #000 34px,
              #000 calc(100% - 34px), transparent);
}

.tabs a:hover,
.match-tab:hover { color: var(--text); }
.tabs a.active,
.match-tab.active { color: var(--text); border-bottom-color: var(--accent); }

/* ---------- layout ---------- */

main { padding: 22px 20px 60px; max-width: 1400px; margin: 0 auto; }

.page-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin-bottom: 18px; flex-wrap: wrap;
}
/* A PAGE TITLE THAT DOES NOT OUTRANK ITS SECTIONS IS NOT A TITLE. This was
   21px against a 14px body — a ratio of 1.5, which on a 1440px screen reads as
   a subheading, and it was the same size as the h2 of every card beneath it.
   The reviewer's phrasing was that the page still had no visible name. */
.page-title { font-size: var(--fs-title); font-weight: 650; letter-spacing: -.3px; margin: 0; }
.page-sub { color: var(--muted); font-size: var(--fs-table); margin-top: 3px; }

.controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

input[type="date"], select, .filter-input {
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  padding: 7px 10px; border-radius: 8px; font-size: var(--fs-table); outline: none;
  font-family: inherit;
}
input[type="date"]:focus, select:focus, .filter-input:focus { border-color: var(--accent); }
input[type="date"] { color-scheme: dark; }

button.btn {
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  padding: 7px 12px; border-radius: 8px; font-size: var(--fs-table); cursor: pointer;
  font-family: inherit;
}
button.btn:hover { background: var(--panel-2); border-color: var(--accent); }
/* `.primary` had NO RULE, so Contact's Send — the button that actually sends
   the message — rendered as a plain secondary button, 58px wide, beneath a
   141px newsletter button. The most important control on the page was the
   quietest and the smallest. Same fill and weight as .btn-primary so the site
   has one primary treatment rather than two that nearly match. */
button.btn.primary {
  background: var(--accent); border-color: var(--accent); color: #06210f;
  font-weight: 600; padding: 9px 18px;
  /* A minimum, because width otherwise just follows label length: "Send" came
     out 74px beside a 141px newsletter button, so the button that actually
     sends the message was the smaller of the two. Colour alone did not fix
     that — a short word stays small however it is filled. */
  min-width: 120px;
}
button.btn.primary:hover { background: var(--accent); filter: brightness(1.06); }

.linkish {
  background: none; border: none; color: var(--muted); cursor: pointer;
  font: inherit; font-size: inherit; padding: 0; text-decoration: underline;
  text-decoration-color: var(--line);
}
.linkish:hover { color: var(--text); }

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

.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.card + .card { margin-top: 16px; }
.card-head {
  padding: 12px 15px; border-bottom: 1px solid var(--line);
  font-weight: 600; font-size: var(--fs-table);
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.card-body { padding: 15px; }
.card-body.flush { padding: 0; }

.grid { display: grid; gap: 14px; }
.grid.games { grid-template-columns: repeat(auto-fill, minmax(370px, 1fr)); }
.grid.two { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

/* ---------- game card ---------- */

.game {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 14px; cursor: pointer;
  transition: border-color .15s, transform .1s;
}
.game:hover { border-color: var(--accent); }
.game:active { transform: translateY(1px); }

.game-top {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 11px; gap: 10px;
}
.game-status { font-size: var(--fs-small); color: var(--muted); }
.game-status.live { color: var(--green); font-weight: 600; }

.tag {
  font: 10.5px/1 var(--mono); font-weight: 700; letter-spacing: .06em;
  padding: 3px 6px; border-radius: 4px; text-transform: uppercase;
}
.tag.mlb { background: rgba(212, 85, 59, .17); color: #e8836c; }
.tag.nba { background: rgba(201, 133, 42, .17); color: #dfa855; }
.tag.nfl { background: rgba(0, 176, 79, .20); color: #57d98c; }

.team-row {
  display: flex; align-items: center; gap: 10px; padding: 5px 0;
}
.team-logo { width: 22px; height: 22px; object-fit: contain; flex: none; }
.team-name { flex: 1; font-size: var(--fs-body); }
.team-rec { color: var(--dim); font-size: var(--fs-small); font-family: var(--mono); }
.team-score { font: 600 16px var(--mono); min-width: 30px; text-align: right; }
.team-row.loser { opacity: .5; }

.game-odds {
  margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--line-soft);
  display: flex; gap: 14px; font-size: var(--fs-small); color: var(--muted);
  font-family: var(--mono); flex-wrap: wrap;
}
.game-odds b { color: var(--text); font-weight: 600; }
.probable { font-size: var(--fs-label); color: var(--dim); }
/* Conditions line sits between the odds and the probables; quieter than the
   odds above it so the card still reads top-down. */
.game-wx { padding-top: 8px; border-top: 1px solid var(--line-soft); font-size: var(--fs-label); }

/* ---------- tables ---------- */

/* Bounded box so the sticky header has a scrolling ancestor to stick to.
   With only `overflow-x`, the element is still a scroll container on both
   axes but never scrolls vertically, so `position: sticky` never engages and
   the header scrolls away with the page. */
.table-scroll {
  overflow: auto;
  max-height: calc(100vh - 13rem);
}

table { width: 100%; border-collapse: collapse; font-size: var(--fs-table); }
th, td { padding: 8px 11px; text-align: left; white-space: nowrap; }
thead th {
  color: var(--muted); font-weight: 600; font-size: var(--fs-label);
  letter-spacing: .04em; text-transform: uppercase;
  border-bottom: 1px solid var(--line); background: var(--panel);
  position: sticky; top: 0; z-index: 3; cursor: pointer; user-select: none;
  /* The header sits above rows that scroll under it, so it needs an opaque
     backdrop of its own rather than inheriting the page behind it. */
  box-shadow: inset 0 -1px 0 var(--line);
}
/* A batter's expanded batted-ball table is nested inside the outer one; its
   header shouldn't compete for the same sticky slot. */
.table-scroll table table thead th {
  position: static; z-index: auto; box-shadow: none;
}

/* Freeze the name column so scrolling right doesn't lose track of who each
   row is. `:not([colspan])` skips the batter board's expanded detail cell,
   which spans the whole table and must not be pinned. */
.table-scroll > table > tbody > tr > td:first-child:not([colspan]),
.table-scroll > table > thead > tr > th:first-child {
  position: sticky;
  left: 0;
  background: var(--panel);
  box-shadow: inset -1px 0 0 var(--line);
}
.table-scroll > table > tbody > tr > td:first-child:not([colspan]) { z-index: 2; }
/* The corner cell overlaps both sticky axes, so it sits above both. */
.table-scroll > table > thead > tr > th:first-child { z-index: 4; }

/* The pinned cell is opaque, so it needs the hover tint applied directly or
   the row appears to break at the first column. */
.table-scroll > table > tbody > tr:hover > td:first-child:not([colspan]) {
  background: var(--panel-2);
}
/* Keep the hard-hit accent rail visible alongside the frozen-column edge. */
.table-scroll > table > tbody > tr.hard-hit-row > td:first-child:not([colspan]) {
  box-shadow: inset 3px 0 0 var(--red), inset -1px 0 0 var(--line);
}
thead th:hover { color: var(--text); }
thead th.sorted { color: var(--accent); }
thead th .arrow { font-size: var(--fs-micro); margin-left: 3px; }
tbody tr { border-bottom: 1px solid var(--line-soft); }
tbody tr:hover { background: var(--panel-2); }
tbody tr.clickable { cursor: pointer; }
td.num, th.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
td.strong { font-weight: 600; }

.pos-badge {
  font: 10.5px/1 var(--mono); font-weight: 700; color: var(--muted);
  border: 1px solid var(--line); border-radius: 4px; padding: 2px 5px;
}

.pill {
  font-size: var(--fs-label); padding: 2px 7px; border-radius: 20px;
  background: var(--panel-2); color: var(--muted); border: 1px solid var(--line);
}
.pill.out { background: rgba(255, 111, 102, .18); color: #ff9d96; border-color: transparent; }
.pill.day { background: rgba(232, 177, 63, .18); color: #f0c766; border-color: transparent; }
.pill.ok { background: rgba(94, 224, 138, .18); color: #7cf0a8; border-color: transparent; }

.up { color: var(--green); }
.down { color: var(--red); }

/* Column heat map: green = favourable, red = unfavourable, ranked across
   the rows currently in the table. */
.heat-chip {
  display: inline-block;
  min-width: 3.1rem;
  padding: 2px 7px;
  border-radius: 5px;
  color: #fff;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ---------- batted-ball detail ---------- */

/* Exit velocity reads as a heat chip: the harder it was hit, the stronger
   the fill, so qualifying balls jump out of a long game log. */
.ev-chip {
  display: inline-block;
  min-width: 3.4rem;
  padding: 2px 7px;
  border-radius: 5px;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.ev-chip.ev-hot { color: #fff; font-weight: 700; }
/* Below the threshold stays plainly readable — the contrast should come from
   the hot chips standing out, not from the rest being hard to read. */
.ev-chip.ev-soft { color: var(--muted); }

.event-chip {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 5px;
  font-weight: 600;
  text-transform: capitalize;
}
/* Estimated columns read a shade quieter than measured ones, so the two
   aren't mistaken for each other at a glance. */
/* The reasoning column wraps instead of clipping — every other cell is a
   number that reads fine on one line, but this one is prose. */
td.why-cell {
  white-space: normal;
  min-width: 22rem;
  max-width: 30rem;
  vertical-align: top;
}
.why-text {
  display: block;
  color: var(--muted);
  font-size: var(--fs-small);
  line-height: 1.45;
}

/* ---------- watchlist player lookup ---------- */

.lookup-panel:not(:empty) { margin-bottom: 16px; }
.lookup-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 0 2px 8px;
}
.lookup-card { border-color: var(--accent-deep); }
.lookup-card .verdict { font-size: var(--fs-body); }
.lookup-card .statgrid { border-top: 1px solid var(--line-soft); }

.driver-list {
  margin: 6px 0 0; padding-left: 18px;
  color: var(--text); font-size: var(--fs-table); line-height: 1.7;
}

.refresh-wrap { display: inline-flex; align-items: center; gap: 8px; }
.freshness { white-space: nowrap; }

.est-val { color: var(--muted); font-style: italic; }
.est-note { color: var(--amber); }

.event-chip.event-hr {
  background: rgba(94, 224, 138, .26);
  color: #7cf0a8;
}
/* Deliberately not green: on a kelly-green page a green "hit" chip would read
   as chrome rather than as a result. */
.event-chip.event-hit {
  background: rgba(96, 165, 250, .18);
  color: #93c2fd;
}

/* A qualifying ball gets an accent rail rather than everything else being
   dimmed — the non-qualifying rows stay readable. */
tbody tr.hard-hit-row td:first-child {
  box-shadow: inset 3px 0 0 var(--red);
}

/* ---------- player / team pages ---------- */

.profile { display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
.profile img.headshot {
  width: 78px; height: 78px; border-radius: 50%; object-fit: cover;
  background: var(--panel-2); border: 1px solid var(--line);
}
.profile-name { font-size: var(--fs-h2); font-weight: 650; letter-spacing: -.3px; }
.profile-meta { color: var(--muted); font-size: var(--fs-table); margin-top: 4px; }

.statgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
  gap: 1px; background: var(--line-soft);
}
.stat { background: var(--panel); padding: 11px 13px; }
.stat-label { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .05em; color: var(--dim); }
.stat-value { font: 600 17px var(--mono); margin-top: 3px; }

/* ---------- misc ---------- */

.loading, .empty, .error {
  padding: 40px 20px; text-align: center; color: var(--muted);
}
.error { color: #ff9d96; }
.spinner {
  width: 17px; height: 17px; border: 2px solid var(--line);
  border-top-color: var(--accent); border-radius: 50%;
  animation: spin .7s linear infinite; display: inline-block; vertical-align: -3px;
  margin-right: 8px;
}
@keyframes spin { to { transform: rotate(360deg); } }

.foot {
  padding: 14px 20px; border-top: 1px solid var(--line);
  color: var(--dim); font-size: var(--fs-small); display: flex; gap: 9px; align-items: center;
  flex-wrap: wrap;
}
.sep { color: var(--line); }

.movement {
  font: 11px var(--mono); color: var(--muted);
}
.news-item { display: block; padding: 13px 15px; border-bottom: 1px solid var(--line-soft); }
.news-item:hover { background: var(--panel-2); }
.news-head { font-weight: 600; margin-bottom: 3px; }
.news-desc { color: var(--muted); font-size: var(--fs-small); }
.news-time { color: var(--dim); font-size: var(--fs-label); margin-top: 4px; }

@media (max-width: 720px) {
  .topbar { flex-wrap: wrap; gap: 10px; }
  .search-wrap { order: 3; flex-basis: 100%; max-width: none; }
  .grid.games { grid-template-columns: 1fr; }

  /* THE STICKINESS WAS THE WRONG WAY ROUND ON A PHONE.
     The header wraps to three rows here — logo, leagues, search — about 270px,
     a third of an iPhone viewport, and it was pinned. The tab bar, which is
     the actual navigation, was set to `position: static` and scrolled away.
     So a reader permanently surrendered a third of the screen to chrome they
     had finished with, and lost the control they still needed.

     Worse, a table's own <thead> is sticky at top:0 of its scroll box, and the
     pinned header sat on top of it — a blind review measured the column
     headers as permanently hidden underneath, so on a phone you scrolled
     through columns of unlabelled numbers.

     Now: the header scrolls away, the tabs pin in its place. */
  .topbar { position: static; }
  .tabs { position: sticky; top: 0; z-index: 45; }

  /* LET CELLS WRAP ON A PHONE. `white-space: nowrap` is right on a wide
     screen — it keeps numbers on one line and columns aligned — and it is what
     forced a four-column table to 658px inside a 333px window, so even the cut
     down board still could not be read without dragging it sideways.
     Wrapping costs vertical space, which a phone has, to buy horizontal space,
     which it does not. */
  th, td { white-space: normal; }
  /* Except the figures themselves: a score or a rate broken across two lines
     is worse than a slightly wider column. */
  td.num, th.num { white-space: nowrap; }
  /* The reasoning column asks for 22rem, which is the right ask on a laptop
     and 352px on a 375px phone — on its own it kept the four-column board
     wider than the screen. It gets whatever is left here instead. */
  td.why-cell { min-width: 0; max-width: none; }
}

/* --- DongWatchList methodology panel ------------------------------------ */
/* Collapsed by default: reference material the user opens on purpose. */
.method-panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  margin: 0 0 14px;
  overflow: hidden;
}
.method-panel > summary {
  cursor: pointer;
  padding: 11px 16px;
  font-weight: 600;
  font-size: var(--fs-body);
  color: var(--accent);
  list-style: none;
  user-select: none;
}
.method-panel > summary::-webkit-details-marker { display: none; }
.method-panel > summary::before {
  content: '▸';
  display: inline-block;
  margin-right: 8px;
  transition: transform 0.15s ease;
}
.method-panel[open] > summary::before { transform: rotate(90deg); }
.method-panel > summary:hover { background: var(--panel-2); }

.method-body {
  padding: 4px 18px 18px;
  border-top: 1px solid var(--line);
  font-size: var(--fs-table);
  line-height: 1.55;
  color: var(--muted);
  max-width: 62rem;
}
.method-body h4 {
  margin: 18px 0 6px;
  font-size: var(--fs-table);
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.method-body p { margin: 8px 0; }
.method-body ul { margin: 8px 0; padding-left: 20px; }
.method-body li { margin: 5px 0; }
.method-body strong { color: var(--text); }

/* Wraps rather than scrolls: the description column is prose, so making the
   table scroll sideways just hid it off the edge on a narrow screen. The two
   short columns stay on one line and the prose column takes the slack. */
.method-table {
  width: 100%;
  border-collapse: collapse;
  margin: 6px 0 4px;
  table-layout: fixed;
}
.method-table td:last-child, .method-table th:last-child { width: auto; }
.method-table td.m-what, .method-table th:first-child { width: 8.5rem; }
.method-table td.m-max, .method-table th:nth-child(2) { width: 4.5rem; }
@media (max-width: 560px) {
  .method-table td.m-what, .method-table th:first-child { width: 6.5rem; }
  .method-table td.m-what { white-space: normal; }
}
.method-table th {
  text-align: left;
  padding: 6px 10px 6px 0;
  font-size: var(--fs-small);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--dim);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.method-table td {
  padding: 7px 10px 7px 0;
  vertical-align: top;
  border-bottom: 1px solid var(--line-soft);
  /* The stats tables set cells to nowrap globally; this one is prose. */
  white-space: normal;
}
.method-table td.m-what { font-weight: 600; color: var(--text); white-space: nowrap; }
.method-table td.m-max  { color: var(--accent); font-weight: 600; white-space: nowrap; }

/* --- expandable rows + the last-three-games log ------------------------- */
tr.expandable { cursor: pointer; }
tr.expandable:hover { background: var(--panel-2); }
tr.expandable .expand-name { display: inline-flex; align-items: center; gap: 6px; }
tr.expandable .caret {
  color: var(--dim);
  font-size: var(--fs-small);
  transition: transform 0.15s ease;
  display: inline-block;
}
tr.expandable.open .caret { transform: rotate(90deg); color: var(--accent); }

tr.expando > td { padding: 0; background: var(--bg); }
tr.expando:hover { background: var(--bg); }

/* The detail cell spans every column, so on a wide table the game cards would
   land far off to the right and you'd have to scroll sideways to read them.
   Sticking it to the left edge of the scroll container keeps the whole log in
   view wherever the table is scrolled to. */
.game-log {
  position: sticky;
  left: 0;
  /* Narrower of a comfortable reading width and whatever the window allows,
     so the pinned block never runs past the right edge on a small screen. */
  /* Widened when the pitcher column arrived: three game cards each need to
     fit a seven-column table whose cells cannot wrap, and at the old width
     they overflowed their own card and drew over each other. */
  max-width: min(60rem, calc(100vw - 3rem));
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  padding: 13px 15px 11px;
}
.log-foot { position: sticky; left: 0; max-width: min(46rem, calc(100vw - 3rem)); }
.log-game {
  flex: 1 1 27rem;
  min-width: 0;
  /* Last-resort guard: if a very long name still overruns, the card scrolls
     on its own rather than spilling across the one beside it. */
  overflow-x: auto;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  padding: 9px 11px 4px;
}
.log-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; margin-bottom: 4px; }
.log-summary { font-size: var(--fs-label); color: var(--dim); margin-left: auto; }
.log-table { width: 100%; border-collapse: collapse; font-size: var(--fs-small); }
.log-table th {
  font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--dim); padding: 3px 6px 3px 0; border-bottom: 1px solid var(--line-soft);
}
.log-table td { padding: 4px 6px 4px 0; border-bottom: none; }
.log-table tr.log-hr td { color: var(--green); font-weight: 600; }
.log-tags { color: var(--accent); font-size: var(--fs-micro); letter-spacing: 0.03em; }
.log-foot { padding: 0 15px 12px; font-size: var(--fs-label); }

/* --- DongWatchList "just missed" section -------------------------------- */
.near-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 16px 15px 8px;
  margin-top: 6px;
  border-top: 1px solid var(--line);
  font-weight: 600;
  font-size: var(--fs-body);
  color: var(--muted);
}
.near-sub { font-weight: 400; font-size: var(--fs-small); color: var(--dim); }
/* The pitcher is the only free-text column, so it is the one that has to yield
   when space runs short. */
.log-table td.log-arm {
  color: var(--muted);
  white-space: nowrap;
  max-width: 9.5rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- DongWatchList: pin the batter alongside the score ------------------ */
/* The generic rule above freezes only the first column, which on this page is
   the Score — so scrolling right to read the "Why" kept the number and lost
   the name, which is the one thing you need to know whose row you are on.
   Both columns are pinned here. The score gets a fixed width so the batter's
   offset is deterministic rather than depending on the rendered digits. */
.watchlist-table > table > thead > tr > th:nth-child(1),
.watchlist-table > table > tbody > tr > td:nth-child(1):not([colspan]) {
  width: 4.6rem;
  min-width: 4.6rem;
}
.watchlist-table > table > thead > tr > th:nth-child(2),
.watchlist-table > table > tbody > tr > td:nth-child(2):not([colspan]) {
  position: sticky;
  left: 4.6rem;
  background: var(--panel);
  box-shadow: inset -1px 0 0 var(--line);
  z-index: 2;
}
.watchlist-table > table > thead > tr > th:nth-child(2) { z-index: 4; }
.watchlist-table > table > tbody > tr:hover > td:nth-child(2):not([colspan]) {
  background: var(--panel-2);
}

/* --- Hot bats: form-first view, with the cohort caveat alongside --------- */
.hot-note {
  padding: 0 15px 10px;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--dim);
  max-width: 58rem;
}
.watchlist-table td .needs-price { color: var(--amber); font-weight: 600; }

/* --- Pitch arsenal panel, inside the expanded row ----------------------- */
.arsenal {
  flex: 1 1 100%;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  padding: 9px 11px 7px;
  margin-bottom: 2px;
}
.arsenal-head { display: flex; align-items: baseline; gap: 4px; margin-bottom: 5px; }
.arsenal-table { max-width: 34rem; }
.arsenal-table td.strong { color: var(--text); font-weight: 600; }
.arsenal-table .thin { color: var(--dim); font-style: italic; font-size: var(--fs-label); }
.arsenal-note {
  margin-top: 6px; font-size: var(--fs-label); line-height: 1.45; color: var(--dim);
  max-width: 40rem;
}
/* A pick that scraped in deserves to look like one. */
.thin-clear { color: var(--amber); }

/* --- Pitch arsenal panel, inside the expanded row ----------------------- */
.arsenal {
  flex: 1 1 100%;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  padding: 9px 11px 5px;
  margin-bottom: 2px;
}
.arsenal-head { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 6px; margin-bottom: 5px; font-size: var(--fs-small); }
.arsenal-note { margin-left: auto; font-size: var(--fs-micro); color: var(--dim);
  text-transform: uppercase; letter-spacing: 0.04em; }
.arsenal-table { max-width: 34rem; }
.arsenal-table td.dim { color: var(--dim); }
.arsenal-table .thin { color: var(--dim); font-size: var(--fs-micro); font-style: italic; }

/* A figure shown for context that must not read as a recommendation. The
   pitcher trend used to be green when the last five starts were louder than
   the season, which invited exactly the bet the data argues against. */
.muted-note {
  color: var(--dim);
  font-size: var(--fs-table);
}

/* Email capture. Hidden unless MODI_SIGNUP_ENDPOINT is set in app.js — the
   [hidden] attribute does the work, this only styles it once shown. */
.signup {
  max-width: 640px;
  margin: 28px auto 8px;
  padding: 18px 20px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
}
.signup label {
  display: block;
  font-weight: 600;
  margin-bottom: 10px;
}
.signup-row { display: flex; gap: 8px; flex-wrap: wrap; }
/* Same reasoning as .btn-primary: the nudge's input was a bare browser field
   for exactly the same reason its button was a bare browser button. */
.signup-row input,
.field {
  flex: 1 1 240px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--bg);
  color: inherit;
  font: inherit;
}
.field:focus { outline: none; border-color: var(--accent); }
/* THE PRIMARY BUTTON, AS A CLASS RATHER THAN A DESCENDANT RULE. This lived
   only as `.signup-row button`, so the identical button in the floating nudge
   — same label, same job, same form handler — got no styling at all and
   rendered as a raw browser control: #efefef fill, 2px outset black bevel,
   Arial, square corners, on a dark green page. Styling by ancestor means every
   new form starts unstyled and nobody notices until someone looks. */
.signup-row button,
.btn-primary {
  padding: 9px 16px;
  border: 0;
  border-radius: 7px;
  background: var(--accent);
  color: #06210f;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.btn-primary:hover { filter: brightness(1.06); }
.btn-primary:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
/* The privacy line is a promise, not decoration. Small, but never hidden. */
.signup-privacy {
  margin: 10px 0 0;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--muted);
}
.signup-status { margin: 8px 0 0; font-size: var(--fs-table); color: var(--muted); }

/* Beta marker. Deliberately quiet — it is a statement of fact next to a title,
   not a warning label, and it should not be the loudest thing on the page. */
.beta-tag {
  margin-left: 10px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  vertical-align: middle;
}

/* Profiles — a card per player, its dimensions each carrying the persistence
   correlation that earned it a place. */
.prof-search { width: 100%; max-width: 28rem; margin: 0.75rem 0 1rem;
  padding: 0.5rem 0.75rem; border-radius: 6px;
  border: 1px solid var(--border); background: var(--bg-alt); color: var(--fg); }
.prof-list { display: grid; gap: 1rem; }
.prof-card { border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg-alt); padding: 0.9rem 1rem; }
.prof-head { display: flex; align-items: baseline; gap: 0.5rem;
  margin-bottom: 0.6rem; }
.prof-name { font-weight: 600; font-size: var(--fs-lead); }
.prof-body { display: grid; gap: 0.9rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.prof-group-title { font-size: var(--fs-small); text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--muted); margin-bottom: 0.35rem; }
.prof-dim { display: flex; justify-content: space-between; gap: 0.5rem;
  padding: 0.12rem 0; font-variant-numeric: tabular-nums; }
.prof-label { color: var(--muted); }
.prof-value { font-weight: 500; }
.prof-matchup { border-left: 2px solid var(--accent); padding-left: 0.75rem; }
.prof-note { margin-top: 0.5rem; font-size: var(--fs-small); color: var(--muted);
  line-height: 1.45; }

/* Coverage splits — a small table rather than label/value rows, because the
   comparison is across coverages and the eye needs columns for that. */
.prof-split { grid-column: 1 / -1; margin-top: 0.3rem; }
.prof-table { width: 100%; border-collapse: collapse; font-size: var(--fs-table);
  font-variant-numeric: tabular-nums; }
.prof-table th { text-align: left; font-weight: 500; color: var(--muted);
  border-bottom: 1px solid var(--border); padding: 0.25rem 0.5rem 0.25rem 0; }
.prof-table td { padding: 0.22rem 0.5rem 0.22rem 0;
  border-bottom: 1px solid var(--border); }
.prof-table tr:last-child td { border-bottom: none; }
.boost-up { color: var(--good, #4ade80); }
.boost-down { color: var(--bad, #f87171); }

/* Charted against inferred — the reader should never have to guess which. */
.prof-badge-measured, .prof-badge-est { font-size: var(--fs-label); padding: 0.1rem 0.4rem;
  border-radius: 3px; text-transform: uppercase; letter-spacing: 0.04em; }
.prof-badge-measured { background: var(--accent, #2563eb); color: #fff; }
.prof-badge-est { background: var(--border); color: var(--muted); }
.prof-thin td { opacity: 0.62; }

/* Matchups — compact rows that open. The grade leads because it is the one
   number on the row that was actually built from measured things. */
/* The near-misses toggle shipped as a bare native checkbox. Dressed to match
   the site rather than left as a system control on a dark page. */
.nfl-check {
  appearance: none; -webkit-appearance: none;
  width: 1rem; height: 1rem; margin: 0;
  border: 1px solid var(--border); border-radius: 3px;
  background: var(--bg-alt); cursor: pointer; flex: none;
  display: inline-grid; place-content: center;
}
.nfl-check:checked { background: var(--accent, #2f855a); border-color: transparent; }
.nfl-check:checked::after {
  content: ''; width: 0.55rem; height: 0.3rem;
  border-left: 2px solid #fff; border-bottom: 2px solid #fff;
  transform: rotate(-45deg) translateY(-1px);
}
.nfl-check:focus-visible { outline: 2px solid var(--accent, #2f855a); outline-offset: 2px; }

/* PROSE MEASURE. Body text ran the full 1360px of the page — about 150
   characters a line, roughly twice the width the eye tracks comfortably — so
   five stacked paragraphs read as a grey wall rather than as writing. 68ch is
   the standard measure. Applied to NFL prose blocks only; the same problem
   exists elsewhere on the site and belongs to whoever owns those pages. */
.prose { max-width: 68ch; }
.prose p { margin: 0 0 0.6rem; }
.prose p:last-child { margin-bottom: 0; }

/* The badge reads as a warning, so it should look like a note rather than
   shout. Dotted underline matches the convention already used for a defined
   column header, so a reader who has learned it once knows to hover. */
.kickoff-badge {
  border-bottom: 1px dotted currentColor; cursor: help; letter-spacing: 0.04em;
}

/* THE PORTHOLE. .table-scroll caps every table at calc(100vh - 13rem) so a
   long board scrolls inside its own box. That is right for a board somebody
   skims and wrong for this one: a reviewer found 111 rows behind an 11-row
   window, with scrollbars on both axes nested inside the page scroll, and no
   way to tell how much was below. The Matchups table IS the page, so it should
   scroll with the page.

   Scoped to .match-scroll rather than relaxing .table-scroll, which MLB and NBA
   boards also use and which is not mine to change. Horizontal scrolling stays,
   because eleven columns genuinely do not fit a phone. */
.match-scroll { max-height: none; overflow-y: visible; }

.match-table { width: 100%; border-collapse: collapse; }
.match-table th { text-align: left; font-weight: 500; color: var(--muted);
  font-size: var(--fs-small); text-transform: uppercase; letter-spacing: 0.04em;
  padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--border); }
.match-table th.num, .match-table td.num { text-align: right; }
.match-row { cursor: pointer; }
.match-row td { padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--border);
  font-variant-numeric: tabular-nums; }
.match-row:hover td { background: var(--bg-alt); }
.match-row.open td { background: var(--bg-alt); }
.match-caret { display: inline-block; width: 1rem; color: var(--muted); }
.grade { display: inline-block; min-width: 2.2rem; padding: 0.1rem 0.4rem;
  border-radius: 4px; font-weight: 600; text-align: center; }
.grade-hi { background: rgba(74, 222, 128, 0.18); color: #4ade80; }
.grade-mid { background: rgba(148, 163, 184, 0.16); color: var(--fg); }
.grade-lo { background: rgba(148, 163, 184, 0.08); color: var(--muted); }
.match-detail-row td { padding: 0; border-bottom: 1px solid var(--border); }
.match-detail { display: grid; gap: 1rem; padding: 0.9rem 1.2rem 1.1rem 2rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  background: var(--bg-alt);
  /* THE OVERLAP, AND IT WAS NEVER min-width. Line 379 sets `th, td` to
     white-space: nowrap site-wide, which is right for a stats cell and wrong
     for everything in here: the drawer lives inside a <td colspan>, so every
     paragraph of the write-up inherited nowrap and rendered as ONE LINE.
     Measured on CeeDee Lamb: the prose box is 517px wide and its content was
     3217px, so it ran straight across the two columns to its right and the
     "Against what they favour" numbers painted on top of it.
     A width sweep never reproduced it because nowrap is not width-dependent —
     it overflows at every width, and I was looking for a breakpoint.
     Same precedent as .method-table, which overrides this for the same reason. */
  white-space: normal; }
/* The row wraps; each MEASURE inside it does not. A split row reads
   "Cover 1 · 0.4 tgt/rte +54% · 4.43 yd/rte +81% · 67 rte · they play it
   29.3%, 2 of 32" — 449px of it in a 277px track. Wrapping between the
   measures costs a second line; wrapping inside one puts "4.43" on one line
   and "yd/rte +81%" on the next, which is unreadable. So the flex row and the
   value both wrap, and only the leaf spans hold. */
.match-detail .prof-dim { flex-wrap: wrap; }
.match-detail .prof-label { white-space: nowrap; }
/* nowrap on the leaf spans was not enough on its own. The spans sit flush
   against each other in the DOM — the separator is INSIDE a span, so there is
   no whitespace between them — and adjacent nowrap runs with no break
   opportunity between them concatenate into one unbreakable 465px line inside
   a 265px track. Laying the value out as its own wrapping flex row gives the
   breaks back between measures while each measure still holds. */
.match-detail .prof-value { display: flex; flex-wrap: wrap;
  justify-content: flex-end; column-gap: 0.3rem; }
.match-detail .prof-value > span { white-space: nowrap; }
.credit-note { font-size: var(--fs-small); opacity: 0.75; border-left: 2px solid var(--border);
  padding-left: 0.6rem; }


/* The delayed signup prompt. Fixed to the bottom, out of the way of the
   tables, and narrow enough on a phone that it never covers a row being read.
   It is dismissible on purpose -- see the note in index.html. */
.nudge {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 14px;
  z-index: 40; max-width: min(680px, calc(100vw - 24px));
  background: var(--card, #16241c); border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 10px; padding: 10px 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
}
.nudge-form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.nudge-copy { flex: 1 1 220px; font-size: var(--fs-table); }
.nudge input { flex: 1 1 160px; min-width: 0; }
/* 17x16px was the whole target for "no thanks". On a phone that is a miss
   waiting to happen, on the one control whose entire job is to let someone
   decline. 44px is the smallest thing a thumb reliably hits. */
.nudge-close {
  background: transparent; border: 0; color: var(--muted); cursor: pointer;
  font-size: var(--fs-lead); line-height: 1;
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  margin: -8px -6px -8px 0;      /* grows the target, not the layout */
}
.nudge-close:hover { color: var(--text); }
.nudge-status { margin: 6px 0 0; font-size: var(--fs-small); color: var(--muted); }
@media (max-width: 520px) {
  .nudge-copy { flex-basis: 100%; }
}

/* Freshness of the charted exports. Quiet when current, loud when overdue —
   a static page can be read long after it was built. */
.freshness { font-size: var(--fs-small); opacity: 0.7; }
.freshness.stale { opacity: 1; color: var(--bad, #f87171); font-weight: 500; }

/* ---------------------------------------------------------------------------
   MATCHUPS
   Rebuilt after the page was called an eyesore, which it was: twelve columns
   of equal weight, two rows of identical chips with no way to tell which tab
   you were on, and a five-sentence methodology paragraph above the data.

   The structure borrows from how the established matchup tools solve it —
   a band grouping the columns by subject, colour on only the columns a reader
   decides from, and the explanation folded away rather than stacked on top.
   --------------------------------------------------------------------------- */

/* The stat tabs are the primary control, so they read as tabs and not as the
   chips used for incidental filters elsewhere. */
/* LEVEL THREE IS A SEGMENTED CONTROL, NOT A SECOND ROW OF TABS.
   ------------------------------------------------------------------
   The re-review counted FIVE control families in the first 340px of NBA and
   NFL Matchups, "two pairs styled identically" — and I made that pair
   identical, collapsing five CSS components into two. That fixed the original
   finding (four looks for one job) and created its mirror image: two rows of
   underline tabs with nothing to say which one is above the other.

   The site has four control jobs, and they are genuinely different:

     league     which sport            header pills
     page       which board            underline tabs   .tabs a
     section    which stat on it       SEGMENTED        .match-tab
     filter     which rows / columns   pills            .match-subtab, .chip

   So the answer is neither five looks nor one, but four — one per job, related
   by shared radius, type and colour, and distinguishable at a glance. A
   segmented control reads as "choose one of these, inside the thing you are
   already in", which is exactly what a stat tab is under a page tab. */
.match-tabs {
  display: inline-flex;
  gap: 0;
  margin: 18px 0 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
  flex-wrap: wrap;
}
/* .match-tab shares the shared tab rule's type and colour, and departs from it
   in form: no underline, joined into a group. Same family, different level. */
.match-tab {
  border-bottom: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  padding: 8px 16px;
}
.match-tab:last-child { border-right: 0; }
.match-tab.active {
  background: var(--panel-2);
  color: var(--text);
  border-bottom-color: transparent;
}
.match-tab:hover { background: var(--panel-2); }

/* Position is a filter within the tab, so it sits below and reads lighter —
   but still filled when active, because "which position am I looking at" was
   the other half of what nobody could tell. */
.match-subtabs { display: flex; gap: 6px; margin: 12px 0 0; flex-wrap: wrap; }
/* ONE TOGGLE, NOT THREE. `.match-subtab` (13px), `.match-preset` (12px) and
   `.chip` (12px, different padding) are one control — a filter you switch on —
   drawn three times at three sizes.

   Worse, they disagreed about what ON looks like. Subtab and preset tinted;
   `.chip` took the SOLID accent fill that is supposed to mean "this button
   does something". So the same state rendered two different ways depending on
   which page the reader was standing on, and on Profiles a row of switched-on
   filters outshouted the page's only real button.

   One shape, one size, one active state, and it is the tint: a page can carry
   a dozen of these and exactly one button, and the button has to win. */
.chip,
.match-subtab,
.match-preset {
  appearance: none;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-small);
  font-weight: 600;
  padding: 5px 14px;
  white-space: nowrap;
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
/* THUMB-SIZED ON A PHONE. These come out 30px tall from padding alone, which
   clears WCAG 2.2's 24px minimum and is still a small target for a thumb on a
   board where the controls sit in a row. Raised on narrow screens only, so
   desktop density is untouched, and as a MINIMUM rather than a fixed height so
   nothing that already wraps or grows is clipped. Padding stays as it was:
   min-height with centred content moves nothing horizontally, which is what
   keeps this safe for the MLB and NFL boards that share these classes. */
@media (max-width: 720px) {
  .chip,
  .match-subtab,
  .match-preset {
    min-height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

.chip:hover,
.match-subtab:hover,
.match-preset:hover { border-color: var(--line); color: var(--text); }
.chip.active,
.match-subtab.active,
.match-preset.active {
  background: rgba(0, 176, 79, .15);
  border-color: var(--accent);
  color: var(--accent);
}

.match-context { margin: 14px 0 10px; font-size: var(--fs-table); line-height: 1.5; }
.match-context-main {
  color: var(--text);
  font-weight: 700;
  font-size: var(--fs-body);
  letter-spacing: 0.01em;
}

/* Folded away, not deleted. The reasoning is the product and it stays one
   click from the number it explains. */
.match-method {
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  margin: 0 0 14px;
}
.match-method > summary {
  cursor: pointer;
  padding: 9px 14px;
  color: var(--muted);
  font-size: var(--fs-table);
  font-weight: 600;
  list-style: none;
}
.match-method > summary::-webkit-details-marker { display: none; }
.match-method > summary::before { content: '▸ '; color: var(--dim); }
.match-method[open] > summary::before { content: '▾ '; }
.match-method > summary:hover { color: var(--text); }
.match-method-body { padding: 0 14px 12px; }
.match-method-body p {
  margin: 0 0 9px;
  color: var(--muted);
  font-size: var(--fs-table);
  line-height: 1.6;
  max-width: 78ch;
}

/* The band that says which subject a run of columns belongs to. */
.match-table thead .band-row th {
  font-size: var(--fs-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 700;
  padding: 5px 10px;
  text-align: left;
  border-bottom: 1px solid var(--line);
  color: var(--dim);
  background: var(--bg);
}
.match-table thead .band-grade {
  color: var(--accent);
  background: var(--accent-soft);
}
.match-table thead .band-opponent { color: var(--muted); background: var(--panel); }

.match-table th.sortable { cursor: pointer; user-select: none; }
.match-table th.sortable:hover { color: var(--text); }
.match-table th.sorted { color: var(--accent); }
.sort-mark { font-size: var(--fs-micro); margin-left: 4px; opacity: 0.9; }
.row-rank { color: var(--dim); font-variant-numeric: tabular-nums; }

/* Dismiss control on the NBA Matchups board. Nearly invisible until the row is
   hovered: it sits in every row, and a column of hard crosses would pull the
   eye away from the numbers the table exists to show. */
/* VISIBLE AT REST, not only on hover.
   This was `color: transparent`, revealed by `tr:hover`. Keyboard and screen
   reader users were fine — there is an aria-label and a :focus-visible rule —
   but for everyone else the control did not exist until the pointer happened
   to cross its row, and on a touch screen there is no hover at all, so it
   never appeared. A blind reviewer counted them as invisible buttons and was
   right: 44 controls on one board that nobody could find.
   --dim measures 4.77:1 against the row background, so it clears AA as text
   while staying the quietest of the three tokens — present, and still
   subordinate to the data it sits beside. Hover and focus lift it to --text
   as before. */
.row-hide {
  background: transparent; border: 0; color: var(--dim);
  cursor: pointer; font-size: var(--fs-body); line-height: 1;
  padding: 0 4px; border-radius: 5px;
}
.row-hide:hover { color: var(--text); background: var(--panel-2); }
/* Keyboard users never trigger :hover, so without this the control is
   invisible to them even while focused. */
.row-hide:focus-visible { color: var(--text); outline: 1px solid var(--line); }

/* Quiet button for a card header — the way back from a hidden row. */
.ghost-btn {
  background: transparent; border: 1px solid var(--line); color: var(--muted);
  padding: 4px 9px; border-radius: 7px; cursor: pointer;
  font-size: var(--fs-label); font-weight: 550; white-space: nowrap;
}
.ghost-btn:hover { color: var(--text); background: var(--panel-2); }

/* The opponent's share of the grade, on its own. Filled only at the ends —
   a pill on every row would be thirty-two shades of nothing. */
.match-pill {
  display: inline-block;
  min-width: 30px;
  padding: 2px 7px;
  border-radius: 5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--panel-2);
  color: var(--muted);
}
.match-pill.soft { background: rgba(94, 224, 138, 0.20); color: var(--green); }
.match-pill.tough { background: rgba(255, 111, 102, 0.16); color: var(--red); }

/* WHAT A DEFENCE ALLOWS: top eight and bottom eight only. The middle sixteen
   stay unpainted, which is the whole reason the ends are legible. */
.allow-cell {
  display: inline-block;
  padding: 2px 7px;
  border-radius: 5px;
  font-variant-numeric: tabular-nums;
}
.allow-cell.soft { background: rgba(94, 224, 138, 0.16); color: var(--green); }
.allow-cell.tough { background: rgba(255, 111, 102, 0.13); color: var(--red); }
/* Three steps inside each extreme, so rank 1 does not look like rank 8. The
   flat version painted the whole top third one colour, which told a reader
   "this is in the top eight" and nothing more — the number was already saying
   that, better. Steps rather than a continuous ramp: eight adjacent shades of
   green are not distinguishable side by side, three are. */
/* TEXT BRIGHTENS WITH THE FILL. A single ramp, not two: the block that used
   to follow this one was labelled "dark mode" and sat in no media query at
   all, so it silently overrode these for every reader. Measured on the real
   page rather than assumed — four of the six steps were failing WCAG AA at
   3.47 to 4.44 against a 4.5 requirement, which is the same fault the review
   found in the pills and I had just reintroduced. */
.allow-cell.soft.s1 { background: rgba(94, 224, 138, 0.12); }
.allow-cell.soft.s2 { background: rgba(94, 224, 138, 0.22); color: #d8ffe6; }
.allow-cell.soft.s3 { background: rgba(94, 224, 138, 0.38); color: #ffffff; }
.allow-cell.tough.t1 { background: rgba(255, 111, 102, 0.07); }
.allow-cell.tough.t2 { background: rgba(255, 111, 102, 0.20); color: #ffe0dd; }
.allow-cell.tough.t3 { background: rgba(255, 111, 102, 0.34); color: #ffffff; }
/* INHERIT, rather than --dim. This word sits INSIDE a tinted chip, and
   --dim is tuned against the page ground, so it landed at 2.85:1 against the
   chip it actually sits on — 48 failures on NBA Matchups, every one of them
   this class. The chip's own colour is already tuned per intensity (.soft,
   .s2, .s3 each override it), so inheriting makes the label exactly as
   legible as the number beside it, and stays correct if those tints are
   ever retuned. It reads as secondary through size, which is what --dim was
   really being asked to do. */
.allow-rank { color: inherit; font-size: var(--fs-label); margin-left: 5px; }

/* ---------- injury impact ---------- */

.injury-block { border-top: 1px solid var(--line); }
.injury-block:first-child { border-top: 0; }
.injury-head {
  margin: 0; padding: 13px 15px 9px;
  font-size: var(--fs-table); font-weight: 600;
  display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
}
/* A gain and a loss are equally worth reading here, so both are tinted. The
   thresholds are +/-20%, well clear of the league-wide drift of about +6%,
   which would otherwise light up almost every row and mean nothing. */
.pct { font-variant-numeric: tabular-nums; padding: 2px 6px; border-radius: 5px; }
.pct.soft { background: rgba(94, 224, 138, 0.16); color: var(--green); }
.pct.tough { background: rgba(255, 111, 102, 0.13); color: var(--red); }
.pad { padding: 14px 15px; }
/* CURRENT injury status, on a board where every other number is history.
   Deliberately unlike the green/red used for measured changes — those mean
   "good or bad for this player" and this means "is he playing tonight",
   which is a different kind of claim and should not borrow their colours. */
.inj-badge {
  display: inline-block; margin-left: 6px; padding: 1px 6px;
  border-radius: 5px; font-size: var(--fs-micro); font-weight: 650;
  letter-spacing: .02em; text-transform: uppercase; vertical-align: 1px;
}
.inj-out { background: rgba(255, 111, 102, 0.20); color: #ffd9d5; }
.inj-maybe { background: var(--panel-2); color: var(--muted); }
/* Profile cells carry a name and its defining number. On one line they were a
   third of the table's width; stacked, the column halves and the name leads,
   which is the order a reader wants anyway. */
.prof-cell { display: inline-block; white-space: normal; max-width: 150px; line-height: 1.3; }
.prof-axis { display: block; font-size: var(--fs-label); color: var(--dim); font-weight: 400; }
/* Let a narrow table be narrow. Tables fill their container by default, which
   is right for a nineteen-column board and wrong for a two-column one — the
   play-type table was stretched across 1328px with the numbers marooned at
   the far edge from their labels. Capped, not centred: it stays aligned with
   everything else in the card. */
.narrow-table table { width: auto; min-width: min(560px, 100%); }
.narrow-table .table-scroll { overflow-x: visible; }
/* The same rule, applied by table() itself on any board of six columns or
   fewer, so a narrow table stops being stretched to the width of the widest
   one on the page. Capped rather than centred: it stays left-aligned with
   every heading and card above it. */
.table-scroll.narrow table { width: auto; min-width: min(560px, 100%); }
.table-scroll.narrow { overflow-x: auto; }
/* A control that cannot act should look like one. Kept visible rather than
   removed, so the reader learns the window exists and will work. */
.match-subtab.is-disabled { opacity: .45; cursor: not-allowed; }
.match-subtab.is-disabled:hover { background: transparent; }
/* Said once, near the top, instead of at the end of a grey sentence. */
.season-note {
  margin: 10px 0 0; padding: 9px 13px; border-radius: 8px;
  background: var(--panel-2); border: 1px solid var(--line);
  font-size: var(--fs-small); line-height: 1.55; color: var(--muted);
}
.season-note strong { color: var(--text); }
/* The column the board is ranked on reads as primary; the raw per-game change
   beside it is context. Before this the honest number sat to the right in grey
   while the misleading one carried all the colour. */
.pct.pct-lead { font-weight: 650; }
/* A per-game move the per-36 does not back. Deliberately drained of colour:
   it is a real number and a poor reason to act, so it should not compete for
   the eye with the ones that are both. */
.pct-minutes { color: var(--dim); background: transparent; }
.pct-note { font-size: var(--fs-micro); letter-spacing: .04em; text-transform: uppercase; }

.match-table td { padding-top: 9px; padding-bottom: 9px; }
.match-legend { margin: 0 0 12px; font-size: var(--fs-small); line-height: 1.9; }
.match-legend .allow-cell { font-size: var(--fs-label); font-weight: 700; }

/* Sortability has to be visible before it is useful. An arrow on only the
   active column told a reader which one was sorted and never that the other
   ten could be. */
.sort-mark { color: var(--dim); opacity: 0.45; }
.sort-mark.on { color: var(--accent); opacity: 1; }
.match-table th.sortable:hover .sort-mark { opacity: 0.9; }

.match-toolbar { margin: 0 0 10px; }
.match-search {
  appearance: none;
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 7px;
  color: var(--text);
  font: inherit;
  font-size: var(--fs-table);
  padding: 7px 12px;
  width: 260px;
  max-width: 100%;
}
.match-search:focus {
  outline: none;
  border-color: var(--accent);
}
.match-search::placeholder { color: var(--dim); }

/* The matchup column is the one a reader decides from, so it carries a real
   fill rather than a tint. Everything else on the row stays quiet. */
.match-pill.soft { background: rgba(94, 224, 138, 0.30); color: #d8ffe6; }
.match-pill.tough { background: rgba(255, 111, 102, 0.26); color: #ffe0dd; }
.pct.soft { background: rgba(94, 224, 138, 0.26); color: #d8ffe6; }
.pct.tough { background: rgba(255, 111, 102, 0.22); color: #ffe0dd; }

/* Floors on grade and matchup, so the table can be cut to the best of both. */
.match-toolbar { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.match-filter {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-small);
  color: var(--muted);
  white-space: nowrap;
}
.match-filter label { display: inline-block; min-width: 78px; }
.match-filter b { color: var(--text); font-variant-numeric: tabular-nums; }
.match-range { width: 110px; accent-color: var(--accent); cursor: pointer; }
/* ROUNDED MEANS CHOOSE ONE, SQUARED MEANS SWITCH ON.
   .match-subtab picks a single position; .match-preset toggles several things
   independently — extra columns on NBA, filter presets on NFL. They were the
   other identical pair. The distinction is not decoration: a reader who has
   learned it once knows, without trying, whether clicking a second one will
   replace their choice or add to it. */
.match-preset { border-radius: 6px; }
.match-preset.active::before { content: '\2713\00a0'; }

.prof-note {
  color: var(--muted);
  font-size: var(--fs-small);
  line-height: 1.6;
  max-width: 46ch;
}

/* A profile name opens the class behind it. Underlined on hover only — every
   row has one, and a table of permanently underlined cells is a mess. */
.profile-link { color: var(--text); text-decoration: none; border-bottom: 1px dotted var(--line); }
.profile-link:hover { color: var(--accent); border-bottom-color: var(--accent); }


/* The batter's name opens his profile; the rest of the row still expands.
   Underlined only on hover so the board does not turn into a wall of links. */
.name-link { color: inherit; text-decoration: none; }
.name-link:hover, .name-link:focus { text-decoration: underline; }

/* A BANDED MATCHUPS TABLE PINS THE PLAYER, NOT THE RANK.
   .table-scroll freezes the first column, which on every other table is the
   name. Both banded tables — NFL Matchups and NBA Matchups — lead with a rank
   number instead, so pinning the first cell keeps "1" on screen and scrolls the
   player away, which is the one thing a reader needs to hold on to. The rank
   gets a fixed width so the name can be pinned at a known offset beside it.

   Scoped to `.table-scroll > .match-table` rather than to one page: `.match-
   table` is worn only by a table with grouped columns, and every such table
   leads with a rank. The NFL one was hand-built and the NBA one goes through
   table(); both had the same fault and only one had been found. */
.table-scroll > .match-table > thead > tr > th:first-child,
.table-scroll > .match-table > tbody > tr > td:first-child:not([colspan]) {
  width: 42px;
  min-width: 42px;
  max-width: 42px;
}
.table-scroll > .match-table > thead > tr > th:nth-child(2),
.table-scroll > .match-table > tbody > tr > td:nth-child(2):not([colspan]) {
  position: sticky;
  left: 42px;
  background: var(--panel);
  box-shadow: inset -1px 0 0 var(--line);
}
.table-scroll > .match-table > tbody > tr > td:nth-child(2):not([colspan]) {
  z-index: 2;
}
.table-scroll > .match-table > thead > tr > th:nth-child(2) { z-index: 4; }
.table-scroll > .match-table > tbody > tr:hover > td:nth-child(2):not([colspan]) {
  background: var(--panel-2);
}
/* The band row spans groups, so it must not be pinned or it tears in two. */
.table-scroll > .match-table > thead > tr.band-row > th { position: static; }

/* ---------- contact form ----------
   The only page on this site that asks for anything rather than showing
   something, so it is deliberately plain: full-width fields, one column on a
   phone, and no styling that makes a text box look like a button. */
/* Announced, not shown. Clipped to a single pixel rather than display:none or
   visibility:hidden, both of which take an element out of the accessibility
   tree — which would make a live region that never speaks. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* The per-batter batted-ball list inside an expanded row. Was an inline
   font-size:12.5px, which was one of the off-scale sizes; on the scale it is
   --fs-small. */
.detail-table { font-size: var(--fs-small); }

/* The way back to every column, on a phone. Reads as a control rather than a
   link, but stays quieter than the page's one real button. */
.col-toggle {
  appearance: none;
  margin: 10px 0 0;
  padding: 7px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text);
  font: inherit;
  font-size: var(--fs-small);
  font-weight: 600;
  cursor: pointer;
}
.col-toggle:hover { border-color: var(--accent); color: var(--accent); }

/* The line under a component's score saying what it is worth. A raw "6.2 /8"
   is true and unreadable when the 8 cannot be reached. */
.stat-note {
  margin-top: 2px;
  font-size: var(--fs-label);
  line-height: 1.4;
  color: var(--dim);
}

/* The key to the one shaded column. Sits under its table, quiet enough not to
   compete with the numbers and close enough to be read as part of them. */
.table-legend {
  margin: 8px 0 0;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--muted);
}

.form { max-width: 720px; }
.form-row { display: flex; gap: 14px; flex-wrap: wrap; }
.form-row > .form-field { flex: 1 1 260px; }
.form-field { margin-bottom: 14px; }
.form-field label {
  display: block; font-weight: 600; font-size: var(--fs-table); margin-bottom: 6px;
}
/* One of the two boxes under a single label — the First/Last pair. */
.form-sub { flex: 1 1 240px; }
/* THE SMALLEST, DIMMEST TEXT ON THE SITE WAS ON A LABEL SOMEBODY HAS TO READ
   TO FILL THE FIELD IN. First/Last inherited .form-hint — 9.75px at 4.77:1,
   which is the treatment for an aside, not for the name of a box. Sized and
   coloured as the label it is; "Name" above still leads, by size and by
   carrying the required marker. */
.form-sub label {
  display: block;
  margin-bottom: 5px;
  color: var(--text);
  font-size: var(--fs-small);
  font-weight: 550;
}
.form-field .req { color: var(--accent); }
.form-field input[type="text"],
.form-field input[type="email"],
.form-sub input,
.form-field select,
.form-field textarea {
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  padding: 9px 11px;
  font: inherit;
  font-size: var(--fs-body);
  outline: none;
}
.form-field textarea { resize: vertical; min-height: 120px; line-height: 1.5; }
.form-sub input:focus,
.form-field input:focus, .form-field select:focus, .form-field textarea:focus {
  border-color: var(--accent);
}
.form-hint { color: var(--muted); font-size: var(--fs-small); margin-top: 4px; }

/* The honeypot. Moved off-screen rather than display:none, which some bots
   read as "ignore this one". Never focusable, never announced. */
.form-trap {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  overflow: hidden;
}

.form-actions {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 4px;
}
button.btn.primary {
  background: var(--accent); border-color: var(--accent); color: #06210f;
  font-weight: 600; padding: 9px 20px;
}
button.btn.primary:hover { filter: brightness(1.08); background: var(--accent); }
.form-alt { color: var(--muted); font-size: var(--fs-table); }
.form-alt a { color: var(--muted); text-decoration: underline; }
.form-alt a:hover { color: var(--text); }
.form-status { margin: 12px 0 0; font-size: var(--fs-table); color: var(--muted); line-height: 1.5; }

/* Contact, which sits with the league buttons without being one. Quieter than
   a league and behind a divider, so the header reads as three sports and a
   link rather than as four sports. */
.leagues-sep {
  width: 1px; align-self: stretch; margin: 4px 8px 4px 6px;
  background: var(--line);
}
.league-link {
  display: flex; align-items: center;
  padding: 6px 11px; border-radius: 7px;
  color: var(--muted); font-size: var(--fs-small); font-weight: 550;
  letter-spacing: .02em; white-space: nowrap;
  border: 1px solid transparent;
}
.league-link:hover { color: var(--text); background: var(--panel-2); }
.league-link.active {
  color: var(--text); background: var(--panel-2); border-color: var(--line);
}

/* Depth-chart marker. Only ever on a quarterback who is not his team's
   starter, and only visible when someone has asked to see backups. Muted, not
   red: it is a fact about his role, not a warning about the number. */
.depth-tag {
  display: inline-block; margin-right: 6px; padding: 1px 5px;
  border: 1px solid var(--line); border-radius: 4px;
  color: var(--muted); font-size: var(--fs-micro); font-weight: 600;
  letter-spacing: .03em; vertical-align: 1px;
}
/* The Cover 1 ceiling flag borrows the depth tag's shape and takes the accent,
   because unlike a depth-chart note it is saying something IN the player's
   favour. Muted would read as a warning. */
.ceiling-tag {
  border-color: var(--accent, #2f855a);
  color: var(--accent, #2f855a);
}

/* A figure the data cannot distinguish from neutral. Greyed rather than
   hidden: the reader should see that it was measured and came out ordinary,
   which is a finding, not an absence. */
.dim { color: var(--dim); }

/* A measured effect with the physical fact underneath it. The second line is
   context, not another measurement, and is typeset so nobody reads them as a
   pair of results. */
.stacked { display: inline-flex; flex-direction: column; align-items: flex-end; line-height: 1.25; }
.sub-figure { font-size: var(--fs-micro); color: var(--dim); font-variant-numeric: tabular-nums; }

/* The one sentence that tells a stranger what a page is. Sits directly under
   the title, above every control and every number, and is deliberately larger
   than the metadata line beneath it — a visitor reads exactly one thing before
   deciding whether to stay. */
.page-lede {
  margin-top: 6px;
  max-width: 68ch;
  font-size: var(--fs-body);
  line-height: 1.55;
  color: var(--text);
}

/* ---------- class chips ----------
   These had NO CSS AT ALL. `.chip` was written in the markup and never styled,
   so NBA Profiles and NFL Profiles shipped up to nineteen raw system buttons —
   light grey, black bevelled borders, Arial — on a dark green page, and
   `.active` rendered byte-identically to inactive, so the current selection
   was invisible. A blind visual review called it the first thing a visitor
   sees on that page and said it reads as broken. It was.

   Deliberately the same shape as .match-preset rather than a new design: the
   control already existed two pages away doing the identical job, and the site
   has more variants of this component than it needs. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
/* .chip is styled with .match-subtab, above. One toggle, one look. */

/* NOTHING REPAINTS THE INK INSIDE A CHIP. The chip chooses a text colour to
   suit its own fill; a .up or .down span inside it used to override that with
   red or green, which is how 55 of 60 Parks chips ended up between 2.58 and
   4.49:1 — red text on a green field. Semantic colour is for a bare cell. */
.heat-chip * { color: inherit; }

/* Emphasis for a value measured against a fixed scale, replacing a coloured
   fill. Three states, inheriting the page's own contrast rather than putting
   white text on a translucent tint. */
.val-strong { color: var(--text); font-weight: 650; }
.val-weak { color: var(--dim); }

/* Why a search result does not click. Sits at the foot of the dropdown on the
   published build, where the rows are answers rather than links. */
.sr-note {
  padding: 9px 12px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-label);
  line-height: 1.45;
}

/* ---------- column glossary ----------
   A visible list of what every column means, generated from the same strings
   the header tooltips use. A blind review listed forty headers it could not
   decode and most of them already HAD tooltips — hover-only text does not
   exist on a phone, and on a desktop nobody knows which headers reward the
   hover. The dotted underline says which ones have an answer; this says the
   answer out loud. */
th.defined { text-decoration: underline dotted var(--line); text-underline-offset: 3px; }
.col-key { margin: 10px 0 0; font-size: var(--fs-small); }
.col-key > summary {
  cursor: pointer; color: var(--muted); padding: 6px 2px;
  font-weight: 600; list-style: revert;
}
.col-key > summary:hover { color: var(--text); }
.col-key-list {
  margin: 4px 0 0; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  display: grid; grid-template-columns: max-content 1fr; gap: 7px 16px;
  max-height: 340px; overflow: auto;
}
.col-key-list dt { color: var(--text); font-weight: 650; white-space: nowrap; }
.col-key-list dd { margin: 0; color: var(--muted); line-height: 1.5; max-width: 68ch; }
@media (max-width: 560px) {
  .col-key-list { grid-template-columns: 1fr; gap: 2px 0; }
  .col-key-list dd { margin: 0 0 10px; }
}

/* The written summary. Measured line length and generous leading, because
   this is the only block on the site meant to be READ rather than scanned —
   the tables around it are deliberately dense and the same treatment here
   would make six paragraphs look like a wall. */
.summary-body { padding-top: 0; }
.summary-para {
  margin: 0 0 0.85em;
  max-width: 68ch;
  line-height: 1.62;
  font-size: var(--fs-body);
  color: var(--ink);
}
.summary-para:last-child {
  margin-bottom: 0;
  /* The standing caveat, set quieter than the facts above it but NOT hidden:
     it is the paragraph most worth reading and the least likely to be. */
  color: var(--ink-dim);
  font-size: var(--fs-small);
  border-top: 1px solid var(--line);
  padding-top: 0.7em;
  max-width: 74ch;
}

/* The hitter lookup BOX. Not `.lookup-card`, which is already the styling for
   a lookup RESULT — reusing it would have given the search box the accent
   border meant to mark an answer.

   Deliberately not styled like the filter row above it either: this is a place
   to go, not a control that narrows a table, and shipping it as the sixth grey
   box in a row of six meant the person who asked for it could not find it on
   the live site. */
.hitter-lookup { margin: 0 0 var(--gap); }
.lookup-title {
  font-size: var(--fs-section);
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 0.25em;
}
.lookup-lede { max-width: 72ch; margin-bottom: 0.8em; }
.lookup-input {
  width: 100%;
  max-width: 26rem;
  padding: 0.6em 0.8em;
  font-size: var(--fs-body);
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.lookup-input:focus { outline: none; border-color: var(--accent); }
.lookup-input::placeholder { color: var(--ink-dim); }

/* The way back from a hidden PeptoPlays row. Under ten names a day, so the
   hidden ones are named rather than counted: "where did Alonso go" is
   answered on the page instead of in localStorage. */
.pepto-hidden { display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px; margin: 10px 12px 2px; }
.pepto-hidden-note { flex-basis: 100%; color: var(--dim);
  font-size: var(--fs-label); margin-top: 2px; }

/* A header with no ranking behind it must not look clickable. `cursor:
   pointer` on every th is inherited from the sortable default, and a control
   column opts out of sorting without opting out of that, which promises an
   ordering that never arrives. */
thead th.nosort { cursor: default; }

/* A 17x14px tap target is a desktop control that happens to render on phones.
   Scoped to this board rather than raised on `.row-hide` globally, because the
   NBA board shares that class and its ergonomics are not mine to change. */
.pepto-table .row-hide {
  min-width: 34px; min-height: 34px; padding: 0 8px;
  font-size: calc(var(--fs-body) * 1.15);
}

/* The PeptoPlays row drawer. Deliberately plain: the list is three conditions
   and the drawer is the numbers under them, so it reads as a definition list
   rather than a second board competing with the one above it. */
.pepto-panel { padding: 10px 12px 12px; }
.pepto-head { font-size: var(--fs-label); letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 8px; }
.pepto-line { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 8px; padding: 3px 0; }
.pepto-k { min-width: 12rem; color: var(--muted); }
.pepto-v { font-variant-numeric: tabular-nums; font-weight: 600; }
.pepto-note { color: var(--dim); font-size: var(--fs-label); }
.pepto-foot { color: var(--dim); font-size: var(--fs-label);
  margin-top: 10px; max-width: 46rem; }

/* THE FIRST-VISIT HOME PAGE. Three cards side by side on a desk, stacked on a
   phone -- the same breakpoint the rest of the site uses, so a newcomer's first
   screen behaves like every screen after it. */
.home-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
/* .card sets a top margin on siblings, which fights a grid. */
.home-grid .card + .card { margin-top: 0; }
.home-card .card-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
}
/* The status line is the point of the card -- it says what is actually on the
   board today -- so it must not wrap away from the sport it describes. */
.home-card .card-head .page-sub { margin-top: 0; white-space: nowrap; }
.home-links {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px;
}
.home-link {
  display: inline-block; padding: 6px 11px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); color: var(--text); text-decoration: none;
  font-size: var(--fs-table);
}
.home-link:hover { border-color: var(--accent); color: var(--accent); }
.home-foot { margin-top: 18px; }
/* The home page owns no section, so its bar is not merely empty — it is gone.
   A class rather than [hidden], because .tabs sets display:flex and wins. */
.tabs.tabs-off { display: none; }

/* Home sits in the league row and matches the buttons, but it is an anchor and
   carries its own active state. */
.leagues .home-tab {
  background: transparent; border: 1px solid transparent; color: var(--muted);
  padding: 6px 11px; border-radius: 7px; text-decoration: none;
  font-size: var(--fs-small); font-weight: 550; letter-spacing: .02em;
  display: inline-block;
}
.leagues .home-tab:hover { color: var(--text); background: var(--panel-2); }
.leagues .home-tab.active {
  color: var(--text); background: var(--panel-2); border-color: var(--line);
}

/* ---------------------------------------------------------------------------
   EVIDENCE PANELS — the games behind a matchup rating, on the player page and
   expanded from a Matchups row. Two panels side by side, collapsing to one
   column under 900px because the game-log chart cannot usefully halve again.

   Colours are the site's own tokens throughout: --green and --red are the
   semantic pair the rest of the NBA boards already use for above/below, and
   --amber is the same baseline colour the ladder rung uses, so a dashed
   amber line means "his own average" here exactly as it means a threshold
   there. No new palette.
   ------------------------------------------------------------------------ */
.ev-head { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap;
  padding-bottom: 12px; border-bottom: 1px solid var(--line-soft); margin-bottom: 14px; }
.ev-cls { font-weight: 600; }
.ev-stat-pick { margin-left: auto; display: flex; gap: 6px; }
/* minmax(0,1fr), NOT 1fr. A grid track's default minimum is min-content, and
   the min-content of a 63-bar flex row is about 1,950px -- so "All seasons"
   blew the column out to that width, pushed the second panel off the card and
   made the page itself scroll sideways instead of the chart. The zero minimum
   lets the track shrink and hands the overflow back to .ev-chart, which is
   the box that is supposed to scroll. */
.ev-pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 20px; align-items: start; }
.ev-title { font-size: .95rem; margin: 0 0 10px; font-weight: 600; }

.ev-filter { background: var(--panel-2); border: 1px solid var(--line-soft);
  border-radius: 8px; padding: 10px 12px 12px; margin-bottom: 14px; }
.ev-filter-top { display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 8px; }
.ev-filter-label { font-size: .72rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--dim); }
.ev-range { font-size: .82rem; color: var(--accent); font-variant-numeric: tabular-nums; }

/* Two range inputs stacked in one box is the standard two-handle trick: the
   inputs themselves ignore pointer events so a click reaches whichever thumb
   is nearest, and only the thumbs take them back. */
.ev-sliders { position: relative; height: 24px; }
.ev-sliders input { position: absolute; left: 0; width: 100%; margin: 0; height: 24px;
  background: none; pointer-events: none; -webkit-appearance: none; appearance: none; }
.ev-sliders input::-webkit-slider-runnable-track { height: 3px; background: var(--line);
  border-radius: 2px; }
.ev-sliders input::-moz-range-track { height: 3px; background: var(--line); border-radius: 2px; }
.ev-sliders input::-webkit-slider-thumb { pointer-events: auto; -webkit-appearance: none;
  height: 15px; width: 15px; margin-top: -6px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg); cursor: pointer; }
.ev-sliders input::-moz-range-thumb { pointer-events: auto; height: 15px; width: 15px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--bg); cursor: pointer; }
.ev-sliders input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.ev-presets { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.ev-preset { font-size: .74rem; color: var(--muted); background: var(--panel);
  border: 1px solid var(--line-soft); border-radius: 5px; padding: 3px 9px;
  cursor: pointer; font: inherit; font-size: .74rem; }
.ev-preset:hover { color: var(--text); border-color: var(--line); }
.ev-preset.on { color: var(--text); border-color: var(--accent); background: var(--accent-soft); }
.ev-preset:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.ev-stats { margin-bottom: 14px; }
.ev-chartwrap { position: relative; height: 130px; }
.ev-chart { display: flex; gap: 5px; align-items: flex-end; height: 100%;
  overflow-x: auto; padding-bottom: 2px; scrollbar-width: thin; }
.ev-base { position: absolute; left: 0; right: 0; border-top: 1px dashed var(--amber);
  pointer-events: none; }
.ev-base span { position: absolute; right: 0; top: -16px; font-size: .7rem;
  color: var(--amber); background: var(--panel); padding: 0 4px; }
.ev-bw { display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; height: 100%; min-width: 26px; }
.ev-bar { width: 22px; border-radius: 2px 2px 0 0; display: flex; justify-content: center;
  padding-top: 2px; }
.ev-bar.up { background: var(--accent-deep); }
.ev-bar.down { background: #7a2b26; }
.ev-bar span { font-size: .68rem; font-weight: 600; color: #fff; }
.ev-opp { font-size: .66rem; color: var(--muted); margin-top: 4px; }
.ev-rk { font-size: .62rem; color: var(--dim); }

.ev-scroll { max-height: 300px; overflow-y: auto; overflow-x: auto; }
.ev-table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.ev-table th { text-align: left; font-size: .68rem; letter-spacing: .07em;
  text-transform: uppercase; color: var(--dim); font-weight: 500;
  padding: 0 7px 6px 0; border-bottom: 1px solid var(--line-soft);
  position: sticky; top: 0; background: var(--panel); }
.ev-table td { padding: 5px 7px 5px 0; border-bottom: 1px solid var(--line-soft);
  font-variant-numeric: tabular-nums; }
.ev-table td.num, .ev-table th.num { text-align: right; }
.ev-table td.dim { color: var(--dim); }
.ev-note { font-size: .8rem; color: var(--muted); margin: 16px 0 0;
  padding-top: 12px; border-top: 1px solid var(--line-soft); }

@media (max-width: 900px) {
  .ev-pair { grid-template-columns: minmax(0, 1fr); }  /* same zero floor */
  .ev-stat-pick { margin-left: 0; }
}

/* --- NBA evidence: expanded in place, under the row that was clicked --- */
tr.ev-open > td { background: var(--panel-2); }
tr.ev-open > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
tr.ev-row > td { padding: 0; background: var(--panel-2);
  border-bottom: 1px solid var(--line); }
/* PINNED TO THE VISIBLE AREA, not to the table's scroll width.
   The expander's <td> spans a table that is wider than the viewport, so a
   block inside it is ~1,790px wide and everything right-aligned in its header
   sits off-screen -- the panel toggles landed at x=1424 against a 1,385px
   viewport and could not be reached without scrolling the table sideways.
   Sticky at left:0 holds the panel against the visible edge while the table
   scrolls under it; the width cap keeps its right edge on screen too. */
.ev-inline { padding: 4px 14px 18px; border-left: 3px solid var(--accent);
  position: sticky; left: 0; box-sizing: border-box;
  width: var(--ev-vw, 100%); max-width: 100%; }
.ev-inline-head { display: flex; align-items: baseline; gap: 12px;
  padding: 10px 0 4px; font-weight: 600; }
.ev-inline-head button { margin-left: auto; }
.ev-inline .card-body { padding: 0; }

/* --- NBA evidence: season / games / line controls --- */
.ev-scope, .ev-linectl { margin-bottom: 10px; }
.ev-scope-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ev-scope-row + .ev-scope-row { margin-top: 8px; }
.ev-scope-row .ev-filter-label { min-width: 58px; }
.ev-scope-row .ev-presets { margin-top: 0; }
.ev-stepper { display: inline-flex; align-items: center; gap: 2px;
  border: 1px solid var(--line-soft); border-radius: 5px; background: var(--panel);
  padding: 1px 2px; }
.ev-step { font-size: .84rem; line-height: 1; color: var(--muted);
  background: none; border: 0; cursor: pointer; padding: 5px 7px; border-radius: 4px; }
.ev-step:hover { color: var(--text); background: var(--panel-2); }
.ev-step:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ev-step-n { font-size: .76rem; color: var(--text); min-width: 26px;
  text-align: center; font-variant-numeric: tabular-nums; }
.ev-line-input { width: 58px; font: inherit; font-size: .78rem; text-align: center;
  color: var(--text); background: none; border: 0; padding: 4px 0;
  font-variant-numeric: tabular-nums; -moz-appearance: textfield; }
.ev-line-input::-webkit-outer-spin-button,
.ev-line-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.ev-line-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px;
  border-radius: 3px; }
.ev-count { font-size: .74rem; color: var(--dim);
  font-variant-numeric: tabular-nums; }
.ev-base-avg { border-top-style: dotted; opacity: .55; }
.ev-base-avg span { right: auto; left: 0; }

/* --- NBA evidence: per-game detail on hover --- */
.ev-poplayer { position: absolute; bottom: calc(100% + 8px); z-index: 30;
  pointer-events: none; }
.ev-poplayer[hidden] { display: none; }
.ev-poplayer.ev-pop-below { bottom: auto; top: calc(100% + 8px); }
.ev-pop { min-width: 190px; background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: 7px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .45); overflow: hidden; }
.ev-pop-head { display: flex; justify-content: space-between; gap: 10px;
  align-items: baseline; padding: 7px 10px; background: var(--panel-2);
  border-bottom: 1px solid var(--line-soft); font-size: .78rem; font-weight: 600; }
.ev-pop-season { color: var(--dim); font-weight: 400; font-size: .7rem; }
.ev-pop-rows { padding: 4px 0; }
.ev-pop-row { display: flex; justify-content: space-between; gap: 18px;
  padding: 3px 10px; font-size: .78rem; font-variant-numeric: tabular-nums; }
.ev-pop-row span:first-child { color: var(--muted); }
.ev-pop-na span { color: var(--dim); font-style: italic; }
.ev-pop-note { padding: 6px 10px; font-size: .68rem; color: var(--dim);
  border-top: 1px solid var(--line-soft); max-width: 240px; }
.ev-inline-note { font-size: .74rem; color: var(--amber); margin: -4px 0 12px;
  line-height: 1.45; }

/* One panel showing gets the full width rather than half of it and a hole. */
.ev-pair.ev-solo { grid-template-columns: minmax(0, 1fr); }
.ev-picks { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
.ev-picks + .ev-stat-pick { margin-left: 14px; }
.ev-pick[aria-pressed="false"] { opacity: .7; }
