/* eigenball — minimal, retro-scientific.
   Body type is Google Sans Code; the page reads like a lab notebook. */

:root {
  --color-bg:          #f8f8f6;   /* warm paper */
  --color-bg-alt:      #eeede9;
  --color-text:        #1a1a1a;
  --color-heading:     #0d0d0d;
  --color-muted:       #5f5f5f;
  --color-faint:       #8a8780;
  --color-rule:        #2a2a2a;   /* near-black hairline rules */
  --color-rule-soft:   #d8d4c8;
  --color-accent:      #3a7d85;   /* faded teal-blue: "good" / above-average */
  --color-accent-dark: #2c6168;
  --color-warn:        #b8442b;   /* faded vermillion: "bad" / below-average */
  --color-warn-dark:   #8a3220;
  --color-grid:        #e5e2d6;

  --font-mono: "Google Sans Code", ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
  --font-sans: "Google Sans Code", ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
}

* { box-sizing: border-box; }

html, body { margin: 0; padding: 0; }

html {
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.55;
  font-variant-ligatures: none;
}
@media (min-width: 38em) {
  html { font-size: 14px; }
}

body {
  color: var(--color-text);
  background-color: var(--color-bg);
  -webkit-text-size-adjust: 100%;
  font-feature-settings: "tnum" 1, "ss01" 1;
}

/* ----- Type ----- */

a {
  color: var(--color-accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
a:hover, a:focus {
  color: var(--color-accent-dark);
  background-color: var(--color-bg-alt);
}

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 .4rem;
  font-weight: 600;
  line-height: 1.15;
  color: var(--color-heading);
  font-family: var(--font-mono);
  letter-spacing: 0;
}
h1 { font-size: 1.05rem; }
h2 { font-size: 0.95rem; }

p { margin: 0 0 .85rem; }

hr {
  margin: 1.2rem 0;
  border: 0;
  border-top: 1px solid var(--color-rule);
}

code {
  font-family: var(--font-mono);
  font-size: 100%;
  padding: 0 0.25em;
  background-color: var(--color-bg-alt);
  border: 1px solid var(--color-rule-soft);
  border-radius: 0;
}

::selection {
  background-color: var(--color-accent);
  color: #fff;
}

/* Small-caps section labels & meta */
.label, .eyebrow {
  display: inline-block;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--color-muted);
}

/* ----- Layout ----- */

.container {
  max-width: 100%;
  padding: 0 1.25rem;
  margin: 0 auto;
}
@media (min-width: 38em) {
  .container { max-width: 64rem; padding: 0 2rem; }
}

/* Masthead = a single-line "header rule" with a numeric figure id, like
   a journal page header. */
.masthead {
  padding: 0.85rem 0 0.65rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--color-rule);
}
.masthead-inner {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
}
.masthead-title {
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0;
}
.masthead-title a { color: var(--color-heading); text-decoration: none; }
.masthead-title a:hover { background: none; color: var(--color-accent); }
.masthead-title small {
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--color-muted);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-left: 0.6rem;
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.4rem;
  font-size: 0.74rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
}
.site-nav a {
  position: relative;
  color: var(--color-muted);
  text-decoration: none;
  padding: 0.05rem 0;
  border-bottom: 1px solid transparent;
}
.site-nav a:not(:last-child)::after {
  content: "";
  position: absolute;
  right: -0.75rem;
  top: 0.2rem;
  bottom: 0.2rem;
  width: 1px;
  background: var(--color-rule-soft);
}
.site-nav a:hover { color: var(--color-accent); background: none; }
.site-nav a.active {
  color: var(--color-heading);
  border-bottom-color: var(--color-accent);
}

/* Secondary nav = the sibling views within a section (e.g. Teams →
   Forecast / Market / Bracket). Deliberately a different register from the
   primary row — Title-case, lighter, tighter-tracked, set further down — so
   the two read as two tiers, not one wrapped menu. */
.sub-nav-inner { margin-top: 0.6rem; }
.sub-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.3rem;
  font-size: 0.7rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
.sub-nav a {
  color: var(--color-muted);
  text-decoration: none;
  padding: 0.05rem 0;
  border-bottom: 1px solid transparent;
}
.sub-nav a:hover { color: var(--color-accent); background: none; }
.sub-nav a.active {
  color: var(--color-heading);
  font-weight: 500;
  border-bottom-color: var(--color-accent);
}

/* Season pill = the ambient "when" control, mirroring the entity nav on the
   left. Sits in the masthead's right slot; empty (and so invisible) on
   single-season views. */
.season-nav { align-self: center; }
.season-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.season-pill-label {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--color-muted);
}
/* Native <select> chrome (OS arrow + bevel) clashes with the paper theme,
   so strip it (appearance:none) and supply our own hairline box + chevron. */
.season-select {
  font-family: inherit;
  font-size: 0.74rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--color-heading);
  background-color: var(--color-bg);
  border: 1px solid var(--color-rule-soft);
  border-radius: 0;
  padding: 0.18rem 1.45rem 0.18rem 0.5rem;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%235f5f5f' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.5rem center;
  background-size: 0.6rem;
  transition: border-color 0.12s ease;
}
.season-select:hover { border-color: var(--color-accent); }
.season-select:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 2px rgba(58, 125, 133, 0.18);
}

.page { margin-bottom: 2rem; }

.page-title {
  margin: 0 0 0.25rem;
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: 0;
}

.page-subtitle {
  color: var(--color-muted);
  margin-bottom: 1.5rem;
  font-size: 0.85rem;
  max-width: 48em;
}

footer.site-footer {
  margin: 4rem 0 2rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--color-rule);
  color: var(--color-muted);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
}
footer.site-footer .container { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }

/* ----- Shot-skill scatter ----- */

#scatter-section { margin: 1rem 0 1.25rem; }
.scatter-caption {
  color: var(--color-muted);
  font-size: 0.8rem;
  max-width: 48em;
  margin-bottom: 0.5rem;
}
#scatter svg { display: block; }
#scatter a { text-decoration: none; }
.scatter-empty {
  color: var(--color-muted);
  font-size: 0.85rem;
  padding: 1.5rem 0;
}

/* ----- Tables — lab-report style ----- */

.table-scroll {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin-top: 0.5rem;
}

table.table {
  margin: 0 auto;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: 0.82rem;
  width: 100%;
  border-top: 1px solid var(--color-rule);
  border-bottom: 1px solid var(--color-rule);
}

.table th {
  font-size: 0.62rem;
  font-weight: 600;
  border-bottom: 1px solid var(--color-rule);
  background-color: transparent;
  color: var(--color-heading);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  user-select: none;
  padding: 0.45rem 0.5rem 0.4rem;
}
/* Only headers that actually sort advertise it — pages add .sortable to
   the columns they wire up. */
.table th.sortable { cursor: pointer; }
.table th.sortable:hover {
  color: var(--color-accent);
}
/* Forecast home table carries longer labels (Player Offense / Player Defense),
   so its column headers run one size smaller to keep them on one line. */
#teams-table .table th { font-size: 0.56rem; }

.table th, .table td {
  text-align: center;
  padding: 0.18rem 0.5rem;
}
.table td {
  border-bottom: 1px dotted var(--color-rule-soft);
  background-color: transparent;
  color: var(--color-text);
  white-space: nowrap;
}
.table tr:last-child td { border-bottom: none; }

.table td.right-border { border-right: 1px solid var(--color-rule); }
.table td.left-border  { border-left:  1px solid var(--color-rule); }

.table .prob-cell, .table th.prob-cell { min-width: 56px; }

/* Forecast playoff-odds columns render at one fixed width so they stay equal
   regardless of header-word length — auto-layout otherwise gives the long
   single-word headers (PLAYOFFS / CHAMPION) more width than the wrapped CONF
   ones. Side padding is trimmed so 50px still fits the widest header
   (PLAYOFFS / CHAMPION) on one line at the column-header font. */
#teams-table .table th.prob-cell,
#teams-table .table td.prob-cell {
  min-width: 50px;
  max-width: 50px;
  padding-left: 0.2rem;
  padding-right: 0.2rem;
}

/* Compact variant — narrower max-width with slightly larger type. */
.table-scroll.compact { max-width: 36rem; margin-left: 0; margin-right: 0; }
.table-scroll.compact table.table { font-size: 0.86rem; }

/* Team-page roster: match the schedule (past-table) width cap. Jersey
   numbers run faint so the eye lands on the name, not the number. */
#team-roster table.table { max-width: 42rem; }
#team-roster td.jersey-cell { color: var(--color-faint); width: 2.2rem; }

/* RAPM leaderboard + peaks: fixed layout with explicit <col> widths so the
   numeric columns pack tight instead of sprawling across the full container.
   The name column has a fixed share and ellipsis-truncates (full name shown on
   hover) when a name overruns it. Capped width, centered. */
#rapm-table table.table,
#peaks-table table.table { table-layout: fixed; }
.leaderboard-section .table-scroll { max-width: 44rem; margin-left: auto; margin-right: auto; }
.peaks-table-wrap .table-scroll   { max-width: 50rem; margin-left: auto; margin-right: auto; }
#rapm-table .name-cell,
#peaks-table .name-cell { overflow: hidden; text-overflow: ellipsis; }
#rapm-table .name-cell a,
#peaks-table .name-cell a {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
}

.clinch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.clinch svg {
  width: 1.2em;
  height: 1.2em;
  display: block;
}

.table .team-cell {
  text-align: left;
  font-family: var(--font-mono);
}
.table .team-cell .abbr {
  font-weight: 700;
  color: var(--color-text);
  margin-right: 6px;
}
.table .team-cell .full {
  color: var(--color-muted);
  font-weight: 400;
}
.table .team-cell a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.table .team-cell a:hover {
  background: none;
  border-bottom-color: var(--color-accent);
}
.table .team-cell a:hover .abbr { color: var(--color-accent); }

@media (max-width: 600px) {
  .table { font-size: 0.72rem; }
  .table td, .table th { padding: 0.18rem 0.35rem; }
  /* Shot-skill leaderboard has 9 fixed-% columns that crush below readable
     at narrow viewports. Force a min-width so .table-scroll's overflow-x
     engages and the table swipes horizontally instead. */
  #shot-table table.table { min-width: 720px; }
  /* RAPM monthly + Career peaks tables are fixed-layout with explicit <col>
     widths summing to their capped width; below that they crush instead of
     scrolling. Pin to those widths so .table-scroll swipes horizontally. */
  #rapm-table table.table  { min-width: 44rem; }
  #peaks-table table.table { min-width: 50rem; }
  /* Forecast home table renders the full desktop column set on mobile too;
     .table-scroll's overflow-x lets it swipe horizontally when it doesn't
     fit, rather than dropping columns. */
}

/* ----- Inline filters / inputs ----- */

.filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.85rem;
  margin: 0.5rem 0 0.75rem;
  font-size: 0.78rem;
}
.filter-row .label { letter-spacing: 0.1em; }
/* Keep a label glued to its control so the pair wraps as one unit
   (otherwise on narrow screens the input breaks onto its own line). */
.filter-row .filter-field {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.filter-row input[type="number"],
.filter-row input[type="text"],
.filter-row select {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  padding: 0.18rem 0.4rem;
  background: var(--color-bg);
  border: 1px solid var(--color-rule);
  border-radius: 0;
  color: var(--color-text);
}
.filter-row input[type="number"] { width: 6.5em; }
.filter-row input[type="text"]   { width: 14em; }
.filter-row input:focus,
.filter-row select:focus {
  outline: none;
  border-color: var(--color-accent);
}

/* Range slider — flat track on the rule color, accent thumb, matching the
   pared-back control styling above. The readout to its right shows the value. */
.filter-row input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 11em;
  height: 2px;
  padding: 0;
  background: var(--color-rule);
  border: none;
  border-radius: 0;
  cursor: pointer;
}
.filter-row input[type="range"]:focus { outline: none; }
.filter-row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--color-accent);
  cursor: pointer;
}
.filter-row input[type="range"]::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--color-accent);
  cursor: pointer;
}
.filter-row .slider-value {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  min-width: 3.2em;
  font-variant-numeric: tabular-nums;
}
.filter-row .checkbox-label {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  letter-spacing: 0.1em;
  cursor: pointer;
  user-select: none;
}
.filter-row .checkbox-label input[type="checkbox"] {
  margin: 0;
  accent-color: var(--color-accent);
}

/* ----- Cell variants used by the data tables ----- */

.table .num     { font-variant-numeric: tabular-nums; text-align: center; }
.table .strong  { font-weight: 700; }
.table .name-cell {
  text-align: left;
  font-family: var(--font-mono);
  padding-left: 0.5rem;
}
.table .name-cell a,
.table a.cell-link {
  color: var(--color-text);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.table .name-cell a:hover,
.table a.cell-link:hover {
  color: var(--color-accent);
  background: none;
  border-bottom-color: var(--color-accent);
}

/* ----- Margin-ruler cell (past predictions on team page) -----
   Inline SVG per row showing predicted (hollow) and actual (filled)
   margin on a shared scale. Header above shows the axis once. */
.table.past-table {
  max-width: 42rem;       /* don't stretch full-width on desktop */
  width: 100%;
  margin: 0 auto;
}
.table.past-table td.vs-cell {
  color: var(--color-faint);
  padding-left: 0.1rem;
  padding-right: 0.1rem;
  width: 1.4rem;
}
.table.past-table th.margin-head {
  vertical-align: bottom;
  padding-bottom: 0.1rem;
}
.table.past-table th.margin-head > div {
  margin-bottom: 0.15rem;
}
.table.past-table td.margin-cell {
  padding-top: 0.1rem;
  padding-bottom: 0.1rem;
  width: 180px;
}
/* Matchup ruler (games page): the team columns flank the ruler, so the Home
   column hugs it from the right and the Away column from the left — a mark's
   side then reads directly as "this team". A little horizontal buffer on the
   ruler cell keeps the marks from crowding the names. */
.table.matchup-table td.matchup-home,
.table.matchup-table th.matchup-home-head { text-align: right; }
.table.matchup-table td.matchup-away,
.table.matchup-table th.matchup-away-head { text-align: left; }
.table.matchup-table td.margin-cell,
.table.matchup-table th.margin-head { padding-left: 0.85rem; padding-right: 0.85rem; }
/* Reversed home cell ("Thunder OKC"): abbr is last, so move its gap to the left
   and let it sit flush against the ruler buffer. */
.table.matchup-table td.matchup-home .abbr { margin-right: 0; margin-left: 6px; }
.margin-ruler, .margin-axis {
  display: block;
  margin: 0 auto;
}
/* Predicted-margin ring: fill with the row background so the residual bar
   underneath reads as a clean punched circle. Track the hover background so
   the hole doesn't show a paper disc against the highlighted row. */
.margin-ruler .pred-ring { fill: var(--color-bg); }
tr.past-row:hover .pred-ring { fill: var(--color-bg-alt); }

/* Hover tooltip for past-predictions rows. */
.row-tooltip {
  position: absolute;
  z-index: 30;
  background: var(--color-bg);
  border: 1px solid var(--color-rule);
  padding: 0.5rem 0.8rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--color-text);
  pointer-events: none;
  box-shadow: 2px 2px 0 var(--color-rule-soft);
  /* Size to the widest line so values never wrap, but stay compact. */
  width: max-content;
  min-width: 9rem;
  max-width: 18rem;
}
.row-tooltip .hd {
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-faint);
  margin-bottom: 0.15rem;
}
.row-tooltip .hd2 {
  font-weight: 600;
  margin-bottom: 0.3rem;
}
.row-tooltip .ln {
  display: flex;
  gap: 0.6rem;
  line-height: 1.3;
}
/* Fixed-width label column so the values start at the same x on every line
   (predicted / actual line up) instead of floating to the right edge. */
.row-tooltip .ln .k { color: var(--color-muted); flex: 0 0 4.7rem; white-space: nowrap; }
.row-tooltip .ln .v { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Shot-skill scatter tooltip: short keys ("2P+"/"3P+") don't need the wide
   label column. Drop the shared 18rem cap and keep each value ("+12.3 per 100
   on 1234") on one line; width: max-content then sizes the box to the longest
   line, and moveScatterTip flips it off the screen edge. */
.row-tooltip.scatter-tip { max-width: none; }
.row-tooltip.scatter-tip .ln .k { flex: 0 0 2.6rem; }
.row-tooltip.scatter-tip .ln .v { white-space: nowrap; }
.row-tooltip .wl-win  { color: var(--color-accent); font-weight: 700; }
.row-tooltip .wl-loss { color: var(--color-warn);   font-weight: 700; }
tr.past-row:hover td { background-color: var(--color-bg-alt); }

/* Narrow viewports: shrink ruler so the table doesn't overflow.
   SVG keeps its 180-wide viewBox; CSS-scales uniformly. */
@media (max-width: 480px) {
  .table.past-table td.margin-cell { width: 130px; }
  .margin-ruler, .margin-axis { width: 130px; height: auto; }
}
.ruler-legend {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  color: var(--color-faint);
  display: flex;
  align-items: center;
  gap: 0.85rem;
  flex-wrap: wrap;
  margin: 0 0 0.35rem;
  padding-left: 0.1rem;
}
.ruler-legend .lk { display: inline-flex; align-items: center; }
.ruler-legend svg { vertical-align: middle; margin-right: 0.22rem; }

/* ----- Toggle buttons — terminal-tab style ----- */

.toggle-row {
  margin: 0.75rem 0 0.6rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  border-bottom: 1px solid var(--color-rule);
}

.toggle-btn {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding: 0.3rem 0.85rem;
  border: 1px solid var(--color-rule);
  border-bottom: none;
  background: var(--color-bg-alt);
  color: var(--color-muted);
  cursor: pointer;
  margin-right: -1px;
  margin-bottom: -1px;
  border-radius: 0;
  transition: background 0.1s, color 0.1s;
}
.toggle-btn.active {
  background: var(--color-bg);
  color: var(--color-heading);
  border-bottom: 1px solid var(--color-bg);
  z-index: 1;
}
.toggle-row .toggle-gap {
  margin-left: auto;
}

/* Pre-seeding snapshot: team × seed probability heatmap above the bracket */
.seed-odds {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin: 0.5rem 0 1.5rem;
}
.seed-odds-conf {
  flex: 1 1 320px;
  min-width: 0;
}
.seed-odds-title {
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--color-muted);
  margin-bottom: 0.35rem;
}
.seed-odds-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: 0.66rem;
}
.seed-odds-table th {
  font-weight: 500;
  color: var(--color-muted);
  text-align: center;
  padding: 0.15rem 0;
}
.seed-odds-table td {
  text-align: center;
  padding: 0.18rem 0.1rem;
  border: 1px solid var(--color-bg);
}
.seed-odds-table td.seed-odds-team {
  text-align: left;
  background: none;
  padding-right: 0.4rem;
  white-space: nowrap;
}

.toggle-btn:hover:not(.active) {
  color: var(--color-accent);
}

/* ----- Card list (landing) — section directory ----- */

.card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin: 1rem 0 2rem;
  border-top: 1px solid var(--color-rule);
}

.card {
  display: block;
  padding: 0.75rem 0.85rem;
  border-bottom: 1px solid var(--color-rule-soft);
  color: inherit;
  text-decoration: none;
  position: relative;
}
.card:hover {
  background-color: var(--color-bg-alt);
  text-decoration: none;
  color: inherit;
}
.card-title {
  font-weight: 700;
  color: var(--color-heading);
  margin-bottom: 0.1rem;
  font-size: 0.88rem;
  letter-spacing: 0;
}
.card-title::before {
  content: attr(data-fig) " · ";
  color: var(--color-accent);
  font-weight: 400;
}
.card-desc {
  color: var(--color-muted);
  font-size: 0.78rem;
  line-height: 1.45;
}
.card.coming-soon .card-title::after {
  content: " [draft]";
  color: var(--color-faint);
  font-weight: 400;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
}

/* ----- Small-multiples grid (markets, etc.) ----- */

.sm-grid {
  display: grid;
  gap: 12px;
  margin-top: 0.5rem;
}
.sm-cell {
  border: 1px solid var(--color-rule-soft);
  padding: 0.35rem 0.45rem 0.2rem;
  background: var(--color-bg);
  cursor: crosshair;
}
.sm-cell svg { overflow: hidden; }

/* SVG text — d3 axes / chart labels — share the page font. */
svg text { font-family: var(--font-mono); }
.sm-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.75rem;
  margin-bottom: 0.1rem;
}
.sm-team {
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--color-heading);
}
.sm-val {
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
}
.sm-val.pos { color: var(--color-accent); }
.sm-val.neg { color: var(--color-warn); }

/* ----- Pagination ----- */

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.75rem 0 1rem;
  font-size: 0.78rem;
}
.pag-nav {
  display: inline-flex;
  align-items: center;
  gap: 0;
}
.pag-btn {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.2rem 0.55rem;
  border: 1px solid var(--color-rule-soft);
  background: var(--color-bg);
  color: var(--color-muted);
  cursor: pointer;
  margin-right: -1px;
  border-radius: 0;
}
.pag-btn:hover:not(:disabled):not(.active) {
  background: var(--color-bg-alt);
  color: var(--color-text);
}
.pag-btn.active {
  background: var(--color-text);
  border-color: var(--color-text);
  color: var(--color-bg);
}
.pag-btn:disabled {
  color: var(--color-faint);
  cursor: not-allowed;
}
.pag-ellipsis {
  padding: 0 0.3rem;
  color: var(--color-faint);
}

/* ----- Misc helpers ----- */

.muted { color: var(--color-muted); }
.small { font-size: 0.78rem; }
.note  {
  color: var(--color-muted);
  font-size: 0.78rem;
  margin: 0.5rem 0 1rem;
  padding-left: 0.8rem;
  border-left: 2px solid var(--color-rule-soft);
}

.pct {
  font-size: 0.72em;
  color: inherit;
  margin-left: 1px;
}

/* ----- Team page ----- */

.team-section-title {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--color-muted);
  margin: 1.6rem 0 0.4rem;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--color-rule);
}

.team-overview { margin: 1rem 0 0.5rem; }
.stat-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}
@media (min-width: 38em) {
  /* auto-fit means a half-empty trailing row's blocks expand to fill it,
     so 3 blocks → 3 cols, 5 blocks → 3 cols then row 2 spans 2 wide each. */
  .stat-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
}

.stat-block {
  border: 1px solid var(--color-rule-soft);
  padding: 0.75rem 1rem;
  background: var(--color-bg);
}
.stat-block-title {
  font-size: 0.62rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--color-muted);
  margin-bottom: 0.4rem;
}
.stat-table {
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  width: 100%;
}
.stat-table td {
  padding: 0.18rem 0;
  border-bottom: 1px dotted var(--color-rule-soft);
}
.stat-table tr:last-child td { border-bottom: none; }
.stat-table .stat-label { color: var(--color-muted); }
.stat-table .stat-val   { text-align: right; font-weight: 600; padding-left: 0.6rem; }
.stat-table .stat-val.pct-cell {
  background-clip: padding-box;
  padding: 0.18rem 0.5rem;
}
.stat-table .stat-rank  {
  text-align: right;
  color: var(--color-faint);
  font-size: 0.78rem;
  padding-left: 0.5rem;
  width: 3.2em;
}
.rank { font-variant-numeric: tabular-nums; }

.win-dist-block svg { display: block; margin-top: 0.1rem; }
.win-dist-foot {
  margin-top: 0.35rem;
  font-size: 0.72rem;
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.win-dist-foot strong { color: var(--color-text, #1a1a1a); font-weight: 600; }

/* ----- Player page ----- */

.player-bio {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.6rem;
  margin: 0.25rem 0 1rem;
  font-size: 0.86rem;
  color: var(--color-text);
}
.player-bio .bio-team {
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--color-heading);
  text-decoration: none;
  border-bottom: 1px solid var(--color-rule-soft);
  padding-bottom: 1px;
}
.player-bio .bio-team:hover {
  color: var(--color-accent);
  background: none;
  border-bottom-color: var(--color-accent);
}
.player-bio .bio-sep { color: var(--color-faint); }
.player-bio .bio-span { color: var(--color-muted); }

/* ----- Playoff bracket ----- */

/* Wrap the 4-column bracket in a horizontal swipe container — at narrow
   viewports the columns sum to ~750px and need to scroll, not collapse. */
#bracket {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.bracket-grid {
  display: grid;
  grid-template-columns: repeat(4, max-content);
  justify-content: start;
  column-gap: 2rem;
  row-gap: 1.1rem;
  margin: 0.4rem 0 1rem;
  position: relative;
}
.bracket-grid.east-only,
.bracket-grid.west-only {
  grid-template-columns: repeat(3, max-content);
}

/* Round wrappers are invisible to layout on desktop — children's explicit
   grid-column / grid-row land them in the parent .bracket-grid as before. */
.round { display: contents; }

/* Round-nav tab bar and per-round east/west labels: hidden on desktop,
   shown only in the mobile swipe layout below. */
.round-nav,
.round-conf-label { display: none; }

/* Narrow viewports: 4 columns can't fit. Switch to a horizontal
   scroll-snap carousel — one round per viewport-width panel, swipe (or
   tap a round-nav button) to advance. Each panel lays out its own
   matchups vertically. */
@media (max-width: 720px) {
  /* Bracket-grid uses width:100vw + negative margin to break out of the
     container's padding. Clip body overflow as a safety net so any
     fractional miscalc never makes the whole page scrollable sideways. */
  html, body { overflow-x: clip; }

  /* Round-nav tab bar above the swipe area. Scrolls horizontally as a
     safety net if labels + viewport don't allow flex:1 division to fit. */
  .round-nav {
    display: flex;
    gap: 0.35rem;
    margin: 0.4rem 0 0.6rem;
    width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .round-nav::-webkit-scrollbar { display: none; }
  .round-nav-btn {
    flex: 1 1 0;
    min-width: 0;
    background: none;
    border: 1px solid var(--color-rule-soft);
    color: var(--color-muted);
    font: inherit;
    font-size: 0.62rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 0.4rem 0.2rem;
    cursor: pointer;
    border-radius: 2px;
    white-space: nowrap;
  }
  .round-nav-btn[aria-current="true"] {
    color: var(--color-heading);
    border-color: var(--color-rule);
    font-weight: 600;
  }

  /* Carousel: full-bleed horizontal scroll with mandatory snap. The
     trailing right-padding lets the last (Finals) panel snap to the same
     start-x as every other panel. */
  #bracket {
    overflow-x: visible;
  }
  .bracket-grid {
    display: flex;
    flex-direction: row;
    overflow-x: auto;
    overflow-y: visible;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    column-gap: 0;
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    padding: 0 1rem;
    scroll-padding: 0 1rem;
  }
  .bracket-grid::-webkit-scrollbar { height: 4px; }
  .bracket-grid::-webkit-scrollbar-thumb {
    background: var(--color-rule-soft);
    border-radius: 3px;
  }

  /* Each round is one snap panel, narrower than the viewport so the next
     round always peeks on the right edge — the swipe affordance, ESPN-
     bracket style. The peek also lets you eyeball which series a matchup
     feeds before you swipe to it. */
  .bracket-grid { --panel-peek: 6.5rem; padding-right: var(--panel-peek); }

  /* Every round shares one fixed-row grid so card centres line up
     vertically across panels: swipe R1 → Semis and the Semis card sits
     exactly at the midpoint of the two R1 cards that feed it. Rows:
       1–4 east slots · 5 east/west gap · 6–9 west slots. */
  .round {
    /* Uniform slot-row floor so every round's rows are identical and cards
       align across panels to the pixel (a completed-series card is well
       under this height). Taller cards — rare pre-playoff multi-candidate
       or expanded slots — grow their row via `auto` rather than overlap,
       trading a little cross-panel drift for never colliding. */
    --slot-row: 8.5rem;
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows:
      repeat(4, minmax(var(--slot-row), auto))   /*  1–4  east slots  */
      1.4rem                                     /*  5  east/west gap */
      repeat(4, minmax(var(--slot-row), auto));  /*  6–9  west slots  */
    row-gap: 0.4rem;
    flex: 0 0 calc(100vw - var(--panel-peek));
    scroll-snap-align: start;
    scroll-snap-stop: always;
    align-content: start;
  }
  /* Single-conference views only render one half — drop the west rows. */
  .bracket-grid.east-only .round,
  .bracket-grid.west-only .round {
    grid-template-rows: repeat(4, minmax(var(--slot-row), auto));
  }
  .round + .round { margin-left: 0.9rem; }

  /* Place each card into its fixed mobile row (set by JS as --m-row), centred
     horizontally in the panel. Compact fit-content width keeps the team name
     next to its win-prob cells (no stretched middle gap). A full box (instead
     of the desktop top/bottom rules) gives the connector lines a clear card
     to terminate against. Multi-row cards (Semis/CF/Finals) centre vertically
     within their span. */
  .round > .matchup {
    grid-column: 1 !important;
    grid-row: var(--m-row, auto) !important;
    align-self: center;
    justify-self: center;
    width: fit-content;
    max-width: 100%;
    margin: 0;
    border: 1.25px solid var(--color-rule);
    border-radius: 0;
  }
  /* Scale the compact card up for touch / readability — bigger type and
     bigger bucket squares (the series-length distribution is the card's
     data viz, so give it room), no column stretching so the team name
     stays right next to its cells. The wider card also fills most of the
     panel, leaving just the branch space on the right where the next
     round peeks. Buckets stay square — same width and height — centered
     in their column. */
  .round .matchup-row,
  .round .matchup-axis {
    grid-template-columns: 1.1rem 2.8rem repeat(4, 1.9rem) 3rem;
    column-gap: 0.4rem;
    font-size: 0.95rem;
  }
  .round .matchup-row .bucket {
    width: 23px;
    height: 23px;
    justify-self: center;
    align-self: center;
  }
  .round .matchup-axis { font-size: 0.72rem; }

  /* No East/West dividers on mobile — the vertical gap row separates the
     two halves on its own. */
  .round-conf-label { display: none; }
  /* Round name already shown in the active .round-nav tab — no need to
     repeat it inside the panel. */
  .round > .bracket-round-head { display: none; }
}

/* Hairline orthogonal connectors between parent and child matchups —
   drawn in the inter-column gaps. Lives behind the cards (cards have
   opaque paper bg so paths only show in the gaps). */
.bracket-connectors {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
.bracket-connectors path {
  fill: none;
  stroke: #c8c8c8;
  stroke-width: 1;
  shape-rendering: crispEdges;
}
/* Single-conference views handled in the rule above. */

.bracket-round-head {
  border-bottom: 1px solid var(--color-rule);
  padding-bottom: 0.25rem;
  margin-bottom: 0.15rem;
  width: fit-content;
  max-width: 100%;
  margin-left: auto;
  margin-right: auto;
  position: relative;
  background: var(--color-bg);
  font-size: 0.62rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--color-text);
  text-align: center;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}

/* Per-card axis tick row above each matchup's team rows. Grid template
   must match .matchup-row exactly so the 4-0…4-3 ticks line up over their
   bucket cells — use rem (root-relative) so the differing font-size on
   this element doesn't reflow the column widths. Bucket columns are wider
   than the squares to fit the series-score labels; squares center within. */
.matchup-axis {
  display: grid;
  grid-template-columns: 0.85rem 1.75rem repeat(4, 1.4rem) 2rem;
  column-gap: 0.28rem;
  padding-bottom: 0.12rem;
  font-size: 0.58rem;
  color: var(--color-text);
  letter-spacing: 0.06em;
  text-align: center;
}

.bracket-conf-rule {
  grid-column: 1 / -1;
  border-top: 1px solid var(--color-rule);
  margin: 0.25rem 0;
}
.bracket-conf-label {
  grid-column: 1 / -1;
  font-size: 0.62rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--color-faint);
  border-bottom: 1px dotted var(--color-rule-soft);
  padding-bottom: 0.15rem;
  margin-top: 0.25rem;
}

/* Each matchup is a distinct boxed card: full near-black hairline border,
   no fill, content sized to be unambiguous in monospace. Cards hug their
   content (fit-content width) and center within their bracket column so
   the cells don't drift to the left edge of a wide column. Internal
   separation is whitespace, not rules — the only line inside the card is
   the one above the series-score footer. */
.matchup {
  border: 1.25px solid var(--color-rule);
  padding: 0.3rem 0.22rem 0.35rem 0.58rem;
  background: var(--color-bg);
  font-variant-numeric: tabular-nums;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  position: relative;
}
.matchup.span2  { align-self: center; }
.matchup.span4  { align-self: center; }
.matchup.spanall{ align-self: center; }

.matchup-row {
  display: grid;
  grid-template-columns: 0.85rem 1.75rem repeat(4, 1.4rem) 2rem;
  align-items: center;
  column-gap: 0.28rem;
  font-size: 0.78rem;
  /* No divider between rows anymore — keep them tight. */
  padding: 0.08rem 0;
  color: var(--color-text);
}
.matchup-row + .matchup-row {
  border-top: none;
}
.matchup-row .seed {
  font-size: 0.62rem;
  color: var(--color-faint);
  letter-spacing: 0.08em;
  text-align: center;
}
.matchup-row .team {
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--color-text);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 0;
}
.matchup-row .team:hover {
  color: var(--color-accent);
  background: none;
  border-bottom-color: var(--color-accent);
}
.matchup-row.lead .team { color: var(--color-heading); }
.matchup-row.dim  .team,
.matchup-row.dim  .seed,
.matchup-row.dim  .winprob { color: #9a9a9a; }

.matchup-row .bucket {
  position: relative;
  /* Whole-pixel size + a real border (not box-shadow): fractional rem sizes
     land cells on sub-pixel coordinates and the antialiasing renders some
     edges thick and some thin. Borders get paint-snapped; 15px ≈ 1.1rem. */
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  justify-self: center;
  align-self: center;
  background: var(--color-bg);
  border: 1.25px solid var(--color-rule);
}
.matchup-row .bucket::after {
  /* CSS-driven tooltip — native title is unreliable on tiny elements
     and has a long delay. data-tip carries the formatted hover text. */
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  padding: 0.22rem 0.5rem;
  /* Match the site-wide row-tooltip idiom: paper bg, ink border, hard
     offset shadow — not an inverted box. */
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-rule);
  box-shadow: 2px 2px 0 var(--color-rule-soft);
  font-size: 0.65rem;
  letter-spacing: 0.04em;
  text-transform: none;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.05s ease-out;
  z-index: 10;
}
.matchup-row .bucket:hover::after { opacity: 1; }

.matchup-row .winprob {
  text-align: center;
  font-size: 0.78rem;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  padding: 0.05rem 0.2rem;
}
.matchup-meta {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--color-text);
  /* Rows are tight against each other, so the footer needs its own margin
     to keep the last row's squares off the rule. */
  margin-top: 0.28rem;
  padding-top: 0.25rem;
  border-top: 1px solid #c8c8c8;
  text-align: center;
}
.matchup-meta .score-team { color: var(--color-text); font-weight: 700; }

.matchup-more {
  display: block;
  width: 100%;
  margin-top: 0.3rem;
  padding: 0.1rem 0;
  background: none;
  border: none;
  border-top: 1px solid #c8c8c8;
  color: var(--color-text);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  cursor: pointer;
  text-align: center;
}
.matchup-more:hover { color: var(--color-accent); }

.bracket-empty {
  color: var(--color-muted);
  font-size: 0.85rem;
  margin: 1rem 0;
}


/* Bracket mobile rules live in the (max-width: 720px) block above — keep
   them together so source order matches specificity. */


/* ----- RAPM page — peaks section ----- */

.section-title {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--color-muted);
  margin: 1.4rem 0 0.35rem;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--color-rule);
}
.section-note {
  margin: 0 0 0.7rem;
  max-width: 48rem;
}
.peaks-section { margin-top: 0.9rem; }
.leaderboard-section { margin-top: 2rem; }

.peaks-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
  align-items: start;
}

.peaks-table-wrap table.table { font-size: 0.78rem; }
.peaks-table-wrap table.table th,
.peaks-table-wrap table.table td { padding: 0.18rem 0.4rem; }
.peaks-table-wrap tr.peak-row { cursor: pointer; }
.peaks-table-wrap tr.peak-row:hover { background: var(--color-bg-alt); }
.peaks-table-wrap tr.peak-row.selected td:first-child {
  position: relative;
}
.peaks-table-wrap tr.peak-row.selected td:first-child::before {
  content: "";
  position: absolute;
  left: 0; top: 4px; bottom: 4px;
  width: 3px;
  background: var(--swatch, var(--color-accent));
}

#peaks-chart {
  width: 100%;
  min-height: 280px;
  /* On narrow viewports the SVG is floored at a readable min width (see
     drawPeaksChart) and overflows; let it swipe horizontally instead of
     crushing the axes. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
#peaks-chart svg { display: block; }
#peaks-chart .traj {
  fill: none;
  stroke-width: 1.1;
  stroke-linejoin: round;
  stroke-linecap: round;
}
#peaks-chart .traj-proj {
  stroke-dasharray: 3 3;
  opacity: 0.85;
}
#peaks-chart .peak-dot {
  stroke: var(--color-bg);
  stroke-width: 1;
}
#peaks-chart .traj-label {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 600;
  fill: var(--color-heading);
  paint-order: stroke;
  stroke: var(--color-bg);
  stroke-width: 3;
  stroke-linejoin: round;
}
.peaks-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.6rem;
  margin-bottom: 0.45rem;
  min-height: 1.1rem;
  font-size: 0.72rem;
}
.peaks-legend .chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--color-rule-soft);
  background: var(--color-bg);
  cursor: pointer;
}
.peaks-legend .chip:hover { background: var(--color-bg-alt); }
.peaks-legend .chip .sw {
  width: 0.7rem;
  height: 0.7rem;
  display: inline-block;
}
.peaks-legend .chip .x {
  color: var(--color-muted);
  margin-left: 0.15rem;
}
.peaks-legend .empty { color: var(--color-muted); }

/* ---- Loading / error states ---------------------------------------- */
/* Dot-square loading indicator: 3×3 pixel grid, the lit pixel orbits the
   perimeter with a fading tail. Injected by util.js loadData() after a
   short delay so fast loads never show it. */
.load-state {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  color: var(--color-muted);
  padding: 0.6rem 0;
}
.ldr { position: relative; display: inline-block; width: 13px; height: 13px; }
.ldr i {
  position: absolute;
  width: 3px;
  height: 3px;
  background: var(--color-muted);
  opacity: 0.15;
  animation: ldr-orbit 1.6s infinite;
}
.ldr .p0 { left: 0;    top: 0;    animation-delay: 0s; }
.ldr .p1 { left: 5px;  top: 0;    animation-delay: 0.2s; }
.ldr .p2 { left: 10px; top: 0;    animation-delay: 0.4s; }
.ldr .p3 { left: 10px; top: 5px;  animation-delay: 0.6s; }
.ldr .p4 { left: 10px; top: 10px; animation-delay: 0.8s; }
.ldr .p5 { left: 5px;  top: 10px; animation-delay: 1.0s; }
.ldr .p6 { left: 0;    top: 10px; animation-delay: 1.2s; }
.ldr .p7 { left: 0;    top: 5px;  animation-delay: 1.4s; }
@keyframes ldr-orbit {
  0%, 8%     { opacity: 1; background: var(--color-text); }
  30%        { opacity: 0.45; }
  55%, 100%  { opacity: 0.15; }
}
@media (prefers-reduced-motion: reduce) {
  .ldr i { animation: none; opacity: 0.4; }
}

/* Quiet failure note rendered by loadData() when primary page data 404s. */
.load-error {
  font-size: 0.8rem;
  color: var(--color-muted);
  border-left: 2px solid var(--color-warn);
  padding: 0.2rem 0 0.2rem 0.6rem;
  margin: 0.6rem 0;
}

/* In-place refresh (e.g. RAPM month scrub): dim, don't announce. */
.is-refreshing { opacity: 0.45; transition: opacity 0.15s ease; }
