/* ============================================================================
   LLCAA Record Book — design system
   Deep slate chrome so 138 team colors carry the color; championship gold as the
   single accent; Saira Condensed (scoreboard/almanac) over Inter.
   ========================================================================== */

:root {
  --bg:        #0c0f15;
  --surface:   #141922;
  --surface-2: #1b2230;
  --raised:    #222b3a;
  --line:      #29323f;
  --line-soft: #1f2733;

  --ink:       #eef2f7;
  --ink-dim:   #b7c0cd;
  --muted:     #7d8798;
  --faint:     #5a6474;

  --gold:      #d6a94e;
  --gold-lite: #f0cd84;
  --gold-deep: #a97e2f;

  --radius:    12px;
  --radius-sm: 8px;
  --team:      #3a4250;

  --sidebar-w: 244px;
  --rail-w:    68px;
  --mbar-h:    58px;   /* the phone's bottom bar; see the MOBILE section */

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --cond: "Saira Condensed", "Inter", sans-serif;
}

* { box-sizing: border-box; }
body {
  margin: 0; font-family: var(--sans); background: var(--bg); color: var(--ink);
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
h1, h2, h3 { margin: 0; font-family: var(--cond); font-weight: 700; letter-spacing: .01em; }

/* ---- layout ------------------------------------------------------------- */
.layout { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; transition: grid-template-columns .18s ease; }
.layout.is-collapsed { grid-template-columns: var(--rail-w) 1fr; }

.sidebar {
  position: sticky; top: 0; align-self: start; height: 100vh; overflow: hidden;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg, #10141c, var(--bg));
  border-right: 1px solid var(--line); padding: 16px 12px;
}
.sidebar-top { display: flex; align-items: center; gap: 6px; padding: 4px 4px 16px; }
.brand { display: flex; align-items: center; gap: 11px; min-width: 0; flex: 1; }
.brand-crest { width: 38px; height: 38px; object-fit: contain; flex: none; }
.brand-text { display: flex; flex-direction: column; line-height: 1; min-width: 0; }
.brand-name { font-family: var(--cond); font-weight: 700; font-size: 1.35rem; letter-spacing: .04em; }
.brand-sub { font-size: .64rem; letter-spacing: .22em; text-transform: uppercase; color: var(--gold); margin-top: 3px; }

.rail-toggle {
  flex: none; display: grid; place-items: center; width: 28px; height: 28px;
  border: 1px solid var(--line); border-radius: 7px; background: var(--surface);
  color: var(--muted); cursor: pointer; transition: color .15s, transform .18s;
}
.rail-toggle:hover { color: var(--ink); }
.is-collapsed .rail-toggle svg { transform: rotate(180deg); }

/* nav */
.nav { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; flex: 1; overflow-y: auto; }
.nav-group { display: flex; flex-direction: column; }
.nav-group-head {
  display: flex; align-items: center; gap: 6px; width: 100%;
  background: none; border: none; cursor: pointer; padding: 6px 8px;
  font-family: var(--sans); color: var(--faint);
}
.nav-group-label { font-size: .64rem; letter-spacing: .2em; text-transform: uppercase; }
.nav-chevron { margin-left: auto; transition: transform .18s; color: var(--faint); }
.nav-group:not(.is-open) .nav-chevron { transform: rotate(-90deg); }
/* A group with no label renders no head at all: the five single-entry sections
   read as one item each rather than a heading with one thing under it. Their
   items sit level with the labelled groups' items, not with their heads. */
.nav-group--bare { margin-top: -4px; }

/* max-height is the accordion's animation target, so it has to clear the
   TALLEST group — History runs to seven entries (~273px plus gaps) and would
   be clipped at the old 320px. */
.nav-items { display: flex; flex-direction: column; gap: 2px; overflow: hidden; max-height: 420px; transition: max-height .2s ease; }
.nav-group:not(.is-open) .nav-items { max-height: 0; }

.nav-link {
  display: flex; align-items: center; gap: 11px; padding: 8px 10px; border-radius: var(--radius-sm);
  font-size: .9rem; font-weight: 500; color: var(--ink-dim);
  transition: background .15s, color .15s; white-space: nowrap;
}
a.nav-link:hover { background: var(--surface-2); color: var(--ink); }
.nav-link.is-active { background: var(--surface-2); color: var(--ink); box-shadow: inset 2px 0 0 var(--gold); }
.nav-link.is-soon { color: var(--faint); cursor: default; }
.nav-ico { flex: none; display: grid; place-items: center; width: 20px; height: 20px; color: currentColor; }
.nav-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.soon {
  margin-left: auto; font-size: .56rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--faint); border: 1px solid var(--line); border-radius: 20px; padding: 2px 6px;
}
/* dev-only nav entries — hidden from prod entirely, so the chip reads as a
   build marker rather than a promise to the league. */
.soon--dev { color: var(--gold-deep); border-color: var(--gold-deep); }
.signout {
  display: flex; align-items: center; gap: 11px; margin-top: 10px; padding: 8px 10px;
  font-size: .85rem; color: var(--muted); border-top: 1px solid var(--line-soft);
}
.signout:hover { color: var(--ink); }

/* collapsed rail: icons only */
.is-collapsed .brand-text,
.is-collapsed .nav-group-label,
.is-collapsed .nav-chevron,
.is-collapsed .nav-name,
.is-collapsed .soon { display: none; }
.is-collapsed .brand { justify-content: center; gap: 0; }
.is-collapsed .sidebar-top { flex-direction: column; gap: 10px; }
.is-collapsed .nav-group-head { justify-content: center; padding: 2px; pointer-events: none; }
.is-collapsed .nav-items { max-height: none !important; }
.is-collapsed .nav-link { justify-content: center; padding: 9px 0; }
.is-collapsed .signout { justify-content: center; }
.is-collapsed .nav-group { border-top: 1px solid var(--line-soft); padding-top: 6px; }

.main { padding: 28px 34px 60px; max-width: 1600px; }

/* ---- page header + season dropdown -------------------------------------- */
.page-head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  margin-bottom: 24px; padding-bottom: 18px; border-bottom: 1px solid var(--line);
}
.eyebrow { font-size: .7rem; letter-spacing: .18em; text-transform: uppercase; color: var(--gold); }
.page-head h1 { font-size: 2.9rem; line-height: .98; margin-top: 6px; }
/* The dashboard headline is itself the season picker, on the team hub's
   overlaid-select control (`.tb-pick`, further down). These three only adapt it
   to 2.9rem type — and each is written to (0,2,x) on purpose, because both the
   rules they override sit LATER in this file: `.tb-pick` (the picker's own
   baseline alignment) and the phone block's `.page-head h1` margin. */
.page-head .page-head-pick { align-items: center; }
.page-head .page-head-pick h1 { margin-top: 0; }
.page-head .page-head-pick .tb-caret { stroke-width: 2.2; }

.season-picker { display: flex; flex-direction: column; gap: 5px; }
.season-picker-label { font-size: .62rem; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }
.season-select-wrap { position: relative; }
.season-select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  font-family: var(--cond); font-weight: 600; font-size: 1.35rem; letter-spacing: .02em;
  color: var(--ink); background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 6px 40px 6px 16px; min-width: 118px;
}
.season-select:hover { border-color: var(--gold-deep); }
.season-select:focus-visible { outline: 2px solid var(--gold-lite); }
.season-caret { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: var(--gold); pointer-events: none; }

/* ---- champion card (lives inside the bracket center column) -------------- */
.champ-card {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  width: 100%; margin-top: 8px; padding: 14px 12px 16px; text-align: center;
  border-radius: var(--radius);
  background: linear-gradient(180deg, color-mix(in srgb, var(--team) 34%, var(--surface)), var(--surface) 88%);
  border: 1px solid var(--gold-deep); border-top: 3px solid var(--team);
}
.champ-card-logo { width: 52px; height: 52px; object-fit: contain; margin-bottom: 2px; }
.champ-card-label { font-size: .6rem; letter-spacing: .2em; text-transform: uppercase; color: var(--gold-lite); }
.champ-card-name {
  font-family: var(--cond); font-weight: 700; line-height: 1.04;
  font-size: clamp(1.3rem, calc(.85rem + .82cqi), 1.75rem);
  overflow-wrap: break-word;
}
.champ-card-line { font-size: .78rem; color: var(--ink-dim); margin-top: 2px; }

/* ---- panels ------------------------------------------------------------- */
.panel { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px; margin-bottom: 22px; }
.panel-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 16px; }
.panel-head h2 { font-size: 1.35rem; }
.panel-sub { font-size: .72rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
.empty { color: var(--muted); font-size: .9rem; padding: 8px 0; }

/* Shared empty-state (empty_state macro): centered, muted, reads as intentional. */
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 30px 16px; color: var(--faint); text-align: center; }
.empty-state-ico { width: 30px; height: 30px; opacity: .7; }
.empty-state-msg { font-size: .82rem; letter-spacing: .04em; }

/* ============================================================================
   Playoff bracket — the signature. It folds to fit, in three states:

     one column   every round stacked, top to bottom
     ladder       first round → quarters → semis → title, left to right
     mirrored     the printed-bracket shape, halves converging on the center

   It answers to the PANEL's width, not the window's, because collapsing the
   sidebar changes the room it has without the viewport moving at all. Team
   names WRAP; nothing in here is ever allowed to truncate a school.
   ========================================================================== */
.bracket-panel { container: bracket / inline-size; }
.bracket-scroll { overflow-x: auto; padding: 4px 2px 12px; }
/* `min-width: min-content` is the load-bearing line: the columns floor at the
   widest school name they hold, so the bracket grows and the panel scrolls
   rather than a column shrinking until "Northwestern" breaks in half. */
.bracket {
  --logo: 30px;
  --name: .95rem;
  --score: 1.05rem;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px;
  align-items: stretch; width: 100%; min-width: min-content;
}
/* The halves are grouping markup for the mirror, not boxes — the round columns
   themselves are the grid items, so a half can be split across two rows. */
.bhalf { display: contents; }

/* each round column: a fixed label on top, then a body that spreads its games
   evenly in the remaining height — so rounds line up across columns and the
   later rounds sit centered between their feeders. */
.bcol { display: flex; flex-direction: column; }
/* A bowl crest hangs 42px above its game and belongs to no column in
   particular, so EVERY column reserves that much twice over: once as top
   padding, for the first game in the body, and once as the gap, for every game
   after it. Reserving it uniformly is what keeps the rounds level with each
   other — the alternative, giving the room only to the games that carry a
   crest, walks those games out of line with their feeders.

   The two paddings are also what centers a later round on its feeders. With
   `space-around`, the midpoint between two games sits at (body height / 2) plus
   the top padding's half, and a lone game centered in a body padded ONLY at the
   top lands on exactly that line — whatever the gap, whatever the cell heights.
   A bottom padding here would drop it half a padding high, which is what the
   semifinals were doing. */
.bcol-body {
  flex: 1; display: flex; flex-direction: column; justify-content: space-around;
  gap: 48px; padding-top: 42px;
}
.bcol-body--one { justify-content: center; }
/* `min-content`, and it has to stay a keyword: an explicit length here REPLACES
   `min-width: auto` on the grid item, which is the only thing telling the champ
   track to floor at the row it holds. A flat 210px pinned the track below the
   title cell — bigger type, bigger logo, wider padding than any other round —
   and the cell spilled over its neighbours. The keyword floors it at the cell
   instead, and the six round tracks share what is left. */
.bcol--champ { align-items: center; justify-content: center; gap: 8px; min-width: min-content; }
/* Stacked order: both halves of a round together, under one label. */
.bhalf--left  > .bcol--fr { order: 1; }
.bhalf--right > .bcol--fr { order: 2; }
.bhalf--left  > .bcol--qf { order: 3; }
.bhalf--right > .bcol--qf { order: 4; }
.bhalf--left  > .bcol--sf { order: 5; }
.bhalf--right > .bcol--sf { order: 6; }
/* The right half continues a round the left half already named. */
.bhalf--right .rlabel { display: none; }
.bcol--champ { order: 7; }

.rlabel {
  font-family: var(--cond); font-weight: 600; font-size: .78rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted); text-align: center; padding-bottom: 10px;
}
.rlabel--champ { color: var(--gold-lite); padding-bottom: 4px; }
.cfp-mark { width: 54px; height: 54px; object-fit: contain; margin-bottom: 2px; }

/* ---- phone: the stacked state, packed two games to a row ------------------
   Stacked used to mean one game per row, which put ~1200px of bracket on a
   390px screen: you scrolled past five pills to reach the quarterfinals and
   never saw a round whole. The round columns stay — First Round, Quarterfinals,
   Semifinals, National Championship still label their own block — but each
   round's body is a two-up grid, so a four-game round is two rows instead of
   four and the whole bracket is a short scroll rather than a long one.

   The crest reservation shrinks with the crest. Every body still reserves it
   uniformly, for the reason the base rule gives: giving the room only to the
   games that carry a bowl badge walks those games out of line. */
@container bracket (max-width: 639.98px) {
  .bracket {
    --logo: 21px;
    --name: .8rem;
    --score: .92rem;
    gap: 6px;
  }
  /* Two up, and the row gap is the crest's clearance rather than breathing
     room — a bowl badge hangs above its cell and would otherwise land on the
     row before it. */
  .bcol-body {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    align-content: start; gap: 26px 7px; padding-top: 24px;
  }
  /* A lone semifinal sits in the middle at the width it would have had beside
     a sibling, not stretched across the screen. */
  .bcol-body--one { grid-template-columns: minmax(0, 58%); justify-content: center; }
  .game { max-width: none; align-self: stretch; }
  .team-line { gap: 5px; padding: 4px 6px; min-height: calc(var(--logo) + 9px); }
  .seed { width: 12px; font-size: .72rem; }
  .team-line-score { min-width: 20px; padding: 1px 4px; }
  .game-bowl { width: 24px; height: 24px; top: -4px; }
  .rlabel { font-size: .68rem; letter-spacing: .1em; padding-bottom: 4px; }
  /* The title game keeps its extra weight, but on a phone that is type and
     border — not the desktop's roomier padding, which it cannot afford. */
  .bcol--champ { gap: 5px; }
  .bcol--champ .game { max-width: 260px; }
  .is-champ .team-line { padding: 6px 8px; min-height: calc(var(--logo) + 16px); }
  .is-champ .team-line-logo { width: calc(var(--logo) + 6px); height: calc(var(--logo) + 6px); }
  .cfp-mark { width: 38px; height: 38px; }
  .champ-card { max-width: 260px; margin-top: 6px; padding: 10px 10px 12px; }
  .champ-card-logo { width: 40px; height: 40px; }
}

/* ---- ladder: one column per round, left half above right ----------------- */
@container bracket (min-width: 640px) {
  .bracket.is-12 { grid-template-columns: [fr] 1fr [qf] 1fr [sf] 1fr [champ] 1fr; }
  .bracket.is-4  { grid-template-columns: [sf] 1fr [champ] 1fr; }
  /* BCS era: a title game and nothing under it. Named so `grid-column: champ`
     resolves to a real track instead of spilling into an implicit one. */
  .bracket.is-title { grid-template-columns: [champ] minmax(min-content, 440px); justify-content: center; }
  .bcol--fr { grid-column: fr; }
  .bcol--qf { grid-column: qf; }
  .bcol--sf { grid-column: sf; }
  .bcol--champ { grid-column: champ; grid-row: 1 / span 2; }
  .bhalf--left  > .bcol { grid-row: 1; }
  .bhalf--right > .bcol { grid-row: 2; }
}

/* ---- mirrored: the shape everyone knows, championship in the middle ------- */
@container bracket (min-width: 1180px) {
  .bracket {
    --logo: clamp(26px, 2.1cqi, 36px);
    --name: clamp(.92rem, calc(.6rem + .5cqi), 1.08rem);
    --score: clamp(1rem, calc(.62rem + .56cqi), 1.24rem);
    min-height: 430px;
  }
  .bracket.is-12 {
    grid-template-columns:
      [frL] 1fr [qfL] 1fr [sfL] 1fr [champ] 1fr [sfR] 1fr [qfR] 1fr [frR] 1fr;
  }
  .bracket.is-4 { grid-template-columns: [sfL] 1fr [champ] 1fr [sfR] 1fr; }
  .bracket.is-title { grid-template-columns: [champ] minmax(min-content, 460px); justify-content: center; }
  .bhalf--left  > .bcol--fr { grid-column: frL; }
  .bhalf--left  > .bcol--qf { grid-column: qfL; }
  .bhalf--left  > .bcol--sf { grid-column: sfL; }
  .bhalf--right > .bcol--sf { grid-column: sfR; }
  .bhalf--right > .bcol--qf { grid-column: qfR; }
  .bhalf--right > .bcol--fr { grid-column: frR; }
  .bracket > .bcol, .bhalf > .bcol { grid-row: 1; }
  .bhalf--right .rlabel { display: block; }
}

/* game cell — capped so an ultra-wide panel gives the bracket air rather than
   seven stretched boxes, and centered in its column when it hits the cap.

   `min-width: min-content` is what makes the column floor above ACTUALLY reach
   the text: a percentage width contributes nothing to an ancestor's min-content
   size, so `width: 100%` alone left every track free to size below the row it
   holds. The championship cell — bigger type, bigger logo, wider padding than
   any other round — was the first to run out of room, and its score badge was
   laid out past the gold border. With the floor in place the track grows to the
   row instead, and the badge stays inside the box. */
.game {
  position: relative; background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  width: 100%; min-width: min-content; max-width: 320px; align-self: center;
}
.game.is-champ {
  border-color: var(--gold-deep); box-shadow: 0 0 0 1px var(--gold-deep), 0 8px 28px rgba(0,0,0,.35);
}
/* bowl badge sits centered just ABOVE its game cell — within the cell's own
   width so it never overlaps the neighboring round's boxes. */
.game-bowl {
  position: absolute; top: -8px; left: 50%; transform: translate(-50%, -100%);
  width: 34px; height: 34px;
  object-fit: contain; padding: 2px; background: var(--bg); border: 1px solid var(--line);
  border-radius: 50%; z-index: 3; box-shadow: 0 2px 6px rgba(0,0,0,.4);
}

/* The cell-wide box-score link: an empty anchor stretched over the whole card,
   BENEATH the team names (which sit at z-index 2) so a click on a school still
   goes to its hub and everything else — logo, seed, score, the gap between —
   goes to the game. Below the bowl badge's z-index 3 as well; the badge is
   drawn outside the cell anyway. */
.game-open { position: absolute; inset: 0; z-index: 1; border-radius: inherit; }
.game.is-linked { cursor: pointer; transition: border-color .12s ease, box-shadow .12s ease; }
.game.is-linked:hover { border-color: var(--gold-deep); box-shadow: 0 0 0 1px var(--gold-deep); }
.game.is-champ.is-linked:hover {
  box-shadow: 0 0 0 1px var(--gold), 0 8px 28px rgba(0,0,0,.45);
}
.game-open:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* team line — logos sized to fill the cell height (winner shown via the gold
   score highlight, NOT by dimming the loser, so every logo stays vivid). */
.team-line { display: flex; align-items: center; gap: 8px; padding: 6px 10px; min-height: calc(var(--logo) + 12px); }
a.team-line-name { position: relative; z-index: 2; }
a.team-line-name:hover { color: var(--gold-lite); }
.team-line + .team-line { border-top: 1px solid var(--line-soft); }
.team-line:first-child { border-top-right-radius: var(--radius-sm); }
.team-line:last-child { border-bottom-right-radius: var(--radius-sm); border-bottom-left-radius: 5px; }
.seed {
  flex: none; width: 15px; text-align: center; font-family: var(--cond); font-weight: 700;
  font-size: .82rem; color: var(--muted); font-variant-numeric: tabular-nums;
}
.team-line.is-winner .seed { color: var(--ink-dim); }
.team-line-logo { width: var(--logo); height: var(--logo); object-fit: contain; flex: none; }
.team-line-logo--none { background: var(--raised); border-radius: 50%; }
/* Saira Condensed carries a full school name in the width Inter needed for an
   abbreviation, and the name wraps at a space before it ever clips. */
.team-line-name {
  flex: 1 1 auto;
  font-family: var(--cond); font-weight: 600; font-size: var(--name);
  line-height: 1.1; letter-spacing: .012em; color: var(--ink-dim);
  overflow-wrap: break-word; hyphens: none;
}
.team-line.is-winner .team-line-name { color: var(--ink); }
.team-line-score {
  flex: none; font-family: var(--cond); font-weight: 700; font-size: var(--score);
  min-width: 24px; text-align: center; padding: 1px 6px; border-radius: 5px;
  background: rgba(0,0,0,.28); color: var(--ink-dim); font-variant-numeric: tabular-nums;
}
.team-line.is-winner .team-line-score { background: var(--gold); color: #14100a; }
/* A slot the 12-team skeleton knows about but no result has reached yet — the
   semifinals in December. Dashed and dimmed so a bracket that is still being
   played reads as unfinished at a glance, rather than as a rendering fault. */
.game.is-pending { border-style: dashed; background: transparent; }
.team-line.is-pending .team-line-name { color: var(--muted); font-style: italic; font-weight: 500; }
.team-line.is-pending .team-line-logo--none { background: rgba(255,255,255,.03); }
.is-champ .team-line { padding: 9px 13px; min-height: calc(var(--logo) + 22px); }
.is-champ .team-line-logo { width: calc(var(--logo) + 12px); height: calc(var(--logo) + 12px); }
.is-champ .team-line-name { font-size: calc(var(--name) + .16rem); font-weight: 700; }
.is-champ .team-line-score { font-size: calc(var(--score) + .16rem); }

/* ---- dashboard: bracket on top, the poll beside the leaders -------------- */
/* The bracket needs the full width — seven columns of school names — so it
   keeps the top of the page on its own. Beneath it the POLL and the LEADERS
   sit side by side: who is ranked and who is putting up the numbers, the two
   reads a check-in visit actually makes. The leaders take the wide side
   because six boards need it and twenty-five poll rows do not — the poll
   keeps a fixed track a little wider than it had when the schedule owned the
   row, so the pills breathe. (The week's games have a page of their own now —
   /schedule.) */
.dash-below { display: grid; grid-template-columns: minmax(0, 360px) minmax(0, 1fr); gap: 0 22px; align-items: start; }
.poll-panel, .sched-panel, .leaders-panel { min-width: 0; }
@media (max-width: 1000px) { .dash-below { grid-template-columns: minmax(0, 1fr); } }

/* A projected bracket is drawn in the same skeleton but must never be mistaken
   for a captured one, so the panel carries a dashed edge. Colour alone would
   not do it — the cells are already mostly the greyed "pending" state that an
   uncaptured real bracket uses. */
.bracket-panel.is-projected { border-style: dashed; }

/* The poll tab strip reuses the week-chip idiom (`.sched-week` above): same
   pill shape, same gold current state, but buttons — switching a poll is
   display state inside the panel, not navigation. */
.poll-tabs { display: flex; gap: 4px; margin: -4px 0 10px; overflow-x: auto; }
.poll-tab {
  flex: 0 0 auto; padding: 4px 9px; border-radius: 999px;
  font-family: var(--cond); font-size: .82rem; letter-spacing: .02em;
  color: var(--muted); white-space: nowrap;
  background: none; border: 1px solid transparent; cursor: pointer;
}
.poll-tab:hover { color: var(--ink); background: var(--surface-2); }
.poll-tab.is-current { color: var(--bg); background: var(--gold); font-weight: 700; }

.poll { list-style: none; margin: 0; padding: 0; }
/* Grid, not flex: the record is a COLUMN and has to land in the same place on
   every row for the eye to run down it, which a flex row with a variable-width
   pill strip in the middle cannot promise. The name track takes the slack. */
.poll-row { display: grid; grid-template-columns: 22px 24px minmax(0, 1fr) auto auto; align-items: center; gap: 0 9px; padding: 7px 8px; border-radius: 4px; }
.poll-row + .poll-row { margin-top: 2px; }
.poll-row:hover { background: var(--surface-2); }
.poll-rank { font-family: var(--cond); font-weight: 700; font-size: 1.05rem; color: var(--muted); text-align: center; }
.poll-logo { width: 24px; height: 24px; object-fit: contain; }
.poll-name { min-width: 0; font-weight: 600; font-size: .92rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
a.poll-name:hover { color: var(--gold-lite); }
/* Pills follow the NAME — they qualify the team, so they belong beside it
   rather than out at the edge next to a number they have nothing to do with.
   `nowrap` for the reason the team banner's strip learned the hard way: more
   pills make the row wider, never taller. */
.poll-pills { display: inline-flex; gap: 4px; flex-wrap: nowrap; margin-left: 2px; }
.poll-rec { justify-self: end; padding-left: 8px; font-size: .78rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---- dashboard: the week scoreboard -------------------------------------- */
/* `sched-` is the panel and its navigation; `sgame-` is one game card. Two
   prefixes because they are two components — the cards are reusable and the
   week strip is not. */
/* `center`, not the `.panel-head` default of `baseline`: this head carries a
   select, and a baseline shared with a 1.35rem heading hangs it low. */
.panel-head--leaders { flex-wrap: wrap; row-gap: 8px; align-items: center; }
/* The filter bar: the cfilter's crest tabs (League History's look) doing the
   scope rail's job — its own strip above the week rail, one line that scrolls
   sideways exactly the way the rail below it does, Top 25 leading. Each tab is
   a <label> over a real checkbox — the box is stretched invisibly across the
   tab so the whole crest is the hit target and focus lands somewhere screen
   readers can name. `.is-on` is the server's word and `:has(:checked)` the
   browser's; they agree except in the beat between a click and the panel
   swapping in, which is exactly when the live one should win. */
/* `.scope-rail.sched-filter`, not `.sched-filter`: the scope-rail baseline sits
   LATER in this file and would take the margin and wrap back at equal
   specificity. */
.scope-rail.sched-filter { align-items: center; gap: 8px; flex-wrap: nowrap; margin: 0 0 10px; min-width: 0; }
.sched-filter .cfilter-tabs {
  flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; overflow-x: auto;
  scrollbar-width: thin; padding-bottom: 4px;
}
.sched-filter .cfilter-tab { flex: 0 0 auto; }
.sched-filter .cfilter-tab { position: relative; }
.sched-filter .cfilter-tab input {
  position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer;
}
.sched-filter .cfilter-tab.is-on,
.sched-filter .cfilter-tab:has(input:checked) {
  background: var(--raised); border-color: var(--gold-deep);
  box-shadow: inset 0 -2px 0 var(--gold);
}
.sched-filter .cfilter-tab:has(input:focus-visible) {
  outline: 2px solid var(--gold); outline-offset: 1px;
}
.sched-filter .sched-top25 {
  font-family: var(--cond); font-size: .72rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-dim);
  padding: 0 11px; white-space: nowrap;
}
.sched-filter .sched-top25.is-on,
.sched-filter .sched-top25:has(input:checked) { color: var(--gold); }

/* A season is twenty weeks, so the strip scrolls rather than wrapping to three
   rows and shoving the games down the page. */
.sched-weeks {
  display: flex; gap: 4px; overflow-x: auto; padding: 0 0 8px;
  margin: 0 0 12px; border-bottom: 1px solid var(--line-soft);
  scrollbar-width: thin;
}
.sched-week {
  flex: 0 0 auto; padding: 4px 9px; border-radius: 999px;
  font-family: var(--cond); font-size: .82rem; letter-spacing: .02em;
  color: var(--muted); text-decoration: none; white-space: nowrap;
  border: 1px solid transparent;
}
.sched-week:hover { color: var(--ink); background: var(--surface-2); }
.sched-week.is-current { color: var(--bg); background: var(--gold); font-weight: 700; }

.sgame-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: 10px; }
.sgame {
  display: block; padding: 9px 10px 7px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line-soft);
  text-decoration: none; color: inherit;
}
.sgame:hover { border-color: var(--line); background: var(--raised); }
/* A game with a ranked team gets a gold hairline down its left edge. It is the
   same signal as leading the grid, restated where the eye is once it is
   scanning cards rather than reading the order. */
.sgame.is-ranked { box-shadow: inset 2px 0 0 var(--gold-deep); }

.sgame-tag {
  display: block; margin: 0 0 6px;
  font-family: var(--cond); font-size: .72rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--gold);
}
.sgame-tag-sub { color: var(--muted); margin-left: 6px; letter-spacing: .03em; }

.sgame-side {
  display: grid; grid-template-columns: 16px 22px minmax(0, 1fr) auto;
  align-items: center; gap: 0 7px; padding: 3px 0;
}
.sgame-rank { font-family: var(--cond); font-size: .72rem; color: var(--gold); text-align: right; }
.sgame-logo { width: 22px; height: 22px; object-fit: contain; }
.sgame-logo--none { background: color-mix(in srgb, var(--team) 30%, var(--surface)); border-radius: 50%; }
/* The name cell is a flex pair — ellipsizing text, then the conference mark —
   so a long school name truncates instead of shoving its annotation off the
   card. The mark is deliberately small and dim: a footnote, not a second crest. */
.sgame-name { min-width: 0; display: flex; align-items: center; gap: 6px; font-size: .86rem; }
.sgame-name-text { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sgame-conf { flex: none; height: 13px; max-width: 22px; object-fit: contain; opacity: .7; }
.sgame-conf-abbr {
  flex: none; font-family: var(--cond); font-size: .58rem; font-weight: 700;
  letter-spacing: .05em; color: var(--faint);
}
.sgame-side.is-lost .sgame-conf { opacity: .4; }
.sgame-score { font-family: var(--cond); font-weight: 700; font-size: 1.02rem; font-variant-numeric: tabular-nums; }
.sgame-rec { font-size: .74rem; color: var(--faint); font-variant-numeric: tabular-nums; }
/* The loser dims rather than the winner brightening: a card of two ordinary
   lines with one dimmed reads as a result, while two bright lines and one
   brighter reads as an error in one of them. The winner also picks up its own
   colour as a wash — which is what the inline `--team` on each side is for,
   composited against the surface so a near-black team colour still shows. */
.sgame-side.is-won {
  color: var(--ink); font-weight: 600;
  background: linear-gradient(90deg,
              color-mix(in srgb, var(--team) 22%, transparent), transparent 60%);
  border-radius: 3px;
}
.sgame-side.is-lost { color: var(--muted); }
.sgame-side.is-lost .sgame-logo { opacity: .55; }

/* ---- dashboard: the season leaderboards ---------------------------------- */
.panel-head--leaders .scope-rail { margin-left: auto; }
.lead-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 18px 22px; align-items: start; }
.lead-board { min-width: 0; }
.lead-head {
  display: flex; align-items: baseline; gap: 8px; margin: 0 0 8px;
  padding-bottom: 6px; border-bottom: 1px solid var(--gold-deep);
  font-family: var(--cond); font-size: .95rem; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--gold);
}
.lead-count { margin-left: auto; font-size: .72rem; letter-spacing: .02em; color: var(--faint); text-transform: none; }

.lead-list { list-style: none; margin: 0; padding: 0; }
/* Three value columns of a fixed width so the numbers line up across all six
   boards, not just down one — they are read side by side. */
.lead-row {
  display: grid; grid-template-columns: 18px minmax(0, 1fr) repeat(3, 46px);
  align-items: center; gap: 0 6px; padding: 4px 5px; border-radius: 4px;
}
.lead-row:not(.lead-row--head):hover { background: var(--surface-2); }
.lead-row--head {
  padding-bottom: 2px; font-family: var(--cond); font-size: .68rem;
  letter-spacing: .05em; text-transform: uppercase; color: var(--faint);
}
.lead-place { font-family: var(--cond); font-size: .82rem; color: var(--muted); text-align: center; }
.lead-row.is-first .lead-place { color: var(--gold); font-weight: 700; }
.lead-name { display: flex; align-items: center; gap: 6px; min-width: 0; }
.lead-logo { width: 18px; height: 18px; object-fit: contain; flex: 0 0 auto; }
.lead-logo--none { border-radius: 50%; background: var(--raised); }
.lead-player { min-width: 0; font-size: .84rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
a.lead-player:hover { color: var(--gold-lite); }
.lead-meta { flex: 0 0 auto; font-size: .68rem; color: var(--faint); }
.lead-val { text-align: right; font-size: .82rem; font-variant-numeric: tabular-nums; }
/* The headline stat is the first value and the one that decided the order, so
   it is the only one printed at full strength. */
.lead-row .lead-val:first-of-type { font-weight: 700; color: var(--ink); }
.lead-row--head .lead-val:first-of-type { font-weight: inherit; color: inherit; }

.standings { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 26px; align-items: start; }
/* Standings has its own full-width page now, so it can fit as many conference
   columns as the viewport allows instead of the dashboard's fixed two. */
.standings-page .standings { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.standings-conf { margin-bottom: 16px; }
.conf-name { font-size: .95rem; letter-spacing: .04em; color: var(--gold-lite); padding-bottom: 6px; margin-bottom: 6px; border-bottom: 1px solid var(--line); }
.conf-table { width: 100%; border-collapse: collapse; font-size: .82rem; }
.conf-table th { font-weight: 600; font-size: .62rem; text-transform: uppercase; letter-spacing: .08em; color: var(--faint); text-align: right; padding: 2px 6px 6px; }
.conf-table th.tl { text-align: left; }
.conf-table td { padding: 4px 6px; border-top: 1px solid var(--line-soft); vertical-align: middle; }
/* content laid out inline (not display:flex on the <td>, which would drop the
   cell out of the table's border-collapse and leave a stray vertical seam). */
.team-cell { white-space: nowrap; }
.team-cell a { vertical-align: middle; }
.team-cell a:hover { color: var(--gold-lite); }
.cell-logo { width: 20px; height: 20px; object-fit: contain; vertical-align: middle; margin-right: 8px; }
.conf-table td.num { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-dim); }
.conf-table td.rk { width: 22px; text-align: center; }
.ap-badge { display: inline-block; min-width: 18px; padding: 1px 4px; border-radius: 4px; background: var(--gold); color: #14100a; font-size: .66rem; font-weight: 700; font-family: var(--cond); }

/* ============================================================================
   Records tables (Team Hub History tab / League History page)
   ========================================================================== */

/* .back-link is NOT team-page chrome — admin_boxscore.html uses it too. */
.back-link {
  display: inline-flex; align-items: center; gap: 6px; font-size: .8rem; font-weight: 600;
  color: var(--muted); padding: 7px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.back-link:hover { color: var(--ink); border-color: var(--gold-deep); }

/* shared records table */
.table-scroll { overflow-x: auto; }
.record-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
/* Ledger column head — the reusable stylized header for every record table:
   condensed gold small-caps seated on a slate band with a gold underscore rule. */
.record-table th {
  font-family: var(--cond); font-weight: 700; font-size: .8rem; text-transform: uppercase;
  letter-spacing: .14em; color: var(--gold); text-align: right; padding: 10px 12px 9px;
  white-space: nowrap; background: var(--surface-2); border-bottom: 2px solid var(--gold-deep);
}
.record-table th.tl { text-align: left; }
/* Two-tier group bands keep a quiet divider so the gold rule reads once, under
   the label/logo row — not stacked twice. */
.record-table th.lh-group { border-bottom: 1px solid var(--line); }
/* League History text columns (Season / National Champion / Heisman): centered,
   a touch larger, and vertically centered across both header rows so they sit
   level with the conference marks. */
.record-table th.lh-col-head {
  text-align: center; vertical-align: middle; font-size: .86rem; letter-spacing: .12em;
  color: var(--gold-lite);
}
/* Trophy marks over the National Champion / Heisman heads — same footing as the
   conference marks beside them: art first, small gold label under it. */
.lh-head-mark {
  display: block; margin: 0 auto 5px; height: 34px; width: auto; object-fit: contain;
  filter: drop-shadow(0 0 1px rgba(255, 255, 255, .35))
          drop-shadow(0 0 7px rgba(255, 255, 255, .1));
}
.lh-head-mark--heisman { height: 38px; }
.lh-head-label { display: block; }
.record-table td { padding: 9px 12px; border-top: 1px solid var(--line-soft); white-space: nowrap; vertical-align: middle; }
.record-table td.tl { text-align: left; }
.record-table td.num { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-dim); }
.record-table tbody tr:hover { background: var(--surface-2); }
.season-cell { font-family: var(--cond); font-weight: 700; font-size: 1.05rem; }

/* league-history team pills + champion column */
.team-pill { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.team-pill:hover .team-pill-name { color: var(--gold-lite); }
.team-pill-logo { width: 24px; height: 24px; object-fit: contain; flex: none; }
.team-pill-name { font-weight: 600; }

/* League History — national champion (the row's anchor) */
.champ-pill { align-items: center; gap: 11px; }
.champ-logo { width: 34px; height: 34px; }
.champ-col .team-pill-name { font-family: var(--cond); font-size: 1.2rem; font-weight: 700; color: var(--gold-lite); line-height: 1.05; }

/* League History — two-tier header: conference-champion group band */
.lh-group {
  text-align: center !important; font-size: .64rem; letter-spacing: .18em; text-transform: uppercase;
  color: var(--gold); border-bottom: 1px solid var(--line);
  padding-bottom: 8px !important;
}
/* Beats `.record-table th`'s right-align, which would push each mark off the
   centre of its column and out of line with the crests below it. */
.record-table th.lh-conf-th { text-align: center; padding: 6px 8px 14px; }
/* The marks sit straight on the ground — no chip. Nearly half of every mark's
   pixels are near-black in-game art, so a hard white edge plus a soft bloom
   carries the dark ones (Big Ten) instead of a container. */
.lh-conf-logo {
  height: 38px; width: auto; max-width: 96px; object-fit: contain;
  display: inline-block; vertical-align: middle;
  filter: drop-shadow(0 0 1px rgba(255, 255, 255, .5))
          drop-shadow(0 0 7px rgba(255, 255, 255, .14));
  transition: filter .18s ease, transform .18s ease;
}
.lh-conf-th:hover .lh-conf-logo {
  filter: drop-shadow(0 0 1px rgba(255, 255, 255, .7))
          drop-shadow(0 0 10px rgba(255, 255, 255, .26));
  transform: translateY(-1px);
}
@media (prefers-reduced-motion: reduce) {
  .lh-conf-logo { transition: none; }
  .lh-conf-th:hover .lh-conf-logo { transform: none; }
}

/* League History — the conference-champion crest rail (the signature) */
.cc-cell { text-align: center; }
.cc-team { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.cc-team:hover .cc-name { color: var(--gold-lite); }
.cc-logo { width: 26px; height: 26px; object-fit: contain; flex: none; }
.cc-name { font-size: .82rem; font-weight: 600; color: var(--ink-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cc-none { color: var(--faint); }
.no-data { font-size: .78rem; font-style: italic; color: var(--faint); }

/* ============================================================================
   Team History v2 (program strip, rich table) + Team Hub (schedule, sections)
   ========================================================================== */

/* program summary strip */
.program-strip {
  display: flex; flex-wrap: wrap; gap: 0; margin: -6px 0 22px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden;
}
.prog-stat { flex: 1 1 0; min-width: 96px; padding: 14px 18px; border-left: 1px solid var(--line-soft); }
.prog-stat:first-child { border-left: none; }
.prog-val { display: block; font-family: var(--cond); font-weight: 700; font-size: 1.7rem; line-height: 1; }
.prog-stat--gold .prog-val { color: var(--gold-lite); }
.prog-label { display: block; margin-top: 5px; font-size: .64rem; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }

/* finish pills (shared vocabulary) */
.pill-row { display: inline-flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.pill-row--lg { margin-top: 10px; }
/* Standings: pills trail the school name inside its cell. `nowrap` on the row
   keeps a two-pill finish (NCL + CC) on the name's line instead of dropping the
   second one under it and making that one team's row taller than the rest. */
.pill-row--inline { margin-left: 6px; flex-wrap: nowrap; }
.pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--cond); font-weight: 700; font-size: .66rem;
  letter-spacing: .05em; text-transform: uppercase; padding: 2px 7px; border-radius: 20px;
  border: 1px solid transparent; white-space: nowrap;
}
.pill-row--lg .pill { font-size: .78rem; padding: 3px 10px; }
/* Trophy marks sit on the cap-height of the label rather than its line box. */
.pill-ico { width: 1em; height: 1em; flex: none; }
.pill-ico--emoji { font-size: .95em; line-height: 1; }
/* National champion: the one solid, bright-gold pill. */
.pill--nc  { background: var(--gold); color: #14100a; }
/* Every other playoff finish: the same gold, dulled back so NC still reads first. */
.pill--pl  { background: color-mix(in srgb, var(--gold-deep) 26%, var(--surface)); border-color: var(--gold-deep); color: var(--gold-lite); }
.pill--cc  { background: color-mix(in srgb, #4bb56a 26%, var(--surface)); border-color: #3f9e5b; color: #8fe4a6; }
.pill--bw  { border-color: #3f6dbe; color: #9cc0f2; }
.pill--bl  { border-color: var(--line); color: var(--muted); }
/* A player's honours, in the same vocabulary a team's finishes use. Each paints
   its OWN background rather than tinting the ground, which is what lets these
   sit on the player banner: `--surface` and `--muted` are fixed light-UI tokens
   and would go invisible the moment a bright team colour flips the ink. */
/* An award won. DARK rather than solid gold, and the trophy image is the reason:
   the art is a gold trophy on transparent, which disappears on a gold ground.
   The gold moves to the border and the type, which reads on any team colour. */
.pill--aw  { background: rgba(12, 15, 21, .58); border-color: var(--gold-deep); color: var(--gold-lite); }
.pill-ico--art { width: 1.5em; height: 1.5em; object-fit: contain; margin: -.35em 0; }
/* An all-team honour's mark is a WORD LOCKUP, not a monogram: "ALL / CONFERENCE"
   is half again as wide as it is tall. Squaring it in the 1.5em box above
   crushes the type, so this frees the width and buys back height — the pill is
   short enough now ("2x 1st") to give the mark the room. */
.pill-ico--wide { width: auto; height: 2em; max-width: 4.2em; margin: -.7em 0; }
.pill--fin { background: rgba(12, 15, 21, .5); border-color: rgba(255, 255, 255, .22); color: #d6dae2; }
.pill--aa  { background: rgba(12, 15, 21, .5); border-color: #3f6dbe; color: #9cc0f2; }
/* Records held. Violet is the one hue this palette had not spent — gold is an
   award, blue is an all-team selection, green is a title — so a record reads as
   its own kind of thing at a glance rather than as a dimmer trophy. The mark is
   the SCHOOL's crest, which is what makes a transfer's two pills legible. */
.pill--rec { background: rgba(12, 15, 21, .5); border-color: #7a5aa8; color: #c9b1ea; }

/* rank tiers (stat league ranks) */
.rk-elite { color: var(--gold-lite) !important; }
.rk-good  { color: #7bc47f !important; }
.rk-mid   { color: var(--muted) !important; }
.rk-low   { color: var(--faint) !important; }
.rk-none  { color: var(--faint) !important; }

/* history table: stacked cells + team/season cell */
.hist-table td { vertical-align: middle; }
.year-link { font-family: var(--cond); }
.year-link:hover { color: var(--gold-lite); }
.rank-cell { font-family: var(--cond); font-weight: 700; font-size: 1.05rem; }
.team-season-cell { white-space: nowrap; }
.tsc-body { display: inline-flex; flex-direction: column; gap: 4px; vertical-align: middle; }
.tsc-conf { font-weight: 600; color: var(--ink-dim); font-size: .82rem; }
.stack-cell { text-align: right; }
.stack-main { display: block; font-family: var(--cond); font-weight: 700; font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.stack-sub { display: block; font-size: .68rem; color: var(--muted); font-variant-numeric: tabular-nums; margin-top: 1px; }

/* history table: the Conf logo column and the Trophies shelf */
.conf-cell { text-align: center; }
.conf-cell .conf-logo { height: 26px; width: auto; max-width: 46px; object-fit: contain; vertical-align: middle; }
.trophy-cell { min-width: 90px; }
.trophy-row { display: inline-flex; align-items: flex-end; gap: 7px; }
.trophy-row .trophy { display: inline-flex; align-items: flex-end; font-size: 1.1rem; line-height: 1; }
.trophy-row .trophy img { height: 34px; width: auto; max-width: 38px; object-fit: contain; display: block; transition: transform .12s ease; }
.trophy-row a.trophy:hover img { transform: scale(1.18); }

/* postseason line items (mlbdash-style) */
.ps-cell { min-width: 210px; }
.ps-line { display: flex; align-items: center; gap: 6px; font-size: .8rem; line-height: 1.7; white-space: nowrap; }
.ps-line.is-lost { color: var(--muted); }
.ps-icon { flex: none; font-size: .8rem; }
.ps-label { font-weight: 600; }
.ps-opp-logo { width: 15px; height: 15px; object-fit: contain; opacity: .85; }
.ps-opp { color: var(--faint); }
.ps-score { color: var(--muted); font-variant-numeric: tabular-nums; }
.dash { color: var(--faint); }

/* ---- Team Hub banner ----------------------------------------------------
   The persistent identity bar every hub tab sits under. Everything inside it
   is coloured with `currentColor` at an opacity step, NOT with --ink-dim /
   --muted: those are fixed light-on-dark tokens and would go invisible the
   moment a bright team colour flips the banner to dark ink. */
/* The four numbers that place everything in the banner live together, because
   they are arithmetic on each other: the stat rule sits at
   --tb-pad-t + --tb-row-h + --tb-stats-gap, and that same sum is the height of
   the crest's clip. Deriving the clip rather than eyeballing it is the point —
   the previous attempt kept the relationship in a comment and the crest
   promptly painted straight through the stat row anyway. */
.team-banner {
  --tb-pad-t: 20px;
  --tb-row-h: 112px;     /* .tb-head — the ONE knob for the identity row */
  --tb-stats-gap: 6px;   /* .tb-head to the stat rule */
  --tb-dial: 46px;       /* rating ring, and the height every stat tile's body
                            reserves so the five ranks share one baseline.
                            Deliberately small: these five are a sub-line under
                            the school, and at 62px they were reading as a
                            second banner with equal billing to the name. */
  /* Named container: the layout inside this banner is decided by how wide the
     BANNER is, never by the viewport — see the strip's query below. */
  container: banner / inline-size;
  position: relative; overflow: hidden; border-radius: var(--radius);
  padding: var(--tb-pad-t) 24px 0; margin-bottom: 22px;
  background: linear-gradient(105deg, var(--team) 0%,
              color-mix(in srgb, var(--team) 58%, var(--bg)) 100%);
  border: 1px solid var(--line);
}
.team-banner.is-ink-light { color: var(--ink); }
.team-banner.is-ink-dark  { color: #10131a; }

/* THE BAND — the school's own locker-room render, behind the identity row and
   ending at the stat rule.

   ANCHORED BOTTOM LEFT. The art is the game's 4K frame cropped to its bottom
   40% (see `gameart_to_static.py team-backgrounds`), because the floor is
   where the helmets, decals and stickers are and the upper frame is an empty
   wall. `cover` from the bottom-left corner means the narrower the banner
   gets, the more of the RIGHT of the frame goes — never the subject.

   IT IS THE GROUND FOR THE WHOLE BANNER, tab strip included (the user's call).
   It ran only to the stat rule for one release, which read as a photograph with
   a slab bolted under it rather than as one object.

   THE TINT IS THE TEAM COLOUR, NOT BLACK. Ink on this banner is chosen from
   the team colour, so a scrim in that same colour darkens or lightens exactly
   the way the ink expects; a black scrim under a bright team's dark ink would
   invert the contrast it was added to protect. Heavy at the left where the
   crest and the name sit, thin at the right where the coach stands. */
.tb-band {
  position: absolute; z-index: 0; inset: 0;
  background-position: left bottom; background-size: cover;
  background-repeat: no-repeat; overflow: hidden; pointer-events: none;
}
/* Only when there IS art. A team the game ships none for keeps the banner's
   plain gradient, and a tint over nothing would just be a second gradient. */
.tb-band.has-art::after {
  content: ""; position: absolute; inset: 0;
  /* Two washes. The sideways one is the composition — heavy where the crest and
     the name are, thin where the locker is worth seeing. The upward one pays
     for the band now reaching the bottom: the tab strip and the rank line are
     small type, and small type on a photograph is the first thing to go. */
  background:
    linear-gradient(to top,
              color-mix(in srgb, var(--team) 72%, transparent) 0%,
              color-mix(in srgb, var(--team) 30%, transparent) 34%,
              transparent 62%),
    linear-gradient(97deg,
              color-mix(in srgb, var(--team) 88%, transparent) 0%,
              color-mix(in srgb, var(--team) 70%, transparent) 30%,
              color-mix(in srgb, var(--team) 34%, transparent) 64%,
              color-mix(in srgb, var(--team) 18%, transparent) 100%);
}
/* A BRIGHT team takes dark ink, and dark ink needs the ground to STAY bright —
   the locker art is dark at its far end, which is where the coach's name sits.
   So the tint barely thins across the band for these teams. Same picture, and
   the type is still on the team's colour rather than on a photograph. */
.is-ink-dark .tb-band.has-art::after {
  background:
    linear-gradient(to top,
              color-mix(in srgb, var(--team) 82%, transparent) 0%,
              color-mix(in srgb, var(--team) 40%, transparent) 34%,
              transparent 62%),
    linear-gradient(97deg,
              color-mix(in srgb, var(--team) 92%, transparent) 0%,
              color-mix(in srgb, var(--team) 82%, transparent) 34%,
              color-mix(in srgb, var(--team) 62%, transparent) 100%);
}

/* THE IDENTITY ROW IS A GRID, and the reason is one sentence: the coach must
   never wrap and the stat strip must. Flexbox cannot say that — it wraps
   whichever item stops fitting, and twice it was the coach, dropped under an
   empty row of locker. Named areas say it exactly: the strip is a second row
   until the banner is wide enough to seat it beside the school, and the other
   three are one row at every width. What gives when the width runs out is the
   titles column (`minmax(0, 1fr)`), and the school name ellipsises.

   --tb-row-h is the FLOOR of the first row. The row must not resize when the
   DATA changes — a rank appears, a record disappears, a season has finish pills
   and the next has none — and it does not, because every line in the titles
   reserves its space with min-height. Only the viewport moves it.
   `align-content: start` so the floor never stretches a second row. */
.tb-head {
  position: relative; z-index: 1;
  display: grid; align-items: center; align-content: start;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "crest titles coach" "stats stats stats";
  gap: 0 20px; min-height: var(--tb-row-h);
}
.tb-crest-plinth { grid-area: crest; }
.tb-titles { grid-area: titles; }
.tb-coach { grid-area: coach; }
.tb-stats { grid-area: stats; }

/* The one place on the site that renders the game's ON-WHITE crest, which is
   drawn for a light ground and needs one under it — hence a near-white plinth
   rather than the dark tile that carried the dark-ground art. Slightly warm
   and not pure #fff, so it reads as a tile rather than a hole in the banner. */
.tb-crest-plinth {
  flex: none; display: grid; place-items: center; padding: 9px;
  background: #f4f5f7; border-radius: 16px;
  box-shadow: inset 0 0 0 1px rgba(10, 12, 17, .16);
}
.tb-crest { width: 78px; height: 78px; object-fit: contain; display: block; }

/* No percentage cap any more. `52%` was measured against the whole identity
   row when this was a flex item; in the grid it resolves against the titles
   COLUMN, which at 1400px cut the block to a third of its track and left
   "#11 Sa..." beside 300px of empty locker. The column already takes exactly
   the slack, so a cap only wastes it; 620px is the one width that matters —
   past it a long name gains nothing. */
.tb-titles { min-width: 0; max-width: 620px; }
/* The conference mark leads the record line. Sized to the record beside it
   rather than to the crest — it says which league, it is not a second
   identity — and given a shadow because the 3D marks are drawn for the game's
   own dark panels and can land on a bright patch of the locker art. */
.tb-conf {
  flex: none; height: 30px; width: auto; object-fit: contain;
  filter: drop-shadow(0 1px 3px rgba(6, 8, 12, .45));
}
/* The words, for a conference the game ships no art for. Set as a label, not
   as a heading: it is standing in for a picture. */
.tb-conf-name {
  flex: none; font-family: var(--cond); font-weight: 600; font-size: .84rem;
  letter-spacing: .12em; text-transform: uppercase; opacity: .78;
}
/* Record leads the pills on one line. min-height, so a season with neither
   still reserves what a season with both occupies. */
.tb-subline {
  display: flex; align-items: center; gap: 12px; min-height: 24px; margin-top: 3px;
  flex-wrap: nowrap;
}
.tb-record {
  white-space: nowrap;
  font-family: var(--cond); font-weight: 600; font-size: 1.05rem;
  letter-spacing: .03em; font-variant-numeric: tabular-nums; opacity: .88;
}

/* --- prestige, five stars, right of the record ---------------------------
   Two identical star rows stacked. The bottom one is the outline the program
   could reach; the top one is clipped to `--fill` (the half-star count x 10%)
   and paints only that much, so a half star is the same art cut down the
   middle rather than a second drawing that has to be kept in step.

   The clip is `width` + `overflow: hidden` on an absolutely positioned layer,
   not a percentage clip-path: a percentage inside clip-path resolves against a
   different box in SVG than it does in HTML, and this row is HTML holding SVG.
   currentColor on both layers means the stars flip with `.is-ink-*` exactly
   like the record beside them. */
.tb-prestige {
  position: relative; flex: none; display: inline-flex;
  line-height: 0; opacity: .95;
}
.tb-stars { display: flex; gap: 3px; }
.tb-stars .star-ico { width: 17px; height: 17px; flex: none; opacity: .26; }
/* The filled layer sits on top and is cut at --fill. `flex: none` on the stars
   is what keeps them at full size inside a narrower box — without it they would
   squash to fit the fill percentage instead of being clipped by it. */
.tb-stars--on {
  position: absolute; top: 0; left: 0; height: 100%;
  width: var(--fill, 0%); overflow: hidden;
}
.tb-stars--on .star-ico { opacity: 1; }

/* THE HEAD COACH — a section at the end of the identity row, not a pill
   floating in it. It has a divider, a full-height column of its own and a
   portrait that STANDS ON THE STAT RULE: the face box is bottom-anchored and
   reaches --tb-stats-gap past the row, so the rule itself cuts him. That is
   the whole trick, and it is why the art needs no bottom edge — a portrait is
   a head over a chest cut off at 512px, and any crop we drew for it would read
   as a crop. The rule reads as a floor.

   The sides FEATHER rather than cut. A shoulder ending in a straight vertical
   line is a cut-out; a shoulder fading into the band is a man standing in it.

   The empty state — no coach, which is every console league, for ever — keeps
   the same column and the same label and says so in the name slot. */
.tb-coach {
  /* ONE knob. The face is this tall, the window is .95 of it and the portrait
     inside is 1.085 of it — the three were measured together against the
     tallest cap in the set, so a narrower screen changes this line and nothing
     else. On a wide screen it is the identity row plus the gap under it, which
     is what puts his feet on the rule. */
  --tb-coach-h: calc(var(--tb-row-h) + var(--tb-stats-gap));
  position: relative; z-index: 1; flex: none; margin-left: auto;
  /* His feet, not his middle: bottom-aligned to the line so the bleed lands on
     the rule however tall a wrapped school name makes that line. */
  align-self: flex-end;
  display: flex; align-items: flex-end; gap: 14px; padding-left: 22px;
}
.tb-coach::before {
  content: ""; position: absolute; left: 0; top: 16px; bottom: 12px; width: 1px;
  background: color-mix(in srgb, currentColor 22%, transparent);
}
/* Right-aligned, so the type ends where the portrait begins and the two read
   as one block rather than as a caption that happens to sit nearby. */
.tb-coach-meta {
  display: flex; flex-direction: column; align-items: flex-end; text-align: right;
  gap: 2px; padding-bottom: 16px; min-width: 0;
}
.tb-coach-label { font-size: .6rem; letter-spacing: .16em; text-transform: uppercase; opacity: .75; }
.tb-coach-name {
  font-family: var(--cond); font-weight: 600; font-size: 1.12rem;
  letter-spacing: .01em; white-space: nowrap;
}
/* Tenure and record, both scoped to this school. Tabular figures because the
   record sits under a name and a wandering dash reads as a typo. */
.tb-coach-facts {
  display: flex; align-items: baseline; margin-top: 1px;
  font-size: .74rem; opacity: .82; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.tb-coach-facts span + span::before { content: "\00b7"; margin: 0 6px; opacity: .7; }
/* The window is the box; the portrait inside it is wider than the box and
   bottom-anchored, so what shows is a head with ~8px of clearance above the
   tallest cap in the set and shoulders running off both sides. -51.2% is the
   head's own centre on a 512px canvas (262/512), not the picture's. */
.tb-coach-face {
  position: relative; flex: none;
  width: calc(var(--tb-coach-h) * .95); height: var(--tb-coach-h);
  margin-bottom: calc(var(--tb-stats-gap) * -1);
  display: grid; place-items: center; overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 15%, #000 85%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 15%, #000 85%, transparent 100%);
}
.tb-coach-img {
  position: absolute; bottom: 0; left: 50%; transform: translateX(-51.2%);
  width: calc(var(--tb-coach-h) * 1.085); height: auto; max-width: none;
  display: block;
}
/* No coach: the placeholder is centred in the column, not standing on the
   rule. A silhouette with its feet on the floor would look like data. */
.tb-coach-none { opacity: .5; margin-bottom: 18px; }
.tb-coach:not(.is-known) .tb-coach-meta { opacity: .62; }

/* --- the name and the year ARE the pickers ------------------------------- */
/* A real <select> at opacity 0 over the styled text: native dropdown, native
   keyboard handling, native mobile picker, and type that owes nothing to form
   chrome. The caret is the only affordance, and it firms up on hover/focus.

   `.tb-pick` / `.tb-pick-select` / `.tb-caret` are the site's "the text is the
   control" primitive and the dashboard's season headline borrows all three —
   they live here because the team banner was the first to need them, not
   because they belong to it. */
.tb-namebar { display: flex; align-items: baseline; gap: 16px; min-height: 44px; margin-top: 0; }
.tb-pick { position: relative; display: inline-flex; align-items: baseline; gap: 7px; min-width: 0; }
.tb-pick-select {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer; font: inherit;
}
.tb-caret { align-self: center; flex: none; opacity: .45; transition: opacity .14s, transform .14s; }
.tb-pick:hover .tb-caret { opacity: .95; transform: translateY(1px); }
.tb-pick:focus-within .tb-caret { opacity: 1; }
.tb-pick:focus-within { outline: 2px solid currentColor; outline-offset: 6px; border-radius: 4px; }
.tb-school {
  font-size: 2.5rem; line-height: 1.15;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A line of its own now, not a word inside the name. Sized off the record
   under it rather than off the school above it: it is the quiet half of the
   team's name, and at .58em of a 2.5rem headline it was competing. */
.tb-mascot {
  font-family: var(--cond); font-weight: 500; font-size: 1.16rem;
  line-height: 1.15; letter-spacing: .02em; opacity: .72;
  margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The AP rank belongs to the NAME, not to the conference line — sitting in the
   eyebrow ahead of "SEC" it read as a conference standing. Gold is this site's
   settled mark for a poll rank, but --gold-lite only carries on a dark banner;
   a bright team colour flips it to a deep amber that reads as the same idea. */
.tb-rank-mark { font-size: .82em; color: var(--gold-lite); }
.is-ink-dark .tb-rank-mark { color: #4a3408; }
/* The year is the name's sibling control, so it takes the same face a step
   down rather than a form field parked next to a headline. */
.tb-pick--season { padding-left: 16px; border-left: 1px solid currentColor; border-left-color: color-mix(in srgb, currentColor 26%, transparent); }
.tb-year {
  font-family: var(--cond); font-weight: 700; font-size: 1.65rem; line-height: 1.15;
  letter-spacing: .02em; opacity: .82; font-variant-numeric: tabular-nums;
}
.tb-pick--season:hover .tb-year, .tb-pick--season:focus-within .tb-year { opacity: 1; }

/* --- the season stat line, inside the pill -------------------------------- */
/* Five equal columns, no dividers: on a saturated ground the rules were doing
   nothing the spacing wasn't already doing. Everything is CENTRED in its
   column, dial and plain tile alike — left-aligned text under a centred ring
   read as two rows that had drifted apart.

   The three ratings are RINGS, the way the game draws them: the number with a
   three-letter label under it, inside the circle. That is what killed the
   horizontal scrollbar on a phone — "OFF OVERALL" set a minimum column width
   no 390px screen had five of — and it is also what tells a rating apart from
   PPG/DPPG at a glance.

   `--tb-dial` is the height of BOTH tile bodies, so the rank line sits on one
   baseline across all five columns however tall each body's content is. */
/* THE STRIP, in the narrow layout: a line of its own under the identity row,
   ruled off from it. The rule sits exactly --tb-stats-gap below line one — the
   head's row-gap is zero and this margin is the whole distance — because the
   coach's portrait bleeds by that same number to stand on it. Change one and
   the other has to move. */
.tb-stats {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 4px 10px; margin-top: var(--tb-stats-gap);
  border-top: 1px solid color-mix(in srgb, currentColor 16%, transparent);
  padding-top: 13px;
}
/* THREE RATINGS, THEN TWO AVERAGES — the gap is the only thing that says so.
   Five evenly spaced tiles read as one list of five, and they are not: the
   rings are the game's judgement of the roster, PPG and DPPG are what the
   season actually did. Tight inside each group, a real break between them. */
.tb-stat:nth-child(4) { margin-left: 26px; }

/* WIDE: the five numbers move up beside the school, and the rule they were
   sitting under moves to the tab strip — one line in the banner either way.

   A CONTAINER QUERY, not a media query, and that is not a preference: the
   sidebar is 244px wide and COLLAPSES TO 68 on a click, so the same viewport
   gives this banner two different widths and only the banner knows which one
   it is. 1150px is measured, not guessed: 96 crest + 460 for the widest
   identity line + ~300 of tiles + 278 of coach, the gaps and the padding.

   The strip's column is `auto` — its own width, never less — and the titles
   column is the one that takes the slack and gives it back. So the tiles sit
   with the coach as one cluster of numbers at the right, the school owns the
   left, and the space between them is where the locker shows through. */
@container banner (min-width: 1150px) {
  .tb-head {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas: "crest titles stats coach";
  }
  .tb-stats {
    margin: 0 26px 0 0; padding-top: 0; border-top: 0;
    gap: 2px 10px; align-self: center;
  }
  .tb-tabs {
    margin-top: var(--tb-stats-gap);
    border-top: 1px solid color-mix(in srgb, currentColor 16%, transparent);
    padding-top: 8px;
  }
}
.tb-stat {
  min-width: 0; text-align: center;
  display: flex; flex-direction: column; align-items: center;
}
.tb-stat-body, .tb-dial {
  height: var(--tb-dial);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.tb-stat-label {
  display: block; font-size: .54rem; letter-spacing: .14em; text-transform: uppercase;
  font-weight: 600; opacity: .62; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100%;
}
.tb-stat-val {
  display: block; font-family: var(--cond); font-weight: 700; font-size: 1.28rem;
  line-height: 1.1; margin-top: 2px; font-variant-numeric: tabular-nums;
}
/* The ring, and it FILLS to the rating. Sized off --tb-dial so one number
   moves the circle and the plain tile's reserved body together.

   Two circles on one 100-unit viewBox: a track all the way round and an arc
   cut to the rating with `stroke-dashoffset`. `--circ` is that circle's
   circumference (2 x pi x 45) and lives here rather than in the markup, so the
   only number the template carries is the rating itself. The whole svg is
   rotated -90deg to start the arc at twelve o'clock, and the cap is round
   because the game's own ring has one.

   currentColor at an opacity step, like the rest of the banner — a fixed
   accent would fight 138 team colours, and gold already means "elite rank" one
   line below. */
.tb-dial {
  position: relative; width: var(--tb-dial); border-radius: 50%;
  --circ: 282.743;
  gap: 0;
}
.tb-ring {
  position: absolute; inset: 0; width: 100%; height: 100%;
  transform: rotate(-90deg); pointer-events: none;
}
.tb-ring circle { fill: none; stroke-width: 9; }
.tb-ring-track { stroke: color-mix(in srgb, currentColor 20%, transparent); }
.tb-ring-arc {
  stroke: currentColor; stroke-linecap: round;
  stroke-dasharray: var(--circ);
  stroke-dashoffset: calc(var(--circ) * (1 - var(--fill) / 100));
}
.tb-dial-val {
  font-family: var(--cond); font-weight: 700; font-size: 1.08rem; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.tb-dial-label {
  font-family: var(--cond); font-weight: 700; font-size: .56rem; line-height: 1;
  letter-spacing: .08em; text-transform: uppercase; opacity: .72; margin-top: 1px;
}
.tb-stat.is-soon .tb-stat-val, .tb-stat.is-soon .tb-dial-val { opacity: .38; }
/* No rating yet: the track alone, fainter. There is no arc in the markup at
   all, so an empty ring can never be read as a rating of zero. */
.tb-stat.is-soon .tb-ring-track { stroke: color-mix(in srgb, currentColor 11%, transparent); }
/* min-height so a tile with no rank reserves the same box as one with a rank —
   otherwise the strip's height would depend on which season you are looking at. */
.tb-stat-rank {
  display: block; min-height: 13px; margin-top: 3px;
  font-size: .62rem; font-weight: 600; font-variant-numeric: tabular-nums; opacity: .55;
}
/* Banner-scoped tiers. rank_cls()'s classes resolve to --muted / --faint with
   !important, which are light-UI greys that disappear on a team colour. */
.tb-rk--elite { color: var(--gold-lite); opacity: 1; }
.is-ink-dark .tb-rk--elite { color: #4a3408; }
.tb-rk--good { color: #a9e8b0; opacity: 1; }
.is-ink-dark .tb-rk--good { color: #17431f; }

/* Tab strip, seated on the banner's bottom edge. z-index keeps it over the
   crest, which is allowed to run behind it. */
.tb-tabs { position: relative; z-index: 1; display: flex; gap: 4px; flex-wrap: wrap; margin-top: 8px; }
.tb-tab {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--cond); font-weight: 600; font-size: .95rem;
  letter-spacing: .08em; text-transform: uppercase;
  padding: 10px 16px; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  color: currentColor; opacity: .62; border-bottom: 3px solid transparent;
  transition: opacity .14s, background .14s;
}
.tb-tab:hover { opacity: .92; background: rgba(12, 15, 21, .18); }
.tb-tab.is-current { opacity: 1; background: var(--surface); color: var(--ink); border-bottom-color: var(--gold); }
.tb-tab--soon { opacity: .42; }
.tb-tab--soon:hover { opacity: .6; }

/* ---- Player banner ------------------------------------------------------
   The same slab as the Team Hub's, turned to face one person, and it inherits
   every rule that block was bug-fixed into. Prefix is `pb-` (`tb-` = team
   banner, `gh-` = game header) so the two can never share a selector.

   THE CARD IS THE SUBJECT — a face and the school's crest, and nothing stamped
   on top of either. Everything else reads down the right of it in three lines:
   who he is, what he is rated and plays and wears, then his measurements.

   NOTHING IS PARKED IN THE TOP RIGHT ANY MORE. That corner has now held a 176px
   ghost crest and an OVR badge, in that order, and each was moved because the
   other was under it. The rating is a LINE OF TYPE under the name instead,
   which buys the longest names in the league back the 100px the badge was
   taking and leaves the right-hand channel to the watermark alone —
   `--pb-mark-w` is the third grid column that keeps it that way, empty on
   purpose.

   EVERY COLOUR IN HERE IS `currentColor` AT AN OPACITY, never a fixed grey.
   `.is-ink-dark` flips the whole banner to near-black type on a bright team, and
   a hardcoded --muted goes invisible the moment it does. The one exception is
   the jersey chip, which cannot be currentColor because its whole job is to
   wear the school's colour — `player.chip_colors()` moves that colour clear of
   the ground and picks the ink to put on it. */
.player-banner {
  --pb-pad-t: 20px;
  --pb-card: 168px;      /* the card's WIDTH — every number on it derives from this */
  /* THE CARD IS LANDSCAPE, AND THAT IS A CROP. Measured over 1,200 portraits,
     the art starts a median 116px down a 512px canvas and the shoulders only at
     360 — so a square card spent its top fifth on empty air above every head,
     which is what pushed the crest up into nothing. .85 takes the top 15% off
     with `object-position: 50% 100%`, and the sample says that costs a wisp of
     hair on about 2% of the set and nothing at all on the rest. Tighter (.82)
     starts flattening dreadlocks; looser (.88) leaves the air it exists to cut. */
  --pb-card-h: calc(var(--pb-card) * .85);
  --pb-mark-w: 156px;    /* the channel the watermark gets to itself */
  position: relative; overflow: hidden; border-radius: var(--radius);
  padding: var(--pb-pad-t) 24px 0; margin-bottom: 22px;
  background: linear-gradient(105deg, var(--team) 0%,
              color-mix(in srgb, var(--team) 58%, var(--bg)) 100%);
  border: 1px solid var(--line);
}
.player-banner.is-ink-light { color: var(--ink); }
.player-banner.is-ink-dark  { color: #10131a; }

/* The secondary mark as ground, in the channel `--pb-mark-w` reserves for it.
   The wrapper IS the box: the mark `contain`s inside it and is never cut.

   DELIBERATELY NOT THE TEAM BANNER'S TREATMENT, which oversizes its crest and
   clips it against the stat rule so the cut lands on a line. There is no line
   to land on here — the honours row under the head renders only for a player
   who has honours — so a mark sized to overhang would end in a hard horizontal
   edge in mid-banner on the 90% of the league that has none.

   `contain` and a fixed box, not `height` with `width: auto`: a fair number of
   the secondary marks are wordmarks two or three times as wide as they are
   tall, and a height-driven one sprawls left out of its channel and back under
   the badge, which is the whole thing this channel exists to prevent. */
.pb-mark-clip {
  position: absolute; z-index: 0; right: 6px; top: 0;
  width: var(--pb-mark-w);
  height: calc(var(--pb-pad-t) + var(--pb-card-h));
  overflow: hidden; pointer-events: none;
}
.pb-mark {
  width: 100%; height: 100%; object-fit: contain;
  opacity: .16; user-select: none;
}

/* FIXED height, deliberately not min-height: this row is the one thing that
   persists across a tab swap, so it must not resize when a jersey, an archetype
   or a longer hometown comes and goes between players. NOTHING inside it
   clips — each line RESERVES space with min-height rather than capping with
   height+overflow, which is what shaved the bottom off the team banner's pills.

   A GRID AND NOT A FLEX ROW, because the mobile arrangement is the same boxes
   in a different pattern: there the fact line drops out from beside the card and
   runs the full width under it. Named areas re-map in one declaration, and
   `.pb-body` goes `display: contents` so its two children become grid items
   directly — no DOM change at the breakpoint. */
.pb-head {
  position: relative; z-index: 1;
  display: grid; column-gap: 20px; row-gap: 10px;
  grid-template-columns: auto minmax(0, 1fr) var(--pb-mark-w);
  grid-template-areas:
    "card body mark"
    "card meta mark";
  align-content: center; min-height: var(--pb-card-h);
}
.pb-body {
  grid-area: body; min-width: 0;
  display: flex; flex-direction: column; gap: 6px;
}

/* --- the card ------------------------------------------------------------ */
/* NOT A GRID, AND THAT IS A BUG FIX. It was one, with both portrait layers as
   items at `grid-area: 1/1` and `height: 100%` — and a percentage height inside
   an AUTO-SIZED grid row is circular, so the spec makes it resolve to `auto`.
   The layers therefore rendered at their intrinsic 1:1 (168x168 inside a 168x143
   card), the row grew to fit them, the row sat at the container's top, and
   `overflow: hidden` cut 25px off the BOTTOM — the collar and the shoulders.
   `object-fit` and `object-position` were never consulted at all, because the
   box already matched the source's aspect ratio.

   It was invisible while the card was square, since 168x168 in a 168x168 box is
   the right answer by accident. Cropping the card to landscape is what exposed
   it, and it exposed it as the exact opposite of the intended crop.

   Absolute positioning has no such cycle: the box is the card, full stop. */
.pb-card {
  grid-area: card; align-self: center; position: relative;
  width: var(--pb-card); height: var(--pb-card-h);
  border-radius: 16px; overflow: hidden;
  background: rgba(8, 10, 14, .5);
  /* Inset hairline rather than a border: the art is `object-fit: cover` to the
     box's edge, and a real border would sit outside the overflow clip and read
     as a frame around the card instead of an edge on it. */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .11),
              0 8px 20px -10px rgba(0, 0, 0, .75);
}
/* Both portrait layers occupy the SAME box, which is what stacks them. They
   must share every sizing rule: the jersey is painted for the portrait's own
   canvas, so any difference in fit or position slides the collar off the neck.

   The z-index is NOT cosmetic. The crest is absolutely positioned too, and it
   is supposed to sit BEHIND the player — without an explicit order here the
   player ends up under the thing meant to be behind him. */
.pb-card-img {
  position: absolute; top: 0; left: 0; z-index: 2;
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* BOTTOM-ANCHORED, which is the crop. A square source covering a landscape
     box overflows vertically, and `100%` puts the whole of that overflow at the
     TOP — the empty air, not the collar. Both layers share this line and must:
     the jersey is painted for the portrait's own 512x512 canvas, so a crop that
     applied to one and not the other would slide the collar off the neck. */
  object-position: 50% 100%;
}
.pb-card-jersey { pointer-events: none; }

/* The crest, big, behind the shoulder and bleeding off the right edge — the
   game's own card treatment. The head occupies x 32-70% of a 512px canvas, so a
   mark from 58% to just past the edge keeps a real band of itself behind him
   and puts the rest in the empty right corner.

   EVERY NUMBER IS A FRACTION OF THE CARD'S WIDTH, percentages included nowhere.
   `width: 52%` and `height: 52%` resolve against DIFFERENT edges now the card
   is landscape, so the pair would letterbox the crest inside its own box and
   quietly shrink it; `calc()` off one axis keeps it square.

   IT LINES UP WITH THE HEAD, which is what the crop bought. The art now runs
   9% to 65% of the card's height and the crest runs 4.7% to 66% of it, so they
   sit at the same level instead of the crest floating in the air the crop cut.
   Horizontally 56% to 108%: 14 points of it behind the shoulder and the rest
   bleeding into the corner the head never reaches. */
.pb-card-mark {
  position: absolute; z-index: 1;
  top: calc(var(--pb-card) * .04); right: calc(var(--pb-card) * -.08);
  width: calc(var(--pb-card) * .52); height: calc(var(--pb-card) * .52);
  object-fit: contain; opacity: .9;
}
/* No portrait: the crest IS the card, centred and quiet. There is nothing for
   it to sit behind, so it stops being ground and becomes the subject. */
.pb-card-mark--solo {
  top: 50%; right: 50%; transform: translate(50%, -50%);
  width: calc(var(--pb-card) * .56); height: calc(var(--pb-card) * .56);
  opacity: .55;
}

/* --- name, rating, and the fact line -------------------------------------- */
/* The name has the line to itself now. The rating badge used to sit at the far
   end of this row and take 100px off the longest names in the league; it is a
   line of type under the name instead, which is a trade of vertical space the
   banner had going spare for horizontal space it did not. */
.pb-namebar {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 2px 14px;
  min-width: 0; min-height: 52px;
}
.pb-name {
  font-size: 3.1rem; line-height: 1.05; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The slot the jersey number used to hold. The number is on a jersey now, and a
   class is the thing that actually qualifies a name on a career-scoped page. */
.pb-class {
  flex: none; font-family: var(--cond); font-weight: 600; font-size: 1.4rem;
  letter-spacing: .03em; opacity: .55; white-space: nowrap;
}

/* --- the rating line ------------------------------------------------------
   "90 OVR | WR (14)". Rated, played, worn — the three things a reader opened
   this page for, set deliberately larger than the fact line beneath so the eye
   lands here first on the way down from the name.

   ONE DIVIDER AND NOT TWO. The rule separates a judgement that changes every
   season from the two things he answers to; a second one between the position
   and the number would split a pair that is read together. */
.pb-rating {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 15px;
  min-width: 0; min-height: 38px;
}
.pb-ovr { display: inline-flex; align-items: baseline; gap: 7px; }
.pb-ovr-val {
  font-family: var(--cond); font-weight: 700; font-size: 2.15rem; line-height: 1;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* The dev trait is a word where the OVR is a number, so it cannot carry the
   same size — "Elite" at 2.15rem outweighs the rating it sits beside. The
   worded badge only renders as the fallback now: the usual carrier is the
   wordless .pb-dev-ico below. */
.pb-ovr--dev .pb-ovr-val { font-size: 1.4rem; }
/* The game's own badge art (static/dev-traits/), standing alone — the icon IS
   the vocabulary and a spelled-out "Impact Dev" beside it read as a caption.
   Sized against .pb-jersey-art, a shade under it: a square sticker at the
   jersey's height outweighs it. */
.pb-dev-ico { height: 2.1rem; width: auto; display: block; }
.pb-ovr-lab {
  font-size: .62rem; letter-spacing: .16em; text-transform: uppercase;
  font-weight: 600; opacity: .6;
}
/* Banner-scoped tiers, three of them. `ovr_cls()` is unusable in here for the
   reason `rank_cls()` was: .ovr-mid resolves to --ink-dim and .ovr-low/.ovr-none
   to --muted, fixed light-UI greys that vanish on a saturated team colour. */
.pb-ovr--elite .pb-ovr-val { color: var(--gold-lite); }
.is-ink-dark .pb-ovr--elite .pb-ovr-val { color: #4a3408; }
.pb-ovr--good .pb-ovr-val { color: #a9e8b0; }
.is-ink-dark .pb-ovr--good .pb-ovr-val { color: #17431f; }
.pb-ovr--none .pb-ovr-val { opacity: .45; }

.pb-rating-rule {
  width: 1px; align-self: stretch; margin: 2px 0;
  background: color-mix(in srgb, currentColor 26%, transparent);
}
.pb-pos {
  font-family: var(--cond); font-weight: 700; font-size: 1.35rem;
  letter-spacing: .09em; text-transform: uppercase; opacity: .92;
}

/* --- the jersey ------------------------------------------------------------
   The number, worn rather than written. The silhouette is traced off the game's
   own `torsoedit_jersey` icon (84% IoU against its alpha; the rest is the "00"
   this replaces) and drawn as a path because that icon has its number baked in.

   THE FILL CANNOT BE THE TEAM COLOUR, which is the whole reason `chip_colors()`
   exists: the banner's ground IS the team colour, so the raw value paints a
   jersey you cannot see. The viewmodel moves the same hue a guaranteed step
   clear of the ground — up in HSL lightness, or down for a team already near
   white — and picks the ink that reads on whatever came back.

   The hairline is `currentColor` so it flips with `.is-ink-*`, and it is what
   holds the chip's edge on the one team whose lifted fill lands close to its
   own ground. `non-scaling-stroke` keeps it a hairline at every breakpoint
   instead of a rope on desktop and nothing on a phone. */
.pb-jersey { display: inline-flex; align-items: center; }
.pb-jersey-art {
  height: 2.35rem; width: auto; display: block;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, .45));
}
/* ON THE PATH, NOT ON THE `<svg>`. `vector-effect` is one of the few SVG
   properties that does NOT inherit, so declared on the parent it silently does
   nothing and the hairline goes back to scaling with the viewBox — a rope at
   2.35rem and a smudge at 1.9. */
/* THE OUTLINE IS DOING REAL WORK NOW. While the fill was a derived colour it
   was guaranteed a step clear of the ground; the real home shirt has no such
   guarantee and usually IS a shade of it — Florida's #021452 on a #0021a5
   banner is 1.4:1. The hairline and the shadow are what hold the shape, and the
   number holds the rest, which is why it is contrast-picked off the jersey. */
.pb-jersey-art path {
  fill: var(--chip, currentColor);
  stroke: currentColor; stroke-opacity: .55; stroke-width: 1.2px;
  vector-effect: non-scaling-stroke;
}
/* Sized in USER UNITS, so it scales with the shape and needs no breakpoint of
   its own. 78 of the shirt's 160 — the game's own icon wears a much smaller
   "00", but that icon is a settings tab and this is the number itself. Two
   digits at 78 come to about 70 of the 107 units across the chest, so there is
   still a margin either side; 86 loses it. */
.pb-jersey-num {
  font-family: var(--cond); font-weight: 700; font-size: 78px;
  fill: var(--chip-ink, #fff); stroke: none;
  font-variant-numeric: tabular-nums; letter-spacing: -1px;
}

/* --- the team pills --------------------------------------------------------
   One per unbroken run at a school, on the tail of the rating line. They paint
   their OWN dark ground, the honours pills' rule (`.pill--fin` states why): the
   banner is the team's colour and any ink tied to it goes invisible on some
   school — and the crests are the game's 3D art, drawn for a dark ground, so
   the pill's fill is also what keeps a former team's mark legible on the
   current team's colour. Below 900px `flex-basis: 100%` drops the row onto a
   line of its own under the rating, still beside the card — the same
   no-DOM-change remapping the rest of the banner does. */
.pb-stints {
  display: inline-flex; align-items: center; flex-wrap: wrap;
  gap: 5px; min-width: 0;
}
.pb-stint {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--cond); font-weight: 700; font-size: .8rem;
  letter-spacing: .05em; font-variant-numeric: tabular-nums;
  padding: 3px 9px 3px 5px; border-radius: 20px; white-space: nowrap;
  background: rgba(12, 15, 21, .5); border: 1px solid rgba(255, 255, 255, .22);
  color: #d6dae2;
}
.pb-stint:hover { border-color: rgba(255, 255, 255, .5); color: #fff; }
/* Taller than the type, the honours pills' trick (`.pill-ico--art`): the crest
   overhangs the pill's padding vertically so it reads at a legible size without
   making the pill a button. */
.pb-stint-logo {
  width: 1.45em; height: 1.45em; object-fit: contain; flex: none;
  margin: -.3em 0;
}

.pb-metabar {
  grid-area: meta; display: flex; align-items: baseline;
  flex-wrap: wrap; column-gap: 11px; row-gap: 4px;
  min-width: 0; min-height: 22px;
}
.pb-fact { display: inline-flex; align-items: baseline; gap: 6px; font-size: .9rem; }
/* TRAILING separator, not a leading one. The line is allowed to wrap — the
   banner's rule is that things grow visibly rather than being shaved — and a
   `.pb-fact + .pb-fact::before` dot would start the wrapped line with a
   floating glyph. A trailing dot at the end of a line reads as a continuation. */
/* 5px, not 11: the dot is a flex child of `.pb-fact` and already inherits its
   6px gap, so 5 more puts it 11 from the value — matching the 11px column gap
   on the other side and centring it between the two facts. */
.pb-fact:not(:last-child)::after { content: "·"; margin-left: 5px; opacity: .45; }
.pb-fact-lead {
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  font-weight: 600; opacity: .58;
}
.pb-fact-val { opacity: .88; white-space: nowrap; }
/* Two of the four leads are marks rather than words now, and they are sized to
   the line's cap height so they read as part of the sentence rather than as
   buttons dropped into it. `.pb-fact` aligns on the BASELINE, which an
   `align-self` of centre would fight, so each mark is nudged instead of the row
   being re-aligned around it.

   THE PIN TAKES THE SCHOOL'S SECOND COLOUR, `--pin`, and falls back to
   currentColor — which is not a safety net but the real answer for the ten
   schools whose secondary is a shade of their primary (see `pin_color`). The
   NIL diamond is the game's own render and is deliberately NOT tinted: it is a
   piece of art with its own light in it, the way the award trophies are. */
.pb-fact-pin {
  display: inline-flex; align-self: center; color: var(--pin, currentColor);
  opacity: .95;
}
.pb-fact-pin svg { width: 1.05em; height: 1.05em; display: block; }
/* No `--pin` means the mark is plain ink, and plain ink at full strength is
   louder than the small grey word it replaced. */
.player-banner:not([style*="--pin"]) .pb-fact-pin { opacity: .6; }
.pb-fact-nil {
  width: 1.15em; height: 1.15em; align-self: center; object-fit: contain;
  flex: none;
}

/* --- the honours row ------------------------------------------------------
   THE ONE BOX IN THIS BANNER THAT SIZES TO ITS CONTENT (the user's call). It
   holds awards and nothing else now — the hometown is a fact on the line above
   and the per-season team chips are gone — so it grows a row per handful of
   pills and a player with none renders no rule and no row at all.

   That is safe where the identity row above it is not: the tab strip is the
   only thing under it, awards do not change while a reader switches tabs on the
   same player, and reserving a fixed strip for the 90% of the league with no
   honours spent the height on nothing. */
.pb-under {
  position: relative; z-index: 1;
  display: flex; align-items: center; flex-wrap: wrap; gap: 9px 20px;
  margin-top: 14px; padding-top: 14px;
  border-top: 1px solid color-mix(in srgb, currentColor 15%, transparent);
}
.pb-honours { min-width: 0; }

/* Tab strip, seated on the banner's bottom edge. */
.pb-tabs { position: relative; z-index: 1; display: flex; gap: 4px; flex-wrap: wrap; margin-top: 12px; }
.pb-tab {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--cond); font-weight: 600; font-size: .95rem;
  letter-spacing: .08em; text-transform: uppercase;
  padding: 10px 16px; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  color: currentColor; opacity: .62; border-bottom: 3px solid transparent;
  transition: opacity .14s, background .14s;
}
.pb-tab:hover { opacity: .92; background: rgba(12, 15, 21, .18); }
.pb-tab.is-current { opacity: 1; background: var(--surface); color: var(--ink); border-bottom-color: var(--gold); }
.pb-tab--soon { opacity: .42; }
.pb-tab--soon:hover { opacity: .6; }

/* ---- the player page's tabs --------------------------------------------- */
/* Two stat-table variants, and they pin different blocks: the season tables pin
   Yr / Team / GP, the game log pins Yr / Wk / Opp / Result. Both are ≤ 4, which
   is what the page's single `stickyTable(panel, 4)` allows — it writes one more
   --sl-N than a three-pin table reads, and nothing reads it. */
/* Player → Stats: the year column carries the category label now, so it is
   sized to RECEIVING rather than to 2027; the Team column is the crest alone
   (see the template) and hands back the width. Pinned block: 92+56+52 = 200px,
   under the 218px it was. */
.stat-table--pseason { --sf-1: 92px; --sf-2: 56px; --sf-3: 52px; }
.stat-table--pseason .team-cell--crest { text-align: center; }
.stat-table--pseason .team-cell--crest .cell-logo { margin-right: 0; vertical-align: middle; }
.stat-table--pseason .stat-total .stat-fix-1 { text-align: left; }
.career-yr-unit { margin-left: 2px; font-size: .7rem; color: var(--muted); font-weight: 500; }
.stat-table--plog    { --sf-1: 54px; --sf-2: 74px; --sf-3: 124px; --sf-4: 92px;
                       --clip-3: 108px; }
.stat-table--plog .stat-fix-3 .cell-clip { max-width: var(--clip-3); }

/* The career line, in the table's foot. Heavier than a season and separated by
   a rule, so it reads as the sum of what is above it rather than another year. */
.stat-table tfoot .stat-total td {
  border-top: 2px solid var(--line);
  font-weight: 700; background: var(--surface-2);
}

/* --- the trophy case ----------------------------------------------------- */
.aw-section {
  font-family: var(--cond); font-weight: 600; font-size: .72rem;
  letter-spacing: .16em; text-transform: uppercase; color: var(--muted);
  margin: 22px 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--line);
}
.aw-section:first-of-type { margin-top: 4px; }
.aw-case {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}
.aw-card {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 18px 14px 14px; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--line); color: var(--ink);
  transition: border-color .14s, transform .14s;
}
.aw-card:hover { border-color: var(--gold-deep); transform: translateY(-2px); }
.aw-art { display: grid; place-items: center; height: 120px; }
.aw-art img { max-height: 120px; max-width: 100%; object-fit: contain; }
/* An award nobody has captured art for. A letter in the trophy's place reads as
   "no photograph", where a broken image reads as a page that failed. */
.aw-monogram {
  display: grid; place-items: center; width: 76px; height: 76px; border-radius: 50%;
  font-family: var(--cond); font-weight: 700; font-size: 2rem;
  color: var(--gold-deep); background: rgba(214, 169, 78, .08);
  border: 1px solid var(--gold-deep);
}
.aw-name {
  font-family: var(--cond); font-weight: 700; font-size: .95rem;
  text-align: center; line-height: 1.25;
}
.aw-years { display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; }
/* Full years, never '35 — a dynasty runs past 2100 and the game's own two-digit
   screens stop being readable long before it gets there. */
.aw-year {
  font-family: var(--cond); font-weight: 600; font-size: .78rem;
  letter-spacing: .04em; font-variant-numeric: tabular-nums;
  padding: 2px 8px; border-radius: 999px;
  background: rgba(255, 255, 255, .06); color: var(--ink-dim);
}

/* --- honours and near-misses, as rows ------------------------------------ */
.aw-list { display: flex; flex-direction: column; gap: 6px; }
.aw-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--line);
}
.aw-row-mark { display: grid; place-items: center; width: 26px; height: 26px; flex: none; }
.aw-row-mark img { max-width: 26px; max-height: 26px; object-fit: contain; }
.aw-row-name { font-weight: 600; color: var(--ink); }
a.aw-row-name:hover { text-decoration: underline; }
/* An honours row names its selection with the game's own mark rather than the
   words. The mark is white-on-transparent art, so it takes the row's ink; the
   conference crest to its left keeps saying WHOSE honour it was. */
.aw-honor-mark {
  height: 20px; width: auto; max-width: 90px; object-fit: contain;
  vertical-align: -.3em; opacity: .92;
}
/* "ALL / CONFERENCE" is a two-line lockup and needs the extra height to stay
   readable at this size; the star-A monogram does not. */
.aw-honor-mark--wide { height: 26px; vertical-align: -.45em; }
.aw-honor-tier { font-weight: 600; color: var(--ink); margin-left: 6px; }
.aw-place, .aw-pos {
  font-family: var(--cond); font-weight: 600; font-size: .74rem;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
}
.aw-row .aw-year { margin-left: auto; }
.aw-row .aw-year a { color: inherit; }
.aw-row .aw-year a:hover { color: var(--gold-lite); }
/* A records row ends on its number, the way a Record Book row does — same
   condensed tabular gold, so the two surfaces print a record identically. The
   school's colour on the left edge is the only thing separating a transfer's
   two blocks, since the rows come grouped by school. */
.aw-row--rec { border-left: 3px solid color-mix(in srgb, var(--team) 62%, transparent); }
.aw-rec-mark {
  font-family: var(--cond); font-weight: 700; font-size: 1.05rem;
  font-variant-numeric: tabular-nums; color: var(--gold-lite);
  min-width: 3.4em; text-align: right;
}

/* A player's name is a link everywhere it appears now. Inside a table it must
   still READ as the cell text — colour and weight unchanged, the affordance is
   the underline on hover — or eight columns of blue would drown the numbers. */
.rost-name a, .box-player a, .aa-player a, .tc-player a,
.hub-award-player a, .heis-name a { color: inherit; }
.rost-name a:hover, .box-player a:hover, .aa-player a:hover, .tc-player a:hover,
.hub-award-player a:hover, .heis-name a:hover { text-decoration: underline; }

.panel-head .soon { margin-left: auto; }

/* View switcher pills — the Roster tab's views, and the player Attributes
   tab's Summary/History strip, which borrows the classes wholesale. */
.roster-views { display: flex; gap: 6px; margin-bottom: 14px; flex-wrap: wrap; }
.rv-btn {
  display: inline-flex; align-items: center; gap: 7px; font: inherit;
  font-size: .82rem; font-weight: 600; color: var(--ink-dim); cursor: pointer;
  padding: 7px 14px; border-radius: 20px;
  background: var(--surface-2); border: 1px solid var(--line);
}
.rv-btn.is-current { color: var(--ink); border-color: var(--gold-deep); background: var(--raised); }
.rv-btn.is-soon { color: var(--faint); cursor: default; }

/* Player → Attributes → History: the ability card. The tier IS the frame —
   the game's bronze/silver/gold/platinum diamonds — with the ability's white
   glyph layered on top and its name in small condensed type below. A locked
   slot keeps its glyph at low opacity on the empty frame: the loadout is
   fixed, what varies is what the player has unlocked. */
.abil-table td { text-align: center; }
.abil-cell { padding: 8px 6px 7px; }
.abil-badge { position: relative; display: inline-block; width: 44px; height: 44px; }
.abil-frame { position: absolute; inset: 0; width: 100%; height: 100%; }
.abil-glyph { position: absolute; inset: 19%; width: 62%; height: 62%; }
.abil-name {
  display: block; margin-top: 2px; font-family: var(--cond);
  font-size: .72rem; letter-spacing: .02em; white-space: nowrap;
  color: var(--muted);
}
.abil-cell.is-locked .abil-glyph { opacity: .3; }
.abil-cell.is-locked .abil-name { color: var(--faint); }

/* Player → Attributes → Summary: the latest card as sections. Two ability
   cards on top (mental circles, physical diamonds — the History cells reused
   outside a table), then one card per rating bucket. The VALUE carries the
   colour, and the scale is the site's ONE rating scale — `ovr_cls`, exactly
   as the roster summary prints OVR as text: 90+ gold, 82+ green, then fading
   grey. A weak number dims; nothing on this site paints red. */
.sum-abils { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
.sum-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
  gap: 14px; align-items: start;
}
.sum-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-sm); overflow: hidden;
}
.sum-card-head {
  background: var(--surface-2); border-bottom: 1px solid var(--line-soft);
  padding: 7px 12px; font-family: var(--cond); font-weight: 700;
  font-size: .8rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-dim);
}
.sum-rows { padding: 6px 12px 9px; }
.sum-row {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; padding: 4px 0; font-size: .88rem;
}
.sum-label { color: var(--ink-dim); }
.sum-val { font-weight: 700; font-variant-numeric: tabular-nums; }
.sum-val.ovr-elite { color: var(--gold-lite); }
.sum-val.ovr-good  { color: #7bc47f; }
.sum-val.ovr-mid   { color: var(--ink-dim); }
.sum-val.ovr-low   { color: var(--muted); }
.sum-val.ovr-none  { color: var(--muted); font-weight: 400; }
.sum-slots { display: flex; gap: 8px; padding: 10px 12px 10px; }
.sum-slots .abil-cell { padding: 0; text-align: center; }
.abil-dash {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: var(--faint); font-size: 1.05rem;
}

/* ---- player tables (Team Hub roster, Player Ratings) --------------------- */
/* `.roster-table` is the shared dense player-table skin, not roster-only — the
   Player Ratings table is the same table with three columns added, and it is
   supposed to look identical. Change here and both move together. */
.roster-table { font-size: .85rem; }
.roster-table td { padding: 7px 8px; }
.rost-jersey { color: var(--faint); width: 34px; }
.rost-name { font-weight: 600; color: var(--ink); }
.rost-pos { font-family: var(--cond); letter-spacing: .04em; color: var(--ink-dim); }
.rost-arch, .rost-town { color: var(--muted); }

/* DEV: the badge art alone, centred, with the trait name hidden beside it for
   the funnel menu. Sized to the row rather than to the art — the source PNGs
   are not all the same canvas, and a column of icons that each pick their own
   height makes 85 rows of uneven leading. */
/* The badge is drawn BIGGER than the line it sits on and pulled back into it,
   so it reads at the size the art was made for without setting the height of
   every row in the table. The 22px OVR pill next to it still governs that, and
   the two stay level. Sized to the cell, not to the art: the source PNGs carry
   their own transparent margin, so a box that only just contains one leaves the
   badge looking shrunken inside a tall row. */
.rost-dev { width: 40px; }
.rost-dev-ico {
  display: block; margin: -4px auto; width: 28px; height: 28px; object-fit: contain;
}

/* Player Ratings, PC leagues: the fifty-three card ratings at the end of the
   row. A seam on the first column of each bucket (Core, Passing, Rushing…) is
   what lets that many three-letter headers read as seven groups. */
.roster-table th.rt-grp, .roster-table td.rt-grp {
  border-left: 1px solid var(--line, rgba(255,255,255,.14));
}

/* RECRUITED: "HS - <stars>", or a crest and "TR - <stars>". `white-space: nowrap`
   because the stars are emoji and a cell that wraps one of them onto a second
   line takes the whole row's height with it. The crest reuses `.cell-logo`, so
   it lines up with the ones in the schedule and the stat tables. */
.rost-recruited { color: var(--ink-dim); white-space: nowrap; }
.rost-recruited .cell-logo { margin-right: 6px; }
.ovr-badge {
  display: inline-block; min-width: 26px; padding: 2px 6px; border-radius: 5px;
  font-family: var(--cond); font-weight: 700; font-size: .82rem; text-align: center;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--ink-dim);
}
.ovr-badge.ovr-elite { background: var(--gold); color: #14100a; border-color: var(--gold-deep); }
.ovr-badge.ovr-good  { color: #7bc47f; border-color: #375a3a; }
.ovr-badge.ovr-mid   { color: var(--ink-dim); }
.ovr-badge.ovr-low, .ovr-badge.ovr-none { color: var(--muted); }

/* ---- Player Stats table -------------------------------------------------- */
/* The ratings skin plus PINNED IDENTITY COLUMNS. Defense prints nineteen stat
   columns and Kicking sixteen, so the name scrolls off long before the numbers
   run out — and a row of figures you cannot put a name to is not a row.
   Player, Team, Pos and OVR stay; everything from the year rightwards moves.

   THE OFFSETS ARE MEASURED, NOT DECLARED. `--sl-1`…`--sl-4` are written onto
   the table by app.js from the widths the browser actually gave those four
   columns. They have to be: the table is auto-layout, so a column is as wide as
   its longest name or as wide as its own header — never as wide as a width
   declared here. Written as constants, the block pinned at the declared 420px
   over columns that measured a good deal wider, so every pinned cell slid left
   across its neighbour and the moving stat columns showed through the seams.

   The widths below are what the browser is ASKED for, and the calc() fallbacks
   are what the offsets are worth with JS off — near enough that the block still
   reads, exact only if the ask was granted. Each is the sum of the widths before
   it, so a width change is four edits, not one.

   NO STICKY HEADER, deliberately. `.table-scroll` is `overflow-x: auto`, and
   setting either overflow axis to a non-visible value computes the OTHER to
   auto — so the box is its own vertical scroll container, sized to its content,
   and `top: 0` on a <th> has nothing left to stick against. It would look like
   it worked in a short table and do nothing in a long one. The horizontal axis
   is the one that actually scrolls here, and that is the one pinned. */
.stat-table {
  /* Player, Team and Opp are CUT to these, not fitted to them — see
     `.cell-clip` below. The user's call, in these words: a long name losing its
     tail is worth what it buys in width. */
  --sf-1: 120px;   /* Player                                              */
  --sf-2: 108px;   /* Team                                                */
  --sf-3: 58px;    /* Pos    — the label plus its funnel, not the value   */
  --sf-4: 60px;    /* OVR    — likewise: "OVR" is wider than any badge    */
  --clip-1: 104px; /* the three clip widths, each its column less padding  */
  --clip-2: 92px;
  --clip-opp: 112px;
}
/* Team Stats pins ONE column, so the width 120px was rationed against three
   other pinned columns is width it does not have to save: a school name gets to
   finish. Everything else about the table is unchanged. */
.stat-table--team { --sf-1: 156px; --clip-1: 140px; }
/* The season grain carries finish pills in this cell, so it — and ONLY it — buys
   the room for them. A game row is one game and an all-time row spans many
   seasons; neither has a finish, and neither should pay for the width.
   200px, not the 240px it takes to never cut a name: THE PILL OUTRANKS THE NAME
   here. A finish is three letters that cannot be inferred from anything else on
   the row, while a clipped school still reads from its crest, its link and the
   title tooltip — so the budget is set for the pills and the name spends what is
   left. */
.stat-table--team.stat-table--season { --sf-1: 200px; }
/* THE PER-GAME GRAIN FREEZES THE WHOLE IDENTITY BLOCK — Team, Yr, Wk, Opp, Res,
   PF, PA — on the user's call (2026-08-18). A row here is one afternoon, and
   forty stat columns that have scrolled away from which afternoon they belong
   to are forty numbers about nobody. It is ~540px of the window spent on
   identity, which is the price; the offsets are still MEASURED by app.js, so
   these widths only set what each column asks for, never where it lands. */
.stat-table--team.stat-table--game {
  --sf-2: 52px;    /* Yr                                                  */
  --sf-3: 62px;    /* Wk     — 'Natty' is wider than 'Wk 12'              */
  --sf-4: 138px;   /* Opp    — a crest plus a short name                  */
  --sf-5: 54px;    /* Res    — the label plus its funnel, not 'W'         */
  --sf-6: 48px;    /* PF                                                  */
  --sf-7: 48px;    /* PA                                                  */
  --clip-opp: 122px;
}
/* `td` and not the bare class: the HEADER wears `stat-fix-1` too, and it holds
   the sort label and the funnel button rather than a name and pills. */
.stat-table--team.stat-table--season td.stat-fix-1 {
  display: flex; align-items: center; gap: 6px;
}
/* `min-width: 0` is what lets the name shrink at all — a flex item's floor is
   its content width until you say otherwise, and without it the name would push
   the pills out of the cell instead of ellipsising. */
.stat-table--team.stat-table--season td.stat-fix-1 .cell-clip { min-width: 0; }
/* No `margin-left: auto`: the pills sit against the name, not against the far
   edge of the column. Two marks reading as one finish have to touch the thing
   they are about, and a gap that changes width with every school name reads as
   two separate columns that never line up. */
.stat-table--team.stat-table--season td.stat-fix-1 .pill-row {
  flex: none; flex-wrap: nowrap;
}
/* ---- Team Hub → Team Stats ------------------------------------------------ */
/* The same sticky machinery again, pinning FOUR columns — but identity ones
   rather than a name: which year, where it finished, what the record was. None
   of them is a stat, all of them are what a stat means nothing without, and all
   of them are short, so this variant is the narrowest pinned block on the site.
   No `--clip-N`: nothing in this block can overflow. */
.stat-table--hub {
  --sf-1: 62px;    /* Year                                                */
  --sf-2: 58px;    /* Rank   — the label, not "#12", is what sets these   */
  --sf-3: 118px;   /* Finish — two pills, which is the widest real finish */
  --sf-4: 76px;    /* Record                                              */
}
/* THE RANK LINE IS THE TABLE'S SIGNATURE, so it is built to be read ACROSS.
   Every stat cell is a value over its national rank; give the rank line a fixed
   height and tabular figures and the ranks land on one baseline the whole width
   of the row, which turns twenty-three separate numbers into a single band of
   colour that says what kind of season this was. Let the baselines drift and it
   is just small text under big text. */
.stat-table--hub .stack-cell { text-align: center; }
.stat-table--hub .stack-main { font-size: .95rem; }
.stat-table--hub .stack-sub { min-height: 13px; line-height: 13px; }
.stat-table--hub td.hub-stat { padding: 5px 8px; }
/* The pinned block reads as identity, not as data: the year leads in the
   condensed face, the record sits beside it in the same rhythm, and neither
   competes with the numbers that are the point. */
.stat-table--hub td.stat-fix-1 .year-link,
.stat-table--hub td.hub-record {
  font-family: var(--cond); font-weight: 700; font-size: 1rem;
  font-variant-numeric: tabular-nums;
}
.stat-table--hub td.stat-fix-1 .year-link:hover { color: var(--gold-lite); }
.stat-table--hub td.hub-record { text-align: center; }
/* Pills only, and they must not wrap: the column is sized for two and a wrapped
   pill would make one row taller than the rest, breaking the rank band this
   whole table is built to keep level. */
.stat-table--hub td.hub-finish { text-align: center; }
.stat-table--hub td.hub-finish .pill-row { flex-wrap: nowrap; }
/* THE GROUP SEAM. The nineteen columns fall into four runs — what the unit
   produced, how much it ran to produce it, how much it got per snap, and what
   it did when the field ran out — and the column that opens each run carries a
   hairline down its left edge. That is the whole device: no labels, no second
   header row, no colspan.

   IT WAS A LABELLED BAND FIRST AND THE BAND BROKE THE TABLE. A colspanned row
   above the headers makes header cell N a block of columns rather than column
   N, and the pinned-offset measurement in app.js reads that row positionally —
   every frozen column landed at twice its offset, leaving gaps behind Rank and
   Finish and parking Record on top of Pass/G. A hairline says the same thing
   and cannot move a column. */
.stat-table--hub .hub-edge { border-left: 1px solid var(--line); }
/* ---- Team Hub → Player Stats ----------------------------------------------
   Four pinned columns again, but a DIFFERENT four: Player, Pos, OVR, GP. The
   base `.stat-table` widths are budgeted for Player Stats' block, which spends
   its second slot on a school name — so every slot after the first is one
   column out of step and Pos would sit in 108px meant for "Ohio State".

   The name gets the width the missing Team column frees. It is the only thing
   here that can overflow, every other slot is set by its own header rather than
   by any value it holds ("OVR" is wider than 99), and the whole block is
   narrower than Player Stats' for it — which is width handed back to Defense,
   the table on this tab that needs it most at nineteen stat columns. */
.stat-table--hubps {
  --sf-1: 150px;   /* Player                                              */
  --sf-2: 58px;    /* Pos                                                 */
  --sf-3: 60px;    /* OVR    — the label, not the badge, sets this        */
  --sf-4: 54px;    /* GP                                                  */
  --clip-1: 134px; /* the name's column less its padding                  */
}
/* ---- Team Hub → Schedule ---------------------------------------------------
   The fourth pinned block on the site, and the narrowest after the hub's own:
   the week, who it was against, what happened and what the record was after it.
   Everything else on this table is a statistic and scrolls.

   The opponent column is the only one that can overflow, so it is the only one
   with a `--clip-N`. It carries four things — the vs/@, the crest, the school
   and its season record — and a fifth on postseason rows, the bowl or the round
   the Wk column had no room for. */
/* THE WIDTHS ARE ON THE PANEL, not on the table, and that is not tidiness: the
   tab strip is a SIBLING of the table and has to start where the pinned block
   ends. Custom properties inherit down and not sideways, so the one ancestor
   both of them have is where the four widths have to live. `app.js` overwrites
   the sum as `--sl-fix` on this same element once it has measured the real
   columns; until then the declared widths add up to the same place. */
.panel--sched {
  --sf-1: 46px;    /* Wk     — "CCG" is the widest thing in it            */
  --sf-2: 232px;   /* Opponent                                            */
  --sf-3: 92px;    /* Result — the badge plus a two-digit score both ways  */
  --sf-4: 62px;    /* Rec                                                 */
  --clip-2: 216px;
}
.stat-table--sched td { padding: 5px 8px; }
/* EVERY HEADER OVER THE MIDDLE OF ITS OWN COLUMN. `.record-table th` is
   right-aligned — right for a ledger of numbers set flush right, wrong here,
   where every cell but the opponent is centred. The headers sat a third of a
   column to the right of the figures they name.

   `text-indent` pays back the tracking. A .14em letter-space is added AFTER the
   last glyph too, so a centred header is half a space left of true centre; the
   indent puts it back. Not on the opponent, which is flush left and where the
   same indent would simply be a 2px dent. */
.stat-table--sched th { text-align: center; text-indent: .14em; }
.stat-table--sched th.tl { text-align: left; text-indent: 0; }
.stat-table--sched .sch-wk, .stat-table--sched .sch-res-cell { text-align: center; }
/* A loss dims its opponent's NAME and nothing else. The badge already says
   which it was, and a red row would make half a good season look like a
   warning. NOT `opacity` on the row, which the list version used and a table
   cannot: the pinned cells carry their own opaque background, and a translucent
   one lets the stat columns slide visibly through the frozen block. */
.stat-table--sched tr.is-loss .sch-opp { color: var(--muted); }
.stat-table--sched .sch-wk {
  font-family: var(--cond); font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.stat-table--sched .sch-opp-cell { white-space: nowrap; }
.stat-table--sched .sch-loc {
  display: inline-block; width: 18px; color: var(--faint); font-size: .78rem;
}
.stat-table--sched .sch-logo {
  width: 22px; height: 22px; object-fit: contain; vertical-align: middle;
  margin: 0 5px 0 2px;
}
.stat-table--sched .sch-logo--none {
  display: inline-block; background: var(--raised); border-radius: 50%;
}
.stat-table--sched .sch-opp { font-weight: 600; font-size: .9rem; }
.stat-table--sched a.sch-opp:hover { color: var(--gold-lite); }
/* The opponent's season record, and the bowl or round. Both are footnotes to
   the name beside them and are sized as such — small, muted, and never bold
   enough to be mistaken for the score. */
.stat-table--sched .sch-oprec {
  margin-left: 6px; font-family: var(--cond); font-size: .78rem;
  font-variant-numeric: tabular-nums; color: var(--faint);
}
.stat-table--sched .sch-note {
  margin-left: 7px; font-size: .72rem; letter-spacing: .03em;
  text-transform: uppercase; color: var(--muted);
}
.stat-table--sched .sch-res-cell { white-space: nowrap; }
.stat-table--sched .sch-rec {
  font-family: var(--cond); font-weight: 700; font-size: .95rem;
  font-variant-numeric: tabular-nums; text-align: center;
}
.stat-table--sched .sch-stat {
  text-align: center; font-variant-numeric: tabular-nums; font-size: .88rem;
}
.stat-table--sched .hub-edge { border-left: 1px solid var(--line); }
/* THE SWITCH IS A CLASS ON THE TABLE, nothing more. Both groups are in the DOM
   on every render — the whole season is seventeen rows — so changing group is a
   repaint rather than a round trip, and the columns cannot arrive out of step
   with the header they sit under. Exactly one group shows at a time; the server
   renders the opening one's class so the table is right before any JS runs. */
.stat-table--sched .sch-g-stats,
.stat-table--sched .sch-g-adv { display: none; }
.stat-table--sched.show-stats .sch-g-stats,
.stat-table--sched.show-adv .sch-g-adv { display: table-cell; }

/* ---- the group tabs -------------------------------------------------------
   A FOLDER TAB ON THE STAT BAND. The live tab shares the header row's slate and
   has no bottom edge, so the two read as one piece of chrome and the tab is
   plainly ABOUT the columns underneath it — which is the whole reason it is
   here rather than floating above the panel with the page furniture.

   It starts where the columns it governs start: `--sl-fix` is the measured
   width of the pinned block, and the fallback is the four declared widths
   summed, the same construction `.stat-fix-N` uses for its own offsets. */
.sched-tabs {
  display: flex; gap: 2px; align-items: flex-end;
  margin-left: var(--sl-fix, calc(var(--sf-1) + var(--sf-2)
                                  + var(--sf-3) + var(--sf-4)));
}
.sched-tab {
  appearance: none; cursor: pointer; padding: 6px 15px 5px;
  font-family: var(--cond); font-weight: 700; font-size: .72rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
  background: transparent; border: 1px solid transparent; border-bottom: 0;
  border-radius: 5px 5px 0 0;
}
.sched-tab:hover { color: var(--ink-dim); background: rgba(255, 255, 255, .035); }
.sched-tab:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
/* No bottom border and the header band's own background: the seam between the
   live tab and the columns it opens is the one edge that must NOT be drawn. */
.sched-tab.is-current {
  color: var(--gold); background: var(--surface-2); border-color: var(--line);
}

/* HOW MANY COLUMNS ARE PINNED IS THE TEMPLATE'S TO SAY, NOT THIS FILE'S. The
   header cells used to be picked out by `thead th:nth-child(-n+4)` — right for
   Player Stats and wrong for every other shape, because Team Stats pins one
   column and got four sticky headers over three scrolling ones. Yr and GP then
   sat still while their own numbers slid away underneath, which is both the
   headers seen through the gaps and the headers not sitting over their data.
   A `.stat-fix-N` on the <th> as well as the <td> pins the two together by
   construction and cannot go out of step with a grain. */
.stat-table .stat-fix {
  position: sticky; z-index: 2;
  background: var(--surface);
}
/* AND THE SAME THING AGAIN, LOUDER, FOR THE HEADER CELLS.
   `.record-table.data-table th` further down this file sets `position:
   relative` — the funnel button is absolutely positioned and needs something to
   hang off — and at (0,2,1) it outranks the (0,2,0) rule above. So a pinned
   <th> computed `relative`, which turns `left: var(--sl-N)` from "hold this far
   from the scrollport" into "shift this far from where you already are". Every
   pinned header slid right by exactly the width of the pinned columns before
   it: on the Team Hub, gaps opened behind Rank and Finish, the displaced Finish
   cell covered PF/G, and Record came to rest on top of Pass/G. The <td>s were
   never matched by that rule, so the body sat still and only the header moved.

   Team Stats hid it by pinning ONE column at `left: 0`, where `relative` is a
   no-op. Player Stats pins four and had it all along.

   (0,3,1), so it wins on specificity rather than on being further down the
   file, and stays won if either rule moves. */
.record-table.data-table th.stat-fix { position: sticky; }
/* The header cells sit above the body cells they share an edge with, or a tall
   row shows through the column head as it slides under — and they keep the
   header band's own slate rather than the panel's, so the pinned block is not a
   differently coloured rectangle sitting in the middle of the gold rule. */
.stat-table thead .stat-fix { z-index: 4; background: var(--surface-2); }
/* A filtered header is tinted, and a tint is transparent — which over a sticky
   cell means the moving columns read straight through the one column that must
   stay legible. Painted over the band instead of replacing it. */
.stat-table thead th.stat-fix[data-filtered] {
  background: linear-gradient(rgba(214, 169, 78, .09), rgba(214, 169, 78, .09)),
              var(--surface-2);
}
/* The row highlight has to be repainted on the pinned cells: they carry their
   own opaque background, so the hover stopped dead at the seam. */
.stat-table tbody tr:hover .stat-fix { background: var(--surface-2); }
.stat-table .stat-fix-1 { left: 0; width: var(--sf-1); }
.stat-table .stat-fix-2 { left: var(--sl-2, var(--sf-1)); width: var(--sf-2); }
.stat-table .stat-fix-3 {
  left: var(--sl-3, calc(var(--sf-1) + var(--sf-2))); width: var(--sf-3);
}
.stat-table .stat-fix-4 {
  left: var(--sl-4, calc(var(--sf-1) + var(--sf-2) + var(--sf-3))); width: var(--sf-4);
}
/* Slots 5-7 exist for Team Stats' per-game block and are otherwise unused. The
   `--sl-N` fallbacks are the declared widths summed, which is what holds the
   block together for the moment before app.js measures the real ones. */
.stat-table .stat-fix-5 {
  left: var(--sl-5, calc(var(--sf-1) + var(--sf-2) + var(--sf-3) + var(--sf-4)));
  width: var(--sf-5);
}
.stat-table .stat-fix-6 {
  left: var(--sl-6, calc(var(--sf-1) + var(--sf-2) + var(--sf-3) + var(--sf-4)
                         + var(--sf-5)));
  width: var(--sf-6);
}
.stat-table .stat-fix-7 {
  left: var(--sl-7, calc(var(--sf-1) + var(--sf-2) + var(--sf-3) + var(--sf-4)
                         + var(--sf-5) + var(--sf-6)));
  width: var(--sf-7);
}
/* The seam. Drawn on the last pinned cell rather than as a border, so it reads
   as the pinned block casting a shadow over what slides beneath it. Which cell
   that is comes from the template — the fourth on Player Stats, the first
   here — for the reason above. */
.stat-table .stat-fix-last { box-shadow: 6px 0 8px -6px rgba(0, 0, 0, .55); }
/* WHAT ACTUALLY CUTS A NAME OFF. Overflow on the cell does nothing: an
   auto-layout column grows to fit its longest row whatever the cell says, which
   is why these three columns used to be as wide as the longest name in the
   league. A block INSIDE the cell with a max-width the text cannot exceed caps
   what the column can ask for, and gets a real ellipsis into the bargain. The
   full text stays in the cell's `title`. */
.stat-table .rost-name, .stat-table .team-cell {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stat-table .cell-clip {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stat-table .stat-fix-1 .cell-clip { max-width: var(--clip-1); }
.stat-table .stat-fix-2 .cell-clip { max-width: var(--clip-2); }
.stat-table .opp-cell .cell-clip { max-width: var(--clip-opp); }
/* A crest 20px wide with 8px after it spent a quarter of the team column on
   something that is not the name. */
.stat-table .cell-logo { width: 18px; height: 18px; margin-right: 5px; }
.stat-table td { padding: 6px 8px; }

/* ---- the second scrollbar, above the header ------------------------------ */
/* Defense is twice the width of the window and the only bar was under 200 rows,
   so reaching the far-right column meant scrolling down, across, and back up.
   The strip below holds one rail that app.js sizes to the table — that width is
   the whole trick, it is what gives an empty div a scrollbar — and the two
   boxes then copy each other's position.

   Both bars are styled, not just the new one: the bottom bar is the browser's
   own, and a matched pair reads as one control where a native bar under a gold
   one reads as something left unfinished. Firefox takes the two `scrollbar-*`
   properties; Chrome takes the pseudo-elements. The box score's side blocks
   and linescore strip take the same pair — one scrollbar across the site. */
.table-scroll-top { overflow-x: auto; overflow-y: hidden; margin-bottom: 7px; }
.table-scroll-rail { height: 1px; }
.table-scroll-top,
.table-scroll.is-mirrored,
.bl-side, .ls-strip { scrollbar-width: thin; scrollbar-color: var(--gold-deep) var(--surface-2); }
.table-scroll-top::-webkit-scrollbar,
.table-scroll.is-mirrored::-webkit-scrollbar,
.bl-side::-webkit-scrollbar, .ls-strip::-webkit-scrollbar { height: 11px; }
.table-scroll-top::-webkit-scrollbar-track,
.table-scroll.is-mirrored::-webkit-scrollbar-track,
.bl-side::-webkit-scrollbar-track, .ls-strip::-webkit-scrollbar-track {
  background: var(--surface-2); border-radius: 6px;
}
.table-scroll-top::-webkit-scrollbar-thumb,
.table-scroll.is-mirrored::-webkit-scrollbar-thumb,
.bl-side::-webkit-scrollbar-thumb, .ls-strip::-webkit-scrollbar-thumb {
  background: var(--gold-deep); border-radius: 6px;
}
.table-scroll-top::-webkit-scrollbar-thumb:hover,
.table-scroll.is-mirrored::-webkit-scrollbar-thumb:hover,
.bl-side::-webkit-scrollbar-thumb:hover, .ls-strip::-webkit-scrollbar-thumb:hover { background: var(--gold); }

/* THE HEADER IS WHAT MAKES THESE COLUMNS WIDE, not the numbers. A stat column is
   as wide as its label plus whatever its header reserves, and the shared table's
   26px a side was sized around a 19px funnel — so every column carried ~30px of
   air that no figure ever used, nineteen times over on Defense.
   16px a side around a 15px funnel gives the same clearance in half the space.
   Symmetric, because that is what keeps the label centred over its own numbers.

   Three classes deep to beat `.record-table.data-table th`, which sets the
   shared padding further down this file — the same "wins on specificity, not on
   position" rule that block is written under. */
.record-table.data-table.stat-table th { padding-left: 16px; padding-right: 16px; }
.record-table.data-table.stat-table th.tl { padding-left: 8px; }
.record-table.data-table.stat-table th.num { padding-right: 8px; }
.record-table.data-table.stat-table th[data-filter="off"] {
  padding-left: 8px; padding-right: 8px;
}
.stat-table .dt-filter { right: 2px; width: 15px; height: 15px; }
.record-table.stat-table th.num .dt-filter { right: auto; left: 2px; }
/* The sort mark rides the same reduced inset as the funnel it sits beneath. */
.record-table.stat-table th.num[data-sorted]::after { right: 9px; }
.record-table.stat-table th.tl[data-sorted]::after { left: 9px; }
.stat-table .stat-at { color: var(--faint); font-size: .78rem; margin-right: 2px; }
.stat-res { font-family: var(--cond); font-weight: 700; margin-right: 5px; }
/* Team Stats' per-game Res cell holds the badge and NOTHING else — PF and PA
   are their own columns now — so the gap it keeps for a margin beside it would
   only push a one-letter cell off its own centre. */
.stat-table--team.stat-table--game .stat-res { margin-right: 0; }
.stat-res--w { color: #7bc47f; }
.stat-res--l { color: var(--muted); }
.stat-res--t { color: var(--ink-dim); }
/* ---- pager (Player Stats) ------------------------------------------------ */
/* Above AND below the table: 200 rows is a long scroll, and having to go back
   to the top to ask for the next 200 is the whole reason people hate pagers. */
.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap; margin: 0 0 10px;
}
.pager--bottom { margin: 12px 0 0; }
.pager-range { font-family: var(--cond); font-size: .85rem; color: var(--muted); }
.pager-buttons { display: flex; align-items: center; gap: 4px; }
.pager-btn {
  min-width: 30px; padding: 4px 8px; cursor: pointer; line-height: 1.2;
  font-family: var(--cond); font-weight: 600; font-size: .85rem;
  color: var(--ink-dim); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.pager-btn:hover:not(:disabled) { color: var(--ink); border-color: var(--gold-deep); }
.pager-btn.is-current {
  color: var(--gold); border-color: var(--gold-deep); background: var(--raised);
}
.pager-btn:disabled { color: var(--faint); cursor: default; }
.pager-gap { color: var(--faint); padding: 0 2px; }

/* The TOP pager is now the WHOLE panel head on both stat pages. The heading and
   the scope line that used to share it were the rail's three controls read back
   at the reader — "Passing", over a Category picker saying Passing, over "all
   conferences · Passing · season" — and the user cut them (2026-08-14). What is
   on screen is what the rail says; this band only counts.

   The height of that line is PINNED, and that is the point of these rules
   rather than a nicety. A filter that leaves a single page drops the buttons,
   and one that matches nothing leaves only "no lines" — either would shorten
   the head, take the table up with it, and slide the column out from under the
   funnel menu the reader has open. Fixed here, so nothing above the table moves
   while it is being filtered. */
/* `flex-end`, not centre: the pickers are a label over a select, so their
   optical line is the select's bottom edge — which is where the count and the
   page buttons belong. */
.panel-head--stats { align-items: flex-end; flex-wrap: wrap; row-gap: 10px; margin-bottom: 14px; }
.pager--top { margin: 0 0 0 auto; justify-content: flex-end; min-height: 28px; }

/* ---- scope rail (Player Ratings, Standings) ------------------------------ */
/* Season-pickers in a row, and nothing more: the fields reuse `.season-picker`
   / `.season-select` wholesale so the rail reads as the control the rest of the
   site already uses, at a size that fits three of them. The select is the only
   thing dialled down.

   Sits directly under a `.ledger-head`, which already carries its own bottom
   margin — hence no top margin here. */
.scope-rail {
  display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap;
  margin: 0 0 22px;
}
.scope-rail .season-select { font-size: 1.05rem; padding: 6px 36px 6px 13px; min-width: 152px; }
/* THE RAIL INSIDE A PANEL HEAD. It had a band of its own above the panel, and
   between that band and the table sat a second one holding a heading and a
   pager — two mostly-empty rows, with the dead space to the right of the last
   picker and to the left of the page buttons that the user pointed at
   (2026-08-14). One band now: pickers left, count and pages right.

   Dialled down to sit on the pager's line rather than tower over it — the
   control is the same `.season-picker` the rest of the site uses, at the size a
   table header deserves rather than the size a page header did. */
.scope-rail--inline { margin: 0; gap: 12px; }
.scope-rail--inline .season-picker { gap: 3px; }
.scope-rail--inline .season-picker-label { font-size: .58rem; letter-spacing: .16em; }
.scope-rail--inline .season-select {
  font-size: .95rem; padding: 5px 30px 5px 11px; min-width: 128px;
}
.scope-rail--inline .season-caret { right: 9px; width: 14px; height: 14px; }
.scope-go {
  font-family: var(--cond); font-weight: 600; font-size: .95rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--gold); cursor: pointer;
  background: var(--surface); border: 1px solid var(--gold-deep);
  border-radius: var(--radius-sm); padding: 9px 20px;
}
.scope-go:hover { color: var(--gold-lite); background: var(--raised); }
.scope-go:focus-visible { outline: 2px solid var(--gold-lite); outline-offset: 2px; }

/* The swap is a fetch away, and the wide scopes are big enough to notice. */
.panel-swap.is-loading { opacity: .45; transition: opacity .12s ease; }

/* ============================================================================
   Sortable, filterable tables — `.data-table`, driven by static/js/tables.js
   and written by the `data_col` macro. Shared: the roster is the first user.

   Written as `.record-table.data-table` wherever it overrides the plain record
   table, so it wins on specificity rather than on where it sits in this file.
   ========================================================================== */

/* Alignment is declared ONCE per column and worn by the header AND the cells.
   `.record-table` right-aligns a th by default while an unclassed td stays
   left, which is exactly how the roster ended up with labels sitting off their
   own columns. Here a column that does not say otherwise is centered, both rows. */
.record-table.data-table th,
.record-table.data-table td { text-align: center; }
.record-table.data-table th.tl,
.record-table.data-table td.tl { text-align: left; }
.record-table.data-table th.num,
.record-table.data-table td.num { text-align: right; }

/* The label keeps the column's own alignment and the funnel is taken out of the
   flow, so the header text stays exactly over its column however wide the
   control is. Symmetric padding on a centered column; on an aligned one the
   text edge matches the cells' 12px and the slack goes to the far side. */
.record-table.data-table th { position: relative; padding-left: 26px; padding-right: 26px; }
.record-table.data-table th.tl { padding-left: 12px; }
.record-table.data-table th.num { padding-right: 12px; }
/* Nothing to make room for on a column that only sorts. */
.record-table.data-table th[data-filter="off"] { padding-left: 12px; padding-right: 12px; }

/* The dense player tables — the Team Hub roster and Player Ratings, which are
   the same skin — run narrower than the default 26px.

   IT IS THE HEADER THAT SETS A COLUMN'S WIDTH HERE, not the cells: 52px of
   padding around the word "Pos" is what held POS, CL, HT and WT that far apart,
   and dropping the <td>s alone moved nothing. The funnel shrinks with it, to
   the size the stat tables already use, or it would overlap a centred label
   once the padding stops clearing it.

   `:not(.stat-table)` because `.roster-table` is on the stat tables too and
   they set their own; matching both at the same specificity would leave which
   one wins to file order. */
.record-table.data-table.roster-table:not(.stat-table) th {
  padding-left: 16px; padding-right: 16px;
}
.record-table.data-table.roster-table:not(.stat-table) th.tl { padding-left: 8px; }
.record-table.data-table.roster-table:not(.stat-table) th.num { padding-right: 8px; }
.record-table.data-table.roster-table:not(.stat-table) th[data-filter="off"] {
  padding-left: 8px; padding-right: 8px;
}
.roster-table:not(.stat-table) .dt-filter { right: 2px; width: 15px; height: 15px; }
.roster-table:not(.stat-table) th.num .dt-filter { right: auto; left: 2px; }
.data-table tbody tr[hidden] { display: none; }

.dt-label {
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit;
  background: none; border: 0; margin: 0; padding: 0; width: 100%;
  text-align: inherit; cursor: pointer;
}
.dt-label:hover { color: var(--gold-lite); }
.dt-label:focus-visible { outline: 2px solid var(--gold-lite); outline-offset: 3px; }

.dt-filter {
  position: absolute; top: 50%; right: 5px; transform: translateY(-50%);
  display: grid; place-items: center; width: 19px; height: 19px; padding: 0;
  background: none; border: 0; border-radius: 4px; color: var(--faint); cursor: pointer;
  transition: color .13s ease, background .13s ease;
}
.dt-filter svg { width: 11px; height: 11px; }
.data-table th.num .dt-filter { right: auto; left: 5px; }
.dt-filter:hover { color: var(--gold-lite); background: rgba(214, 169, 78, .14); }
.dt-filter:focus-visible { outline: 2px solid var(--gold-lite); outline-offset: 1px; }
.data-table th[data-filtered] { background: rgba(214, 169, 78, .09); }
.data-table th[data-filtered] .dt-filter { color: var(--gold); }

/* The sort mark rides the gold rule the header band already sits on, rather
   than standing next to the label — a caret beside the text would push the
   label off the centre this table just spent its padding getting right. */
.data-table th[data-sorted] { color: var(--gold-lite); }
.data-table th[data-sorted]::after {
  content: ""; position: absolute; left: 50%; bottom: -7px; transform: translateX(-50%);
  width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent;
  pointer-events: none;
}
.data-table th[data-sorted="desc"]::after { border-top: 6px solid var(--gold); }
.data-table th[data-sorted="asc"]::after { border-bottom: 6px solid var(--gold); }
.data-table th.num[data-sorted]::after { left: auto; right: 14px; transform: none; }
.data-table th.tl[data-sorted]::after { left: 14px; transform: none; }

/* Strip above the table — only there once a filter is. */
.dt-status {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-bottom: 11px; font-size: .82rem; color: var(--ink-dim);
}
.dt-status[hidden] { display: none; }
.dt-count b { font-family: var(--cond); font-size: 1.02rem; color: var(--gold-lite); }
.dt-clear-all {
  font: inherit; font-size: .8rem; color: var(--gold); background: none; cursor: pointer;
  border: 1px solid var(--gold-deep); border-radius: 20px; padding: 3px 12px;
}
.dt-clear-all:hover { color: var(--gold-lite); background: rgba(214, 169, 78, .12); }
.dt-clear-all:focus-visible { outline: 2px solid var(--gold-lite); outline-offset: 2px; }
.dt-empty td { color: var(--muted); padding: 26px 12px; }
.dt-empty-msg { margin-right: 8px; }

/* The menu lives in <body> at a fixed position — the table sits in a scroll box
   that would otherwise clip it, and no ancestor can be trusted not to. */
.dt-menu {
  position: fixed; z-index: 60; width: 252px; padding: 11px;
  display: flex; flex-direction: column; gap: 9px; font-size: .84rem;
  background: var(--surface-2); border: 1px solid var(--gold-deep);
  border-radius: var(--radius-sm); box-shadow: 0 16px 40px rgba(0, 0, 0, .55);
}
.dt-menu-head {
  font-family: var(--cond); font-weight: 700; font-size: .76rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--gold);
}
.dt-search {
  font: inherit; width: 100%; padding: 6px 9px; border-radius: 6px;
  background: var(--bg); border: 1px solid var(--line); color: var(--ink);
}
.dt-search::placeholder { color: var(--faint); }
.dt-search:focus { outline: none; border-color: var(--gold-deep); }
.dt-values {
  max-height: 232px; overflow-y: auto;
  background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 3px;
}
.dt-val {
  display: flex; align-items: center; gap: 9px; padding: 4px 6px;
  border-radius: 5px; cursor: pointer;
}
.dt-val:hover { background: var(--raised); }
.dt-val[hidden] { display: none; }
.dt-val input { accent-color: var(--gold); margin: 0; flex: none; }
.dt-val-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-val.is-none .dt-val-name { color: var(--faint); }
.dt-val-n { flex: none; font-size: .76rem; color: var(--faint); font-variant-numeric: tabular-nums; }
/* First row of the list, as Excel draws it — but stuck to the top, so it is
   still there to clear with after you have scrolled 80 hometowns. */
.dt-val.dt-all {
  position: sticky; top: -3px; z-index: 1;
  margin: -3px -3px 3px; padding: 7px 9px; border-radius: 5px 5px 0 0;
  background: var(--surface-2); border-bottom: 1px solid var(--line);
}
.dt-val.dt-all:hover { background: var(--raised); }
.dt-val.dt-all .dt-val-name { color: var(--ink-dim); font-weight: 600; }

/* The Team Hub schedule was a `<ul class="sched">` of flex rows. It is a table
   now — see `.stat-table--sched` — and the row, week, location, crest and
   opponent rules that dressed the list went with it. The result badge and the
   score below are still the schedule's, read inside a table cell. */
.sched-result { display: inline-block; width: 20px; text-align: center; font-family: var(--cond); font-weight: 700; font-size: .92rem; border-radius: 4px; }
.sched-result--w { color: #14100a; background: var(--gold); }
.sched-result--l { color: var(--ink-dim); background: rgba(0,0,0,.3); }
.sched-score { min-width: 46px; text-align: right; font-family: var(--cond); font-weight: 600; font-variant-numeric: tabular-nums; color: var(--ink-dim); }

/* ---- login (password-gated sites only) ---------------------------------- */
.login-wrap { display: grid; place-items: center; min-height: 100vh; padding: 20px; }
.login-card { width: 100%; max-width: 360px; text-align: center; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 34px 30px; }
.login-logo { height: 88px; width: auto; margin-bottom: 10px; }
.login-card h1 { font-size: 1.7rem; }
.login-card .muted { color: var(--muted); font-size: .88rem; margin: 6px 0 18px; }
.login-card input, .login-card button { width: 100%; padding: 11px 13px; margin-top: 10px; border-radius: var(--radius-sm); border: 1px solid var(--line); font-size: 1rem; font-family: var(--sans); }
.login-card input { background: var(--bg); color: var(--ink); }
.login-card button { background: var(--gold); color: #14100a; font-weight: 700; border: none; cursor: pointer; }
.login-card button:hover { background: var(--gold-lite); }
.login-card .error { color: #ff8080; font-size: .85rem; font-weight: 600; margin-top: 12px; }


/* ============================================================================
   Awards page — the trophy case
   One award at a time. The 24 cut-outs from the award screens are the page's
   only ornament, so they are treated as objects: stood on a shelf rather than
   boxed as icons, lit from beneath, and repeated at scale over the table. The
   cut-outs are trimmed tight and vary in height, which is why every trophy
   aligns to a shared BASELINE — equal boxes would flatten that back into a
   toolbar and lose the case.
   ========================================================================== */
.tc-case { padding: 14px 4px 4px; overflow: hidden; }

.tc-shelf { display: flex; gap: 26px; overflow-x: auto; padding: 0 16px 4px; scrollbar-width: thin; }
.tc-group { display: flex; flex-direction: column; gap: 7px; flex: none; }
.tc-group-label {
  font-family: var(--cond); font-size: .6rem; font-weight: 600; letter-spacing: .2em;
  text-transform: uppercase; color: var(--faint); padding-left: 2px;
}
/* The shelf itself: one hairline rule the trophies stand on, lit gold. */
.tc-group-rail {
  display: flex; align-items: flex-end; gap: 4px;
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--gold-deep) 30%, transparent);
}

.tc-pick {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  width: 76px; padding: 6px 4px 0; cursor: pointer;
}
.tc-pick-stage {
  display: flex; align-items: flex-end; justify-content: center;
  height: 52px; width: 100%; position: relative;
}
/* The pool of light each trophy stands in — the tell that it's a shelf and not
   a row of buttons. Sits under the art, brightens with the selection. */
.tc-pick-stage::after {
  content: ""; position: absolute; bottom: -1px; left: 50%; translate: -50% 0;
  width: 46px; height: 9px; border-radius: 50%;
  background: radial-gradient(ellipse at center,
              color-mix(in srgb, var(--gold) 26%, transparent), transparent 70%);
  opacity: 0; transition: opacity .18s, width .18s;
}
.tc-pick-art {
  max-height: 52px; max-width: 56px; object-fit: contain; object-position: bottom;
  filter: saturate(.55) brightness(.78); transition: filter .18s, translate .18s;
}
.tc-pick-art--none {
  display: grid; place-items: center; width: 34px; height: 44px;
  font-family: var(--cond); font-weight: 700; font-size: 1.5rem; color: var(--gold-deep);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 4px;
}
.tc-pick-name {
  font-family: var(--cond); font-size: .64rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--faint); text-align: center; line-height: 1.15;
  padding-bottom: 5px; transition: color .16s;
}

/* Hover and selection are told in LIGHT, not in chrome: the trophy comes up out
   of shadow and its pool brightens. No background plate on either — that would
   turn the shelf back into the row of buttons this is deliberately not. */
.tc-pick:hover .tc-pick-art { filter: saturate(.85) brightness(.95); }
.tc-pick:hover .tc-pick-name { color: var(--ink-dim); }
.tc-pick:hover .tc-pick-stage::after { opacity: .5; }

/* Selected: lifted off the shelf, full colour, its nameplate engraved gold —
   and the REST of the case falls back into shadow, so the shelf itself says
   which award is open. (There is no hero restating it below any more.) */
.tc-pick.is-current .tc-pick-art { filter: none; translate: 0 -3px; }
.tc-pick.is-current .tc-pick-stage::after { opacity: 1; width: 54px; }
.tc-pick.is-current .tc-pick-name { color: var(--gold-lite); }
.tc-pick:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
.tc-shelf .tc-pick:not(.is-current) { opacity: .5; transition: opacity .16s; }
.tc-shelf .tc-pick:not(.is-current):hover { opacity: 1; }

/* --- the hero: trophy and titles left, the reigning winner right ---------- */
/* One band, no rule between it and the table below — the hero IS the table's
   newest season, pulled up and set at size, so a divider would cut the record
   in half. Every piece enters on a short stagger when an award is swapped in:
   trophy first, then the winner, the finalists, the history. */
.tc-hero {
  display: flex; align-items: center; flex-wrap: wrap; gap: 14px 22px;
  padding: 6px 4px 18px;
}
.tc-plinth {
  flex: none; width: 116px; height: 118px; position: relative;
  display: flex; align-items: flex-end; justify-content: center;
}
.tc-plinth::before {          /* the case light */
  content: ""; position: absolute; inset: -14% -6% 6%;
  background: radial-gradient(ellipse at 50% 62%,
              color-mix(in srgb, var(--gold) 20%, transparent), transparent 68%);
}
.tc-plinth::after {           /* the shelf it stands on */
  content: ""; position: absolute; bottom: 0; left: 8%; right: 8%; height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold-deep), transparent);
}
.tc-trophy {
  max-height: 112px; max-width: 104px; object-fit: contain; object-position: bottom;
  position: relative; filter: drop-shadow(0 6px 10px rgba(0,0,0,.55));
  animation: tc-rise .32s ease-out backwards;
}
.tc-trophy--none {
  display: grid; place-items: center; width: 76px; height: 94px;
  font-family: var(--cond); font-weight: 700; font-size: 2.6rem; color: var(--gold-deep);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 6px;
}
@keyframes tc-rise { from { opacity: 0; translate: 0 8px; } to { opacity: 1; translate: 0 0; } }

.tc-titles { min-width: 0; }
.tc-award {
  font-family: var(--cond); font-weight: 700; font-size: 2.2rem; line-height: 1.02;
  text-transform: uppercase; letter-spacing: .012em; margin: 0;
}
.tc-recognizes { margin: 5px 0 0; font-size: .92rem; color: var(--ink-dim); }
.tc-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 10px 0 0;
  font-size: .72rem; letter-spacing: .04em; color: var(--muted);
}

/* The reigning winner: the Record Book's portrait-and-jersey face (`.rb-face`;
   the crest becomes the picture only when no portrait exists), seated right
   beside the titles behind a hairline — no card, no pill: the portrait and
   the name carry it, and the team-colour ring on the face is the only wash.
   The finalists line up under it, one row, and scroll if the year ran deep. */
/* Stretch to the hero's full height, winner on top, finalists pinned to the
   bottom edge — the band between them is what lets the portrait run big
   instead of leaving dead air under the runner-up line. */
.tc-reignwrap {
  display: flex; flex-direction: column; gap: 10px; justify-content: space-between;
  align-self: stretch; min-width: 0; max-width: 100%;
  padding-left: 24px; border-left: 1px solid var(--line);
}
.tc-reign {
  --rb-face: 96px;
  display: flex; align-items: center; gap: 16px;
  animation: tc-rise .32s ease-out .08s backwards;
}
.tc-reign-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tc-reign-eyebrow {
  font-family: var(--cond); font-size: .62rem; font-weight: 600; letter-spacing: .2em;
  text-transform: uppercase; color: var(--gold-lite);
}
.tc-reign-name {
  font-family: var(--cond); font-weight: 700; font-size: 1.5rem; line-height: 1.1;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tc-reign-name a { color: inherit; text-decoration: none; }
.tc-reign-name a:hover { color: var(--gold-lite); }
/* The crest sits where the team's NAME used to be — one mark instead of a
   word, with the school on its hover title. */
.tc-reign-line { margin-top: 2px; }
.tc-reign-line .tc-crest { width: 18px; height: 18px; }

.tc-finalists {
  display: flex; align-items: center; gap: 16px;
  overflow-x: auto; scrollbar-width: thin; padding: 2px 2px 4px;
  animation: tc-rise .32s ease-out .16s backwards;
}
.tc-final { flex: none; display: flex; align-items: center; gap: 7px; }
/* A hairline between the places, so the row reads as a ranking rather than a
   run-on sentence. */
.tc-final + .tc-final { border-left: 1px solid var(--line); padding-left: 16px; }
.tc-final-place {
  font-family: var(--cond); font-size: .62rem; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--gold-deep);
}
.tc-final .tc-crest { width: 17px; height: 17px; }
.tc-final-name {
  font-family: var(--cond); font-weight: 600; font-size: .95rem; white-space: nowrap;
  color: var(--ink-dim);
}
.tc-final-name a { color: inherit; text-decoration: none; }
.tc-final-name a:hover { color: var(--gold-lite); }
.tc-final-pos { font-size: .66rem; font-weight: 600; color: var(--gold); }

/* --- the table: winner left, finalists descending right ------------------- */
.tc-scroll {
  max-height: calc(100vh - 340px); overflow: auto;
  animation: tc-rise .36s ease-out .22s backwards;
}
.tc-table { font-size: .86rem; }
.tc-table thead th { position: sticky; top: 0; z-index: 2; }
.tc-table th.tc-th-season { width: 1%; }
.tc-table td.tc-season {
  font-family: var(--cond); font-weight: 700; font-size: 1.05rem; color: var(--ink-dim);
}
/* The winner's column is the point of the table: gold head, widest column, and
   a plinth edge in the winning team's colour. The finalists recede. */
/* Base first, winner second: both are one class deep, so source order is what
   decides — flipped, the generic head would repaint the winner's gold grey. */
.tc-th { text-align: left !important; color: var(--muted) !important; font-size: .72rem !important; min-width: 158px; }
.tc-th--1 { color: var(--gold-lite) !important; font-size: .8rem !important; min-width: 210px; }
.tc-cell { border-left: 1px solid var(--line-soft); }
.tc-cell--1 { border-left: 3px solid var(--team); background: color-mix(in srgb, var(--raised) 42%, transparent); }

.tc-name { display: flex; align-items: center; gap: 9px; min-width: 0; }
.tc-crest { flex: none; width: 19px; height: 19px; }
.tc-crest img { width: 100%; height: 100%; object-fit: contain; }
.tc-crest--none { border-radius: 50%; background: var(--surface-2); display: block; }
.tc-cell--1 .tc-crest { width: 28px; height: 28px; }
.tc-player { min-width: 0; overflow: hidden; text-overflow: ellipsis; font-weight: 500; color: var(--ink-dim); line-height: 1.2; }
.tc-cell--1 .tc-player { font-weight: 600; font-size: 1rem; color: var(--ink); }
.tc-crest:hover + .tc-player { color: var(--gold-lite); }
/* The class/position line survives only in the hero — the table is crest and
   name alone. */
.tc-line { display: flex; align-items: center; gap: 6px; margin-top: 2px; font-size: .66rem; }
.tc-class { color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.tc-pos { font-weight: 600; color: var(--gold); }
.tc-dash { color: var(--faint); }

@media (max-width: 900px) {
  .tc-hero { gap: 12px 16px; padding-bottom: 14px; }
  .tc-plinth { width: 88px; height: 92px; }
  .tc-trophy { max-height: 86px; max-width: 80px; }
  .tc-award { font-size: 1.7rem; }
  /* Narrow: the wrap dissolves (`display: contents`) so its two children
     become the hero's own flex items — the winner stays UP beside the award
     titles, and the finalists take a full row of their own below. */
  .tc-reignwrap { display: contents; }
  /* Both clusters can shrink (the names ellipsize), so the winner always FITS
     beside the titles instead of wrapping under them. */
  .tc-titles { flex: 1 1 0; }
  .tc-reign { --rb-face: 72px; flex: 0 1 auto; min-width: 0; }
  .tc-finalists { width: 100%; border-top: 1px solid var(--line-soft); padding-top: 10px; }
  .tc-scroll { max-height: none; }
}

/* Phone: the hero stops being a three-column squeeze. The award's WORDS take a
   full-width row of their own — the name, then what it recognises and the span
   on one line — and the two PICTURES share the row below, at size. That is the
   whole point of the rearrangement: at 390px the middle column was ~220px and
   spent five wrapped lines to leave a 72px trophy beside a 60px face. Both now
   scale with the viewport instead of sitting at one fixed phone size. */
@media (max-width: 620px) {
  .tc-hero {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "titles titles"
      "trophy winner"
      "finals finals";
    align-items: center;
    column-gap: 16px; row-gap: 12px;
  }
  .tc-titles {
    grid-area: titles;
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 8px;
  }
  .tc-award { flex: 1 0 100%; font-size: clamp(1.35rem, 6.2vw, 1.75rem); }
  .tc-recognizes { margin: 0; font-size: .86rem; }
  .tc-meta { margin: 0; }
  /* Only when there is something for it to separate. */
  .tc-recognizes + .tc-meta::before { content: "\00b7"; color: var(--faint); }

  .tc-plinth { grid-area: trophy; width: clamp(92px, 30vw, 132px); height: clamp(98px, 32vw, 140px); }
  .tc-trophy { max-height: 100%; max-width: 92%; }
  .tc-reign { grid-area: winner; --rb-face: clamp(74px, 24vw, 104px); gap: 12px; }
  /* Beside a picture this tall a long name is better wrapped than clipped —
     two lines still sit inside the portrait's height. */
  .tc-reign-name { font-size: 1.15rem; white-space: normal; overflow: visible; }
  .tc-finalists { grid-area: finals; }
}
@media (prefers-reduced-motion: reduce) {
  .tc-trophy, .tc-reign, .tc-finalists, .tc-scroll { animation: none; }
  .tc-pick, .tc-pick-art, .tc-pick-name, .tc-pick-stage::after { transition: none; }
}

/* ============================================================================
   ALL-AMERICANS — the depth chart, read across the years.
   The game picks the same 25 spots every season, so the honest shape is a GRID:
   position down the side, season across the top, one player in each box. Read a
   row and you have that position's whole history; read a column and you have
   that year's team. Three of them stacked, first team on top.
   ========================================================================== */
.aa-case { padding: 14px 4px 6px; overflow: hidden; }
.aa-picker {
  display: flex; align-items: flex-end; gap: 4px;
  overflow-x: auto; padding: 0 16px 4px; scrollbar-width: thin;
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--gold-deep) 30%, transparent);
}
.aa-pick-tab {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  width: 72px; padding: 6px 4px 0; cursor: pointer; flex: none;
}
/* National is the whole league's team, not one more conference, so it sits
   apart behind a rule rather than being the first of twelve equals. */
.aa-pick-tab--national { border-right: 1px solid var(--line); margin-right: 10px; padding-right: 12px; }
.aa-pick-stage {
  display: flex; align-items: center; justify-content: center;
  height: 46px; width: 100%; position: relative;
}
.aa-pick-stage::after {
  content: ""; position: absolute; bottom: -5px; left: 50%; translate: -50% 0;
  width: 42px; height: 8px; border-radius: 50%;
  background: radial-gradient(ellipse at center,
              color-mix(in srgb, var(--gold) 26%, transparent), transparent 70%);
  opacity: 0; transition: opacity .18s, width .18s;
}
.aa-pick-art {
  max-height: 42px; max-width: 46px; object-fit: contain;
  filter: saturate(.5) brightness(.76); transition: filter .18s, translate .18s;
}
.aa-pick-art--none {
  display: grid; place-items: center; min-width: 40px; height: 38px; padding: 0 6px;
  font-family: var(--cond); font-weight: 700; font-size: .95rem; color: var(--gold-deep);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 4px;
}
.aa-pick-name {
  font-family: var(--cond); font-size: .62rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--faint); text-align: center; line-height: 1.15;
  padding-bottom: 5px; transition: color .16s;
}
.aa-pick-tab:hover .aa-pick-art { filter: saturate(.9) brightness(.98); }
.aa-pick-tab:hover .aa-pick-name { color: var(--ink-dim); }
.aa-pick-tab:hover .aa-pick-stage::after { opacity: .5; }
.aa-pick-tab.is-current .aa-pick-art { filter: none; translate: 0 -3px; }
.aa-pick-tab.is-current .aa-pick-stage::after { opacity: 1; width: 50px; }
.aa-pick-tab.is-current .aa-pick-name { color: var(--gold-lite); }
.aa-pick-tab:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
/* The picker says which scope is open — the current mark stands lit while the
   rest recede. (No hero restates it below any more.) */
.aa-picker .aa-pick-tab:not(.is-current) { opacity: .5; transition: opacity .16s; }
.aa-picker .aa-pick-tab:not(.is-current):hover { opacity: 1; }

/* The tier heads are the panel's structure now, so the gold rule that used to
   run under every year row moved up here, and the names grew into headings. */
.aa-tier { margin-bottom: 26px; }
.aa-tier-head {
  display: flex; align-items: baseline; gap: 10px; margin: 0 0 8px; padding: 0 2px 6px;
  border-bottom: 2px solid var(--gold-deep);
}
.aa-tier-name {
  font-family: var(--cond); font-size: 1.3rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold);
}

.aa-scroll { overflow: auto; }
.aa-table { font-size: .8rem; }
/* The grid's own chrome is QUIET on purpose — muted heads, hairline rules, no
   column borders — so the gold tier heads above are the only structure the eye
   snags on. The generic `.record-table th` gold is overridden here. */
.aa-table th { color: var(--muted); border-bottom: 1px solid var(--line); }
/* Position down the side and season across the top both stay put while the
   other axis scrolls — a 25 x 8 grid is unreadable if either label leaves. */
.aa-table thead th { position: sticky; top: 0; z-index: 3; background: var(--surface); }
.aa-table th.aa-th-pos {
  position: sticky; left: 0; z-index: 4; width: 1%; white-space: nowrap;
  background: var(--surface);
}
.aa-table th.aa-pos {
  position: sticky; left: 0; z-index: 2; background: var(--surface);
  font-family: var(--cond); font-size: .82rem; font-weight: 700; letter-spacing: .08em;
  color: var(--ink-dim); text-align: left; white-space: nowrap;
  border-right: 1px solid var(--line);
}
.aa-th { font-variant-numeric: tabular-nums; text-align: left; }
/* A season the dynasty has not reached yet: the column is kept so the shape of
   the record book is visible, but it is plainly not missing DATA. */
.aa-th--empty { color: var(--faint); }

.aa-cell { min-width: 158px; }
.aa-cell:not(.aa-cell--empty) {
  border-left: 3px solid var(--team);
  background: color-mix(in srgb, var(--raised) 38%, transparent);
}
.aa-pick { display: flex; align-items: center; gap: 8px; min-width: 0; }
.aa-crest { flex: none; width: 24px; height: 24px; }
.aa-crest img { width: 100%; height: 100%; object-fit: contain; }
.aa-crest--none { border-radius: 50%; background: var(--surface-2); display: block; }
.aa-who { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.aa-player { font-weight: 600; color: var(--ink); }
.aa-crest:hover + .aa-who .aa-player { color: var(--gold-lite); }
.aa-class {
  margin-top: 2px; font-size: .66rem; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.aa-dash { color: var(--faint); }

@media (max-width: 900px) {
  .aa-cell { min-width: 140px; }
}
@media (prefers-reduced-motion: reduce) {
  .aa-pick-tab, .aa-pick-art, .aa-pick-name, .aa-pick-stage::after { transition: none; }
}

/* ============================================================================
   RECORD BOOK — nine records down, three timeframes across.
   The record type is named ONCE on the left and the timeframes ONCE in the
   head, so no cell carries a label: a cell is a crest, a name, a year and the
   mark, and nothing else. That is the whole idea — an earlier pass printed
   "Passing Yards" three times across one eyeful, a later one printed HOLDER /
   GAME / MARK over every book, and both spent the width on captions.

   ONE MARKUP, TWO SHAPES, SWITCHED AT 1180px — the same number `.rsum-sides`
   already breaks at, so this adds none to the file. Three cells need ~220px
   each beside a ~120px key column, which is what 1180 leaves once the sidebar
   is out of the way.

   Below it the timeframe tabs appear, the row shows only the open cell, and
   THAT CELL GOES `display: contents` — its holder and its mark drop into the
   row's own grid, which is what lets a head line up over them on a phone. The
   school name is a compressed-only line for the same reason: wide, the crest is
   already beside the name and there is no room to say it three times across.

   The rows are a LIST wearing a grid's alignment, not a data-table — a record
   book is already ranked, so sort arrows and filter funnels would be chrome
   over a decision nobody has to make.
   ========================================================================== */
.rb-case { padding: 14px 4px 6px; overflow: hidden; }
.rb-picker {
  display: flex; align-items: flex-end; gap: 4px;
  overflow-x: auto; padding: 0 16px 4px; scrollbar-width: thin;
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 color-mix(in srgb, var(--gold-deep) 30%, transparent);
}
.rb-pick-tab {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  width: 72px; padding: 6px 4px 0; cursor: pointer; flex: none;
}
/* National is the whole league's book, not one more conference. */
.rb-pick-tab--national { border-right: 1px solid var(--line); margin-right: 10px; padding-right: 12px; }
.rb-pick-stage {
  display: flex; align-items: center; justify-content: center;
  height: 46px; width: 100%; position: relative;
}
.rb-pick-stage::after {
  content: ""; position: absolute; bottom: -5px; left: 50%; translate: -50% 0;
  width: 42px; height: 8px; border-radius: 50%;
  background: radial-gradient(ellipse at center,
              color-mix(in srgb, var(--gold) 26%, transparent), transparent 70%);
  opacity: 0; transition: opacity .18s, width .18s;
}
.rb-pick-art {
  max-height: 42px; max-width: 46px; object-fit: contain;
  filter: saturate(.5) brightness(.76); transition: filter .18s, translate .18s;
}
/* FBS Independents has no crest on disk and no EA book. This is what it wears,
   and it is the site's only fallback — there is no onerror= anywhere. */
.rb-pick-art--none {
  display: grid; place-items: center; min-width: 40px; height: 38px; padding: 0 6px;
  font-family: var(--cond); font-weight: 700; font-size: .95rem; color: var(--gold-deep);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 4px;
}
.rb-pick-name {
  font-family: var(--cond); font-size: .62rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--faint); text-align: center; line-height: 1.15;
  padding-bottom: 5px; transition: color .16s;
}
.rb-pick-tab:hover .rb-pick-art { filter: saturate(.9) brightness(.98); }
.rb-pick-tab:hover .rb-pick-name { color: var(--ink-dim); }
.rb-pick-tab:hover .rb-pick-stage::after { opacity: .5; }
.rb-pick-tab.is-current .rb-pick-art { filter: none; translate: 0 -3px; }
.rb-pick-tab.is-current .rb-pick-stage::after { opacity: 1; width: 50px; }
.rb-pick-tab.is-current .rb-pick-name { color: var(--gold-lite); }
.rb-pick-tab:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }

.rb-panel {
  --rb-face: 44px;
  --rb-cols: minmax(104px, .5fr) repeat(3, minmax(0, 1fr));
  padding: 0 0 2px;
}

/* The timeframe tabs. Compressed widths only — wide, all three books are on
   screen and there is nothing to switch. */
.rb-cats { display: none; gap: 2px; flex-wrap: wrap; border-bottom: 1px solid var(--line); }
.rb-cat {
  font-family: var(--cond); font-weight: 600; font-size: 1rem;
  letter-spacing: .1em; text-transform: uppercase;
  padding: 9px 18px; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  color: var(--muted); border-bottom: 3px solid transparent; margin-bottom: -1px;
  transition: color .14s, background .14s;
}
.rb-cat:hover { color: var(--ink-dim); background: rgba(255, 255, 255, .03); }
.rb-cat.is-current { color: var(--gold-lite); border-bottom-color: var(--gold); }
.rb-cat:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }

/* The head names the four columns and nothing else does. aria-hidden because
   every cell under it already reads on its own; a screen reader gets the row. */
.rb-head {
  display: grid; grid-template-columns: var(--rb-cols); align-items: end;
  gap: 0 12px; padding: 6px 10px 8px;
  font-family: var(--cond); font-size: .68rem; font-weight: 600; letter-spacing: .22em;
  text-transform: uppercase; color: var(--gold-deep);
  border-bottom: 1px solid var(--line);
}
/* 14px = the cell's 2px rule plus its 12px of padding, so a heading sits over
   the crest it names rather than over the rule beside it. */
.rb-h-cat { padding-left: 14px; }
.rb-h-mark { display: none; }

.rb-rows { list-style: none; margin: 0; padding: 0; }
.rb-row {
  display: grid; grid-template-columns: var(--rb-cols); align-items: stretch;
  gap: 0 12px; padding: 5px 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
}
.rb-row:last-child { border-bottom: 0; }
.rb-key {
  align-self: center;
  font-family: var(--cond); font-size: .88rem; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-dim); min-width: 0;
}

/* One holder, one cell. The rule down its left is the divider the grid needs
   AND the holder's team colour — one device doing both jobs, which is why the
   book can be scanned by school without a swatch column. */
.rb-cell {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 10px; min-width: 0;
  padding: 5px 4px 5px 12px;
  border-left: 2px solid color-mix(in srgb, var(--team) 55%, transparent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  transition: background .14s;
}
.rb-cell:hover { background: color-mix(in srgb, var(--team) 10%, transparent); }
.rb-cell--open { border-left-color: var(--line-soft); place-items: center; }
.rb-cell--open:hover { background: none; }
.rb-none { color: var(--faint); font-size: 1rem; }

.rb-holder { display: flex; align-items: center; gap: 10px; min-width: 0; }
.rb-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.rb-name {
  font-size: .86rem; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rb-name a { color: inherit; }
.rb-name a:hover { color: var(--gold-lite); }
.rb-meta {
  display: flex; align-items: baseline; gap: 5px; min-width: 0;
  font-family: var(--cond); font-size: .74rem; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
/* Wide, the crest says the school. The name is a compressed-only line, and the
   separator is attached to it so hiding one hides both. */
.rb-team { display: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rb-team::after { content: "·"; margin-left: 5px; color: var(--faint); }
.rb-when { font-variant-numeric: tabular-nums; white-space: nowrap; }
.rb-when-link { color: var(--muted); border-bottom: 1px solid transparent; }
.rb-when-link:hover { color: var(--gold-lite); border-bottom-color: color-mix(in srgb, var(--gold) 50%, transparent); }
/* The mark ends the cell, it does not headline it. Gold and tabular figures do
   the emphasising; it does not also carry twice everyone else's height. */
.rb-mark {
  font-family: var(--cond); font-size: 1.05rem; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--gold-lite); line-height: 1;
  text-align: right;
}

/* The face: the same three-layer stack the player banner uses. The crest sits
   behind the shoulder, and BECOMES the picture when there is no portrait —
   which is every EA holder, since they have no players row at all. The ring is
   the holder's team colour, which is what ties a crest-only cell to a school. */
.rb-face {
  position: relative; flex: none;
  width: var(--rb-face); height: var(--rb-face);
  border-radius: 9px; overflow: hidden;
  background: rgba(8, 10, 14, .5);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--team) 55%, rgba(255, 255, 255, .14));
}
/* Portrait and jersey are drawn on the SAME 512x512 canvas, so they take the
   same crop — a crop on one and not the other slides the collar off the neck. */
.rb-face-img {
  position: absolute; top: 0; left: 0; z-index: 2;
  width: 100%; height: 100%; object-fit: cover; display: block;
  object-position: 50% 100%;
}
.rb-face-jersey { pointer-events: none; }
.rb-face-mark {
  position: absolute; z-index: 1;
  top: calc(var(--rb-face) * .06); right: calc(var(--rb-face) * -.1);
  width: calc(var(--rb-face) * .58); height: calc(var(--rb-face) * .58);
  object-fit: contain; opacity: .9;
}
.rb-face-mark--solo {
  top: 50%; right: 50%; transform: translate(50%, -50%);
  width: calc(var(--rb-face) * .72); height: calc(var(--rb-face) * .72);
  opacity: .9;
}

/* --- Compressed: one timeframe at a time --------------------------------- */
/* Three cells no longer fit, so the tabs come back and the row shows the open
   one. `display: contents` on that cell drops its holder and its mark into the
   row's grid, which is what lets the head line up over them. `--team` still
   inherits through it, so the face keeps its ring. */
@media (max-width: 1180px) {
  .rb-panel { --rb-cols: minmax(0, 1fr) auto; --rb-face: 46px; }
  .rb-cats { display: flex; }
  .rb-head { padding: 12px 10px 8px; }
  .rb-h-cat { display: none; }
  .rb-h-mark { display: block; text-align: right; }

  .rb-row {
    grid-template-areas: "key mark" "holder holder";
    align-items: center; gap: 6px 12px; padding: 9px 10px;
  }
  .rb-key { grid-area: key; align-self: end; font-size: .84rem; }
  .rb-name { font-size: .95rem; }
  .rb-mark { font-size: 1.15rem; }
  .rb-cell { display: none; }
  .rb-cell.is-shown { display: contents; }
  .rb-holder { grid-area: holder; }
  .rb-none { grid-area: holder; }
  .rb-mark { grid-area: mark; align-self: end; }
  /* The school has room again, and with no head over the crest it is the only
     thing naming it. */
  .rb-team { display: block; }
}
@media (max-width: 560px) {
  .rb-cat { font-size: .9rem; padding: 9px 13px; letter-spacing: .06em; }
  .rb-key { font-size: .78rem; }
  .rb-mark { font-size: 1rem; }
}
@media (prefers-reduced-motion: reduce) {
  .rb-pick-tab, .rb-pick-art, .rb-pick-name, .rb-pick-stage::after,
  .rb-cat, .rb-cell { transition: none; }
}

/* --- Capture: the stats team picker ------------------------------------- */
/* Folded away by default. 138 teams is a wall, and most runs are a filter click
   plus Run — the grid is for checking what the filter chose, not for scrolling. */
.cap-teams { border: 1px solid var(--line); border-radius: var(--radius-sm); margin-bottom: 10px; }
.cap-teams > summary {
  cursor: pointer; padding: 8px 12px; font-family: var(--cond); font-size: .74rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-dim);
}
.cap-teams.has-picks > summary { color: var(--gold-lite); }
.cap-teams-count { color: var(--faint); margin-left: 8px; letter-spacing: .04em; }
.cap-teams.has-picks .cap-teams-count { color: var(--gold); }
.cap-teams-bar {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  padding: 0 12px 8px; border-bottom: 1px solid var(--line-soft);
}
.cap-teams-n {
  margin-left: 5px; padding: 0 5px; border-radius: 999px;
  background: var(--surface-2); color: var(--gold); font-size: .72em;
}
.cap-teams-bar [disabled] { opacity: .45; cursor: not-allowed; }
.cap-teams-note { margin: 8px 12px 0; font-size: .74rem; color: var(--muted); }
.cap-teams-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(184px, 1fr));
  gap: 2px; padding: 8px; max-height: 340px; overflow: auto;
}
.cap-team {
  display: flex; align-items: center; gap: 7px; padding: 4px 6px;
  border-radius: 4px; cursor: pointer; font-size: .8rem; color: var(--ink-dim);
}
.cap-team:hover { background: var(--surface-2); }
.cap-team img { width: 18px; height: 18px; object-fit: contain; flex: none; }
.cap-team-pos {
  width: 26px; flex: none; text-align: right; font-variant-numeric: tabular-nums;
  font-size: .7rem; color: var(--faint);
}
.cap-team-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cap-team:has(input:checked) { background: color-mix(in srgb, var(--gold) 14%, transparent); }
.cap-team:has(input:checked) .cap-team-name { color: var(--ink); }

/* League History — Heisman column winner cell */
.heis-win { display: inline-flex; align-items: center; gap: 8px; }
.heis-logo { width: 24px; height: 24px; object-fit: contain; flex: none; }
.heis-name { font-weight: 600; }
.heis-team:hover ~ .heis-name { color: var(--gold-lite); }

/* Team Hub — National Awards section */
.hub-awards { list-style: none; margin: 0; padding: 0; }
.hub-award { display: flex; align-items: center; gap: 12px; padding: 11px 4px; border-top: 1px solid var(--line-soft); }
.hub-award:first-child { border-top: none; }
.hub-award-ico { font-size: 1.15rem; flex: none; }
.hub-award-meta { display: flex; flex-direction: column; min-width: 0; }
.hub-award-name { font-family: var(--cond); font-weight: 700; font-size: 1.1rem; line-height: 1.05; }
.hub-award-sub { font-size: .64rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.hub-award-win { margin-left: auto; text-align: right; display: flex; flex-direction: column; }
.hub-award-player { font-weight: 600; color: var(--gold-lite); }
.hub-award-pos { font-size: .7rem; color: var(--muted); }

/* ============================================================================
   Ledger head — compact reusable running-head (replaces the tall page-head on
   record pages) + conference "highlight a team" filter on the right.
   ========================================================================== */
.ledger-head {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 16px 28px;
  flex-wrap: wrap; padding-bottom: 12px; margin-bottom: 20px;
  border-bottom: 1px solid var(--line); position: relative;
}
.ledger-head::after {
  content: ""; position: absolute; left: 0; bottom: -1px; height: 2px; width: 96px;
  background: linear-gradient(90deg, var(--gold), transparent);
}
.ledger-title { display: flex; align-items: baseline; gap: 12px; min-width: 0; }
.ledger-mark {
  align-self: center; flex: none; width: 11px; height: 20px; background: var(--gold);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 78%, 0 100%);
}
/* The page's own nav glyph, in gold — the sidebar mark and the page mark are
   the same drawing, so the head confirms where the sidebar says you are. The
   icons are stroked with currentColor, which is all this has to set. */
.ledger-mark--icon {
  width: auto; height: auto; background: none; clip-path: none;
  display: grid; place-items: center; color: var(--gold);
}
.ledger-mark--icon svg { display: block; width: 22px; height: 22px; }
.ledger-title h1 { font-family: var(--cond); font-weight: 700; font-size: 1.75rem; letter-spacing: .02em; line-height: 1; }
.ledger-meta {
  font-size: .66rem; letter-spacing: .16em; text-transform: uppercase; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* A season picker seated in the title row — the team hub's overlaid-select
   primitive (`.tb-pick`, further up) in ledger clothes. Gold because it is a
   control, condensed and near the h1's size so the row reads as one line. */
.tb-pick.ledger-pick { align-items: center; color: var(--gold); }
.ledger-pick-year {
  font-family: var(--cond); font-weight: 700; font-size: 1.45rem; line-height: 1;
  letter-spacing: .02em; font-variant-numeric: tabular-nums;
}
.panel--flush { padding-top: 14px; }

/* --- the filter bar (conference index tabs) --- */
/* One line that scrolls, never a grid that wraps — the same behaviour as the
   schedule's filter strip and the week rail, so every crest row on the site
   moves the same way. `min-width: 0` down the chain is what lets the tabs
   shrink inside the ledger head instead of forcing it to wrap. */
.cfilter-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: nowrap;
  flex: 1 1 auto; min-width: 0; justify-content: flex-end;
}
.cfilter-hint { font-size: .6rem; letter-spacing: .18em; text-transform: uppercase; color: var(--muted); }
.cfilter-tabs {
  display: flex; gap: 3px; flex-wrap: nowrap; overflow-x: auto;
  min-width: 0; scrollbar-width: thin; padding-bottom: 4px;
}
.cfilter-tab {
  flex: none;
  min-width: 38px; height: 34px; display: inline-flex; align-items: center; justify-content: center;
  padding: 0 7px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 7px;
  cursor: pointer; transition: background .15s ease, border-color .15s ease, transform .15s ease;
}
.cfilter-tab img { max-width: 26px; max-height: 22px; object-fit: contain; filter: drop-shadow(0 0 1px rgba(255,255,255,.35)); }
.cfilter-tab-abbr { font-family: var(--cond); font-size: .64rem; font-weight: 700; color: var(--ink-dim); letter-spacing: .04em; }
.cfilter-tab:hover { background: var(--raised); }
.cfilter-tab.is-open { background: var(--raised); border-color: var(--gold-deep); transform: translateY(-1px); }
.cfilter-tab.has-selected { border-color: var(--gold-deep); box-shadow: inset 0 -2px 0 var(--gold); }
.cfilter-clear {
  font-family: var(--cond); font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--gold); background: none; border: 1px solid var(--gold-deep); border-radius: 6px;
  padding: 5px 11px; cursor: pointer; transition: background .15s ease;
}
.cfilter-clear:hover { background: rgba(214, 169, 78, .1); }

/* --- the drop-down conference roster (in flow; margin lives on the open panel
   so a closed filter adds no dead space above the table) --- */
.cfilter-panels { margin: 0; }
.cfilter-teams {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 10px 14px 6px; margin: 4px 0 18px;
}
/* One row of pills, scrolling — a whole conference wraps to three rows
   otherwise, and the roster shoves the table down the page. */
.cfilter-teams-grid {
  display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 6px;
  scrollbar-width: thin; padding-bottom: 4px;
}
.cfilter-team {
  flex: none;
  display: inline-flex; align-items: center; gap: 7px; padding: 5px 12px 5px 7px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px;
  cursor: pointer; transition: background .15s ease, border-color .15s ease;
}
.cfilter-team img { width: 20px; height: 20px; object-fit: contain; flex: none; }
.cfilter-team-name { font-size: .8rem; color: var(--ink-dim); white-space: nowrap; }
.cfilter-team:hover { background: var(--raised); }
.cfilter-team.is-selected {
  border-color: var(--team); background: color-mix(in srgb, var(--team) 24%, var(--surface-2));
}
.cfilter-team.is-selected .cfilter-team-name { color: var(--ink); font-weight: 600; }

/* --- dim/highlight: spotlight a program's cells down the years --- */
.record-table td[data-team] { transition: opacity .2s ease, filter .2s ease; }
.record-table.is-filtering td[data-team] { opacity: .13; filter: grayscale(.6); }
.record-table.is-filtering td[data-team].cell-hot {
  opacity: 1; filter: none;
  box-shadow: inset 0 0 0 1px rgba(214, 169, 78, .42); background: rgba(214, 169, 78, .07);
}

/* ---- responsive --------------------------------------------------------- */
@media (max-width: 900px) {
  .layout, .layout.is-collapsed { grid-template-columns: 1fr; }
  /* The sidebar used to unstick and sit at the top of the document here. It is
     `display: none` below this width now — see the MOBILE section at the end of
     this file, which owns the whole question of which navigation shows. */
  .main { padding: 20px 16px 48px; }
  .page-head h1 { font-size: 2.1rem; }
  .ledger-head { align-items: flex-start; }
  .ledger-title h1 { font-size: 1.45rem; }
  .lead-grid { grid-template-columns: 1fr; }
  .sgame-grid { grid-template-columns: 1fr; }
  /* The rail wraps under the panel name rather than squeezing the selects and
     the heading onto one line. */
  .panel-head--leaders .scope-rail { margin-left: 0; width: 100%; }
  .standings { grid-template-columns: 1fr; }
  .standings-page .standings { grid-template-columns: 1fr; }
  /* Narrow: the crest shrinks, the stat strip is on its own line (it is under
     1200px, so that is already true) and THE COACH STAYS IN THE TOP RIGHT — he
     wrapped onto a line of his own for one release and the user asked for the
     wide-screen arrangement back. He is smaller, not moved.

     His height stops deriving from --tb-row-h here, because that is `auto` at
     this width and `calc()` on `auto` is invalid. A literal keeps the bleed
     honest: whatever the identity line ends up being, the face is bottom-
     anchored in it and reaches --tb-stats-gap past it, onto the rule. */
  .team-banner { --tb-row-h: auto; padding: 16px 16px 0; }
  .tb-head { min-height: 92px; padding: 4px 0; gap: 0 14px; }
  .tb-titles { max-width: 100%; }
  .tb-crest { width: 56px; height: 56px; }
  .tb-coach { --tb-coach-h: 98px; gap: 10px; padding-left: 14px; }
  .tb-coach-name { font-size: 1rem; }
  .tb-coach-facts { font-size: .68rem; }
  .tb-coach-label { font-size: .55rem; }

  /* The tint goes HEAVIER: the five dials and the tab strip now sit on the
     photograph at every width, and small type on a photograph is the first
     thing to go. Same picture, more colour over it. */
  .tb-band.has-art::after {
    background:
      linear-gradient(to top,
                color-mix(in srgb, var(--team) 84%, transparent) 0%,
                color-mix(in srgb, var(--team) 46%, transparent) 40%,
                transparent 68%),
      linear-gradient(97deg,
                color-mix(in srgb, var(--team) 94%, transparent) 0%,
                color-mix(in srgb, var(--team) 84%, transparent) 34%,
                color-mix(in srgb, var(--team) 58%, transparent) 100%);
  }
  .tb-school { font-size: 1.7rem; }
  .tb-year { font-size: 1.25rem; }
  .tb-namebar { gap: 12px; min-height: 34px; }
  .tb-pick--season { padding-left: 12px; }
  /* No minmax floor and no overflow-x any more: five rings and two short
     labels fit the width, which is what the dials were for. A scrollable strip
     inside a banner is a scrollbar the user has to find. */
  .team-banner { --tb-dial: 42px; }
  .tb-stats { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px 10px; }
  .tb-stat-val { font-size: 1.15rem; }
  .tb-dial-val { font-size: 1rem; }

  /* THE PLAYER BANNER RE-MAPS RATHER THAN RESIZING, and at this width the
     re-map is only that the watermark stops owning a column of its own. The
     three lines stay beside the card — cropping the card to landscape is what
     made that fit, and the fact line is what fills the dead band the shorter
     name and rating lines were leaving under them.

     THE CHANNEL CLOSES BUT THE MARK DOES NOT GO. `--pb-mark-w: 0` and no
     reserved column, because at 600px the name needs every pixel; the mark
     stays as ground behind it at a lower opacity, which is exactly what the
     Team Hub's does on desktop. It was hidden outright here for one release and
     the user asked for it back.

     The card SHRINKS BUT STAYS LARGE. It is the page's subject, and the thing
     it replaced was a 54px thumbnail of a face. */
  /* --pb-pad-t moves WITH the padding. The watermark's clip is
     `pad-t + card-h` tall, so a literal padding here would leave the clip 4px
     past the card's bottom edge and the mark hanging under it. */
  .player-banner {
    --pb-card: 140px; --pb-mark-w: 0px; --pb-pad-t: 16px;
    padding: var(--pb-pad-t) 16px 0;
  }
  .pb-mark-clip { right: 4px; width: calc(var(--pb-card) * .95); }
  .pb-mark { opacity: .12; }
  .pb-head {
    padding: 2px 0; column-gap: 14px; row-gap: 8px;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      "card body"
      "card meta";
  }
  .pb-body { justify-content: center; gap: 4px; }
  .pb-name { font-size: 2.05rem; }
  .pb-namebar { min-height: 0; }
  .pb-class { font-size: 1.15rem; }
  .pb-rating { gap: 5px 13px; min-height: 0; }
  .pb-ovr-val { font-size: 1.85rem; }
  .pb-ovr--dev .pb-ovr-val { font-size: 1.2rem; }
  .pb-dev-ico { height: 1.9rem; }
  .pb-pos { font-size: 1.2rem; }
  .pb-jersey-art { height: 2.1rem; }
  /* The pills leave the rating line for one of their own beneath it — the slot
     the user picked. Same boxes, remapped by the wrap, no DOM change. */
  .pb-stints { flex-basis: 100%; }
  .pb-under { margin-top: 12px; padding-top: 12px; }
}
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
:focus-visible { outline: 2px solid var(--gold-lite); outline-offset: 2px; }

/* ---- admin / data flags -------------------------------------------------- */
.flag-intro { color: var(--muted); font-size: .9rem; margin-bottom: 18px; }
.flag-card { padding: 16px; margin-bottom: 26px; }

/* full-width screenshot, big enough to read the table directly */
.flag-shot-img { position: relative; display: block; line-height: 0; }
.flag-img { width: 100%; height: auto; border-radius: var(--radius-sm); border: 1px solid var(--line); display: block; background: #000; }
/* Stands in for a capture that is gone, so the card reads as a finding still
   waiting on an answer rather than as an image that failed to load. */
.flag-gone { color: var(--faint); font-style: italic; }
.flag-noimg {
  margin: 0; padding: 16px 18px; line-height: 1.5; font-size: .82rem;
  color: var(--muted); background: var(--surface-2);
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
}
.flag-rowhi { position: absolute; left: 0; right: 0; border: 2px solid var(--gold); background: rgba(214,169,78,.12); box-shadow: 0 0 8px 1px rgba(214,169,78,.5); border-radius: 3px; pointer-events: none; }
.flag-rowhi span { position: absolute; left: -12px; top: 50%; transform: translateY(-50%); width: 22px; height: 22px; border-radius: 50%; background: var(--gold); color: #14100a; font-weight: 700; font-size: .78rem; line-height: 0; display: grid; place-items: center; }

.flag-bar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 12px 2px 14px; }
.flag-screen { font-family: var(--cond); text-transform: uppercase; letter-spacing: .1em; color: var(--gold); font-weight: 600; font-size: .82rem; }
.flag-file { color: var(--faint); font-size: .74rem; word-break: break-all; }
.flag-fulllink { font-size: .76rem; color: var(--muted); }
.flag-fulllink:hover { color: var(--gold-lite); }
.flag-bar-discard { display: flex; align-items: center; gap: 6px; }
/* The editor page's whole-game drop, kept visually clear of the save form. */
.be-drop { margin-top: 18px; justify-content: flex-end; }

/* The card's own answers, in one row with the problems' — right-aligned so
   they line up under each problem's Save/Discard rather than starting a new
   column of their own. */
.flag-card-foot {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 10px; flex-wrap: wrap; margin-top: 12px;
}
/* An answer that exists for this screen type but has no valid target here.
   Shown rather than dropped so the card still says all its answers out loud;
   the "?" beside it says why this one is not on offer. */
.btn:disabled, .btn.is-disabled {
  opacity: .38; cursor: not-allowed; filter: grayscale(1);
}
.btn:disabled:hover, .btn.is-disabled:hover { background: transparent; border-color: var(--line); }

/* Help affordance: one "?" per button, carrying that button's own sentence.
   Focusable so it is reachable without a mouse, and title-based so it needs
   no JS — the page has to work with scripting off. */
.help-dot {
  display: inline-grid; place-items: center; width: 17px; height: 17px;
  border-radius: 50%; border: 1px solid var(--line); background: var(--surface-2);
  color: var(--muted); font-size: .68rem; font-weight: 700; line-height: 1;
  cursor: help; flex: 0 0 auto; user-select: none;
}
.help-dot:hover, .help-dot:focus { color: var(--gold-lite); border-color: var(--gold-deep); outline: none; }

/* stacked review boxes below the screenshot */
.flag-boxes { display: flex; flex-direction: column; gap: 14px; }
.flag-box { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 14px 16px; background: var(--surface-2); }
/* Marked as a batched "Save all" works down the page, so it can be watched.
   Both states are replaced by the reload that follows, not read afterwards. */
.flag-box.is-saved { opacity: .45; border-color: var(--gold-deep); }
.flag-box.is-refused { border-color: rgba(255,120,120,.5); }
.flag-box-head { display: flex; align-items: center; gap: 8px; font-size: .82rem; color: var(--ink-dim); margin-bottom: 12px; }
.flag-num { display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: var(--gold); color: #14100a; font-weight: 700; font-size: .72rem; flex: 0 0 auto; }
.flag-rowlab { font-family: var(--cond); text-transform: uppercase; letter-spacing: .06em; font-size: .7rem; color: var(--gold-lite); }
.flag-ocr { color: var(--muted); }
.flag-ocr b { color: var(--ink); }
/* Box-score review: what the OCR read, above the one choice being asked for. */
.flag-readout { font-size: .82rem; line-height: 1.45; margin-bottom: 12px; }
.flag-hint { display: block; margin-top: 4px; font-size: .76rem; color: var(--ink-dim); }
/* Two readings of one row, side by side — only the columns that disagree. The
   stored one is marked, because "which of these is in the database" is the
   whole question the card exists to answer. */
.flag-diff { border-collapse: collapse; margin-bottom: 6px; font-variant-numeric: tabular-nums; }
.flag-diff th, .flag-diff td { padding: 4px 12px 4px 0; text-align: left; font-weight: 400; }
.flag-diff thead th {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .06em;
  font-size: .66rem; color: var(--muted); border-bottom: 1px solid var(--line);
  padding-bottom: 5px;
}
.flag-diff tbody th {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .06em;
  font-size: .68rem; color: var(--gold-lite);
}
.flag-diff td { font-size: .9rem; color: var(--muted); }
.flag-diff .is-kept { color: var(--ink); font-weight: 600; }
.flag-diff thead .is-kept { color: var(--gold-lite); font-weight: 400; }
/* A withheld stat line, set as the game's own table row: same column order,
   same tight numeric cells, so it reads straight off the screenshot above it.
   Cells stay narrow and never wrap — a stat line that folded onto two rows
   would be exactly as hard to check as the screenshot it is replacing. */
.ff-statrow {
  gap: 0; flex-wrap: nowrap; overflow-x: auto; align-items: stretch;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, .18); padding: 2px;
}
.ff-statrow .ff-lab {
  flex: 0 0 auto; width: 58px; gap: 0; align-items: stretch; text-align: center;
  font-family: var(--cond); font-size: .6rem; letter-spacing: .07em;
  color: var(--gold-lite); padding: 5px 0 0;
}
.ff-statrow .ff-lab + .ff-lab { border-left: 1px solid var(--line); }
.ff-stat {
  border: 0; background: none; border-radius: 0; text-align: center;
  padding: 3px 2px 5px; font-size: .95rem; font-variant-numeric: tabular-nums;
}
.ff-stat:focus { outline: none; background: rgba(212, 175, 90, .12); }
/* The disputed cell of a page-conflict row: the input holds the stored read,
   the small line under it what the other shot said. */
.ff-statrow .ff-lab.is-conflict { color: #d98a66; }
.ff-stat.is-conflict { background: rgba(255, 120, 120, .12); }
.ff-alt { font-size: .62rem; color: #d98a66; letter-spacing: .04em;
          padding: 1px 0 4px; text-transform: none; }
/* The NAME cell of that row — sized to a name, not to the card. */
.ff-lab.ff-player { flex: 0 1 320px; }
.ff-choice { flex-direction: column; align-items: flex-start; gap: 4px; }
.ff-choice .ff-lab.ff-check { padding-bottom: 0; }

.ff-row { display: flex; gap: 10px; align-items: flex-end; margin-bottom: 10px; flex-wrap: wrap; }
.ff-matchup { align-items: flex-end; }
.ff-lab { display: flex; flex-direction: column; gap: 4px; font-size: .68rem; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); }
.ff-lab.ff-grow { flex: 1 1 200px; }
.ff-lab.ff-nolab { justify-content: flex-end; }
.ff-lab.ff-check { flex-direction: row; align-items: center; gap: 6px; text-transform: none; letter-spacing: 0; font-size: .82rem; color: var(--ink-dim); padding-bottom: 8px; }
.ff { background: var(--bg); color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px 10px; font-size: .9rem; font-family: var(--sans); width: 100%; }
.ff:focus { outline: none; border-color: var(--gold-deep); }
.ff-team { min-width: 150px; }
.ff-num { width: 78px; }
.ff-pts { width: 70px; }
.ff-sm { width: 68px; }
.ff-xs { width: 56px; }

/* footer: error notes on the left, actions on the bottom-right */
.flag-box-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-top: 4px; flex-wrap: wrap; }
.flag-box-errs { display: flex; flex-direction: column; gap: 5px; flex: 1 1 260px; min-width: 0; }
.flag-note { font-size: .78rem; color: var(--ink-dim); line-height: 1.35; }
.flag-reason { display: inline-block; font-family: var(--cond); text-transform: uppercase; letter-spacing: .06em; font-size: .66rem; color: #ffb0b0; background: rgba(255,120,120,.1); border: 1px solid rgba(255,120,120,.25); padding: 1px 6px; border-radius: 5px; margin-right: 4px; }
/* Wraps because a season-stats mismatch offers three answers, and the widest
   of them names a team and a category ("Delete Air Force defence"). */
.flag-box-actions { display: flex; gap: 10px; align-items: center; flex: 0 1 auto;
                    flex-wrap: wrap; justify-content: flex-end; }

.btn { padding: 9px 16px; border-radius: var(--radius-sm); border: 1px solid var(--line); font-size: .85rem; font-weight: 600; font-family: var(--sans); cursor: pointer; background: var(--raised); color: var(--ink); }
.btn:hover { border-color: var(--faint); }
.btn-gold { background: var(--gold); color: #14100a; border: none; font-weight: 700; }
.btn-gold:hover { background: var(--gold-lite); }
.btn-ghost { background: transparent; color: var(--muted); }
.btn-ghost:hover { color: var(--ink-dim); }
.btn-danger { background: transparent; color: #ff9a9a; border-color: rgba(255,120,120,.3); font-size: .78rem; padding: 7px 12px; }
.btn-danger:hover { background: rgba(255,120,120,.1); border-color: rgba(255,120,120,.5); }

/* ============================================================================
   Data Tracker (admin) — capture coverage per league season.
   One status vocabulary shared by week cells and season screens:
   missing / schedule / partial / final / blocked, carried on --trk.
   ========================================================================== */
.trk-intro { color: var(--ink-dim); font-size: .9rem; max-width: 68ch; margin: 0 0 14px; line-height: 1.5; }
.trk-intro code { font-family: var(--cond); font-size: .95em; color: var(--gold-lite); background: var(--surface-2); border-radius: 4px; padding: 1px 5px; }

.trk-dbbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 22px; }
.trk-dbchip { display: inline-flex; align-items: center; gap: 8px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 6px 12px; font-size: .8rem; color: var(--ink-dim); font-family: var(--cond); letter-spacing: .02em; max-width: 100%; overflow-wrap: anywhere; }
.trk-dbchip-k { font-size: .66rem; text-transform: uppercase; letter-spacing: .12em; color: var(--muted); }
.trk-flagchip { font-size: .78rem; color: #ffb0b0; background: rgba(255,120,120,.1); border: 1px solid rgba(255,120,120,.25); border-radius: var(--radius-sm); padding: 6px 12px; }
.trk-flagchip:hover { background: rgba(255,120,120,.18); }

.trk-season { margin-bottom: 34px; }
.trk-season-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 2px solid var(--gold-deep); }
.trk-season-head h2 { font-family: var(--cond); font-size: 1.9rem; letter-spacing: .02em; margin: 0; }
.trk-season-pct { font-size: .74rem; text-transform: uppercase; letter-spacing: .12em; color: var(--muted); }

/* progress meter, seated on the right of a panel head */
.trk-meter { display: flex; align-items: center; gap: 9px; margin-left: auto; flex: 0 0 auto; }
.trk-meter-bar { width: 108px; height: 5px; background: var(--surface-2); border-radius: 3px; overflow: hidden; }
.trk-meter-bar span { display: block; height: 100%; background: var(--gold); border-radius: 3px; }
.trk-meter-num { font-family: var(--cond); font-size: .95rem; color: var(--ink-dim); }
.trk-meter-num i { font-style: normal; color: var(--faint); }

/* week grid */
.trk-weeks { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; }
.trk-weeks--post { margin-top: 8px; }
.trk-cell { --trk: var(--line); display: flex; flex-direction: column; gap: 2px; padding: 9px 10px; border-radius: var(--radius-sm); background: var(--surface-2); border: 1px solid var(--line); border-left: 3px solid var(--trk); }
.trk-cell-label { font-family: var(--cond); font-size: .78rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
.trk-cell-num { font-family: var(--cond); font-size: 1.35rem; line-height: 1.1; color: var(--ink); }
.trk-cell-sub { font-size: .68rem; color: var(--faint); }
.trk-cell--final    { --trk: #4ea36b; }
.trk-cell--final .trk-cell-sub { color: #7cc294; }
.trk-cell--partial  { --trk: var(--gold); }
.trk-cell--partial .trk-cell-sub { color: var(--gold-lite); }
.trk-cell--schedule { --trk: #5b86c4; }
.trk-cell--missing  { --trk: var(--line); background: transparent; }
.trk-cell--missing .trk-cell-num { color: var(--faint); }
.trk-cell--blocked  { --trk: transparent; background: transparent; border-style: dashed; }
.trk-cell--blocked .trk-cell-num, .trk-cell--blocked .trk-cell-sub { color: var(--faint); }
/* Box-score coverage, inside a week cell. Two segments in one track: captured,
   then held-for-review. What is left dark is what a capture run would go and
   get, so the empty part of the bar is the actual work list. */
.trk-box { display: flex; align-items: center; gap: 5px; margin-top: 3px; }
.trk-box-bar { flex: 1; display: flex; height: 3px; min-width: 0;
  background: var(--line); border-radius: 2px; overflow: hidden; }
.trk-box-bar i { display: block; height: 100%; }
.trk-box-done { background: #4ea36b; }
.trk-box-held { background: var(--gold); }
.trk-box-pct { font-family: var(--cond); font-size: .68rem; color: var(--faint);
  line-height: 1; }

.trk-foot { font-size: .76rem; color: var(--muted); margin: 10px 0 0; }

/* Week cells are buttons now — they open the week's ledger. */
.trk-cell { font: inherit; text-align: left; cursor: pointer; appearance: none;
  transition: border-color .15s, background .15s; }
.trk-cell:hover { border-color: var(--gold-deep); }
.trk-cell:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.trk-cell.is-open { background: var(--surface-2); border-color: var(--gold);
  box-shadow: 0 0 0 1px var(--gold-deep) inset; }

/* Season tabs, only shown once there is more than one season to pick. */
.trk-years { display: flex; gap: 4px; flex-wrap: wrap; }
.trk-year { font-family: var(--cond); font-size: 1.05rem; letter-spacing: .04em;
  padding: 3px 12px; border-radius: 20px; border: 1px solid var(--line);
  color: var(--muted); text-decoration: none; }
.trk-year:hover { color: var(--ink); border-color: var(--gold-deep); }
.trk-year.is-on { color: #14100a; background: var(--gold); border-color: var(--gold); font-weight: 700; }

.trk-unplaced { font-size: .82rem; color: var(--ink-dim); background: var(--surface-2);
  border: 1px solid var(--line); border-left: 3px solid var(--gold);
  border-radius: var(--radius-sm); padding: 9px 12px; margin: 0 0 14px; }
.trk-unplaced a { color: var(--gold-lite); }

/* ---- one week's ledger --------------------------------------------------
   A box score is made of specific screens, so the ledger shows those screens
   as marks in the order the walk takes them: team stats, line score, then
   passing / rushing / receiving for each side. Reading a row tells you what
   to go back and capture. */
.trk-weekpanel { margin-top: 12px; }
.wl { border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2); overflow: hidden; }
.wl-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; border-bottom: 1px solid var(--line); }
.wl-head h3 { font-size: 1.1rem; letter-spacing: .04em; text-transform: uppercase; }
.wl-sum { font-size: .82rem; color: var(--ink-dim); }
.wl-sum b { color: var(--ink); }
/* A gap in `#` is a slot kept for a row still in review, not a numbering bug. */
.wl-heldslots { font-size: .74rem; color: var(--gold-lite);
                border: 1px solid var(--line); border-radius: 3px; padding: 1px 6px; }
.wl-legend { margin-left: auto; display: flex; align-items: center; gap: 5px;
  font-size: .7rem; color: var(--muted); }
.wl-legend .wl-slot { margin-left: 6px; }
.wl-loading, .wl-empty { padding: 16px; font-size: .84rem; color: var(--muted); margin: 0; }

.wl-table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.wl-table th { text-align: left; padding: 7px 14px; font-family: var(--cond);
  font-size: .68rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--muted); border-bottom: 1px solid var(--line); font-weight: 400; }
.wl-table td { padding: 6px 14px; border-bottom: 1px solid var(--line-soft);
  vertical-align: middle; }
.wl-table tr:last-child td { border-bottom: 0; }
.wl-n { width: 62px; color: var(--faint); font-variant-numeric: tabular-nums; }
.wl-colkeys { display: none; }
/* A game still needing work reads brighter than one that's finished. This used
   to hang off the matchup line; with that column gone it moves to the team
   names, which are what the eye scans down now. */
.wl-row.is-done { color: var(--muted); }
.wl-row.is-done .wl-team-name { color: var(--muted); }
.wl-row.is-none .wl-team-name, .wl-row.is-gaps .wl-team-name { color: var(--ink); }
.wl-row:hover td, .wl-row:hover + .wl-flagrow td { background: rgba(255,255,255,.02); }
/* The findings line belongs to the game above it, so it carries no border of
   its own and the game's bottom border is suppressed -- the two read as one. */
.wl-row:has(+ .wl-flagrow) td { border-bottom: 0; }
.wl-flagrow td { padding-top: 0; }
.wl-flagline { display: block; }
.wl-flag { display: inline-block; font-size: .64rem; font-family: var(--cond);
  text-transform: uppercase; letter-spacing: .05em; color: var(--gold-lite);
  border: 1px solid rgba(240,205,132,.3); border-radius: 4px;
  padding: 0 5px; margin-right: 4px; text-decoration: none; }
.wl-flag:hover { background: rgba(240,205,132,.12); }

.wl-side { display: flex; align-items: center; gap: 8px; }
.wl-side.is-fcs .wl-team-name { color: var(--faint); font-style: italic; }
.wl-team { display: inline-flex; align-items: center; gap: 6px; min-width: 0; flex: 1 1 auto; }
.wl-team img { width: 18px; height: 18px; object-fit: contain; flex: 0 0 auto; }
.wl-team-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wl-pts { color: var(--faint); font-variant-numeric: tabular-nums; }
.wl-slots { display: inline-flex; gap: 3px; flex: 0 0 auto; }
.wl-slot { font-family: var(--cond); font-size: .62rem; letter-spacing: .06em;
  text-transform: uppercase; padding: 1px 5px; border-radius: 4px;
  border: 1px solid transparent; min-width: 34px; text-align: center; }
.wl-slot.is-in { color: #9ad8b0; background: rgba(78,163,107,.16); border-color: rgba(78,163,107,.4); }
.wl-slot.is-held { color: var(--gold-lite); background: rgba(214,169,78,.14); border-color: rgba(214,169,78,.45); }
.wl-slot.is-missing { color: #e79a9a; background: rgba(255,120,120,.08); border-color: rgba(255,120,120,.3); }
.wl-slot.is-na { color: var(--faint); border-color: var(--line-soft); border-style: dashed; }

@media (max-width: 900px) {
  .wl-table th.wl-n, .wl-table td.wl-n { display: none; }
  .wl-side { flex-wrap: wrap; }
}

/* season-level screens */
.trk-cats { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
/* Fourth column holds the Clear control; it is `auto` so a row without one
   gives the space back to the detail rather than leaving a gap. */
.trk-cat { display: grid; grid-template-columns: 10px 190px 1fr auto; align-items: baseline; gap: 10px; padding: 9px 4px; border-bottom: 1px solid var(--line-soft); }
.trk-cat-clear { grid-column: 4; grid-row: 1; align-self: center; }
/* The two bulk schedule deletes. The season one stands alone under the week
   grids rather than beside a heading, so it needs its own spacing; the week one
   follows a legend that already claims the row's spare width. */
.trk-wipe { margin-top: 10px; }
.wl-wipe { margin-left: 8px; }
.trk-cat:last-child { border-bottom: none; }
.trk-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--line); align-self: center; }
.trk-cat--final .trk-dot { background: #4ea36b; }
.trk-cat--partial .trk-dot { background: var(--gold); }
.trk-cat-state { font-size: .74rem; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-dim); align-self: center; }
.trk-cat--partial .trk-cat-state { color: var(--gold-lite); }
.trk-cat-label { font-family: var(--cond); font-size: 1.02rem; letter-spacing: .02em; }
.trk-cat--missing .trk-cat-label { color: var(--ink-dim); }
.trk-cat-detail { font-size: .84rem; color: var(--ink-dim); }
.trk-cat--missing .trk-cat-detail { color: var(--muted); }
.trk-cat-note { grid-column: 3 / -1; font-size: .74rem; color: var(--faint); line-height: 1.4; }

/* ---- All-Americans coverage grid ----------------------------------------
   12 groups x 3 tiers, one cell per page the capture walk shoots. The cell
   carries two numbers on purpose — what is in, and what is still open — since
   "18/25" and "7 open" answer different questions and the second is the one
   that decides whether to go back to the console. */
.trk-aa { width: 100%; border-collapse: collapse; }
.trk-aa thead th {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .06em;
  font-size: .68rem; color: var(--muted); font-weight: 400; text-align: left;
  border-bottom: 1px solid var(--line); padding: 0 10px 6px 0;
}
.trk-aa tbody th {
  font-family: var(--cond); font-size: .92rem; font-weight: 400; text-align: left;
  color: var(--ink-dim); padding: 6px 12px 6px 0; white-space: nowrap;
}
.trk-aa td { padding: 4px 10px 4px 0; }
.trk-aa tbody tr { border-bottom: 1px solid var(--line-soft); }
.trk-aa tbody tr:last-child { border-bottom: none; }

.trk-aacell {
  border-left: 3px solid var(--line); padding-left: 9px; line-height: 1.35;
  min-width: 116px;
}
.trk-aa-n { font-variant-numeric: tabular-nums; font-size: .92rem; color: var(--ink); }
.trk-aa-n i { font-style: normal; color: var(--faint); font-size: .78rem; }
.trk-aa-tag { display: block; font-size: .7rem; color: var(--muted); }
.trk-aa-held {
  display: inline-block; margin-top: 2px; font-family: var(--cond);
  font-size: .64rem; text-transform: uppercase; letter-spacing: .07em;
  color: var(--gold-lite); border: 1px solid var(--gold-deep);
  border-radius: 4px; padding: 0 5px; text-decoration: none;
}
.trk-aa-held:hover { background: rgba(212, 175, 90, .14); }

/* Green only for a page that is genuinely done: full AND nothing in review. */
.trk-aacell--final { border-left-color: #4ea36b; }
.trk-aacell--final .trk-aa-n { color: var(--ink); }
.trk-aacell--partial { border-left-color: var(--gold); }
.trk-aacell--held { border-left-color: var(--gold); background: rgba(214, 169, 78, .07); }
/* A page with nothing at all is the one this section exists to surface. */
.trk-aacell--missing { border-left-color: #c9694e; background: rgba(201, 105, 78, .09); }
.trk-aacell--missing .trk-aa-n { color: var(--muted); }
.trk-aacell--missing .trk-aa-tag { color: #d98a66; }
.trk-aa-tot { text-align: right; font-variant-numeric: tabular-nums;
              color: var(--ink-dim); font-size: .88rem; padding-right: 0; }

/* re-capture call-out — the incomplete rosters, worst first */
.trk-recapture { margin: 0 0 10px; font-size: .8rem; color: var(--ink-dim); line-height: 2; }
.trk-recapture strong { font-family: var(--cond); letter-spacing: .04em; color: var(--gold); font-weight: 500; }
.trk-recapture-rule { font-size: .72rem; color: var(--faint); margin-right: 4px; }
.trk-recapture-team {
  display: inline-flex; align-items: baseline; gap: 5px; margin-right: 4px; padding: 2px 8px;
  border: 1px solid var(--gold-deep); border-radius: 20px; color: var(--ink-dim); text-decoration: none;
}
.trk-recapture-team:hover { border-color: var(--gold); color: var(--ink); }
.trk-recapture-team span { font-size: .72rem; color: var(--gold); font-variant-numeric: tabular-nums; }

/* roster crest grid — complete lit, partial flagged, missing ghosted */
.trk-teams { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 6px; }
.trk-team { display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-radius: var(--radius-sm); background: var(--surface-2); border: 1px solid var(--line); min-width: 0; }
.trk-team img { width: 22px; height: 22px; object-fit: contain; flex: none; }
.trk-team-name { font-size: .8rem; color: var(--ink-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.trk-team.is-missing { background: transparent; border-color: var(--line-soft); }
.trk-team.is-missing img { opacity: .18; filter: grayscale(1); }
.trk-team.is-missing .trk-team-name { color: var(--faint); }
.trk-team.is-partial { border-color: var(--gold-deep); }
.trk-team.is-partial img { opacity: .55; }
/* Held outranks the count: a roster with findings in the review queue is not a
   finished capture, and the cell links at the findings instead of the editor.
   Gold, the same treatment every other held state on this page gets. */
.trk-team.is-held { border-color: var(--gold); background: rgba(214, 169, 78, .07); text-decoration: none; cursor: pointer; }
.trk-team.is-held .trk-team-name { color: var(--gold-lite); }
.trk-team-count {
  margin-left: auto; flex: none; font-size: .7rem; color: var(--gold);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   ADMIN — CAPTURE (local only)
   Launch the capture scripts + OCR ingest and watch them run.
   ============================================================ */
.cap-error { background: rgba(255,120,120,.1); border: 1px solid rgba(255,120,120,.35); color: #ffb0b0; padding: 10px 14px; border-radius: var(--radius-sm); font-size: .86rem; margin-bottom: 16px; }
.cap-notice { background: rgba(78,163,107,.12); border: 1px solid rgba(78,163,107,.4); color: #9ad8b0; padding: 10px 14px; border-radius: var(--radius-sm); font-size: .86rem; margin-bottom: 16px; }

/* Review queue: filter by what went wrong, then clear a whole kind at once. */
.flag-filter { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 14px; }
.flag-chip { display: inline-flex; align-items: center; gap: 6px; font-family: var(--cond);
  font-size: .74rem; text-transform: uppercase; letter-spacing: .06em;
  padding: 3px 10px; border-radius: 20px; border: 1px solid var(--line);
  color: var(--muted); text-decoration: none; }
.flag-chip b { color: var(--ink); font-weight: 600; }
.flag-chip:hover { border-color: var(--gold-deep); color: var(--ink-dim); }
.flag-chip.is-on { background: var(--gold); border-color: var(--gold); color: #14100a; }
.flag-chip.is-on b { color: #14100a; }
.flag-bulk { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: 16px; }
.flag-bulk .flag-intro { margin: 0; flex: 1 1 320px; }
.flag-bulk form { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }

/* Screen chips — the same idiom as the reason chips above them, dashed so the
   two rows read as two different axes of one filter rather than one long one. */
.flag-filter-screens .flag-chip { border-style: dashed; }
.flag-filter-screens .flag-chip.is-on { border-style: solid; }

/* The team/week form: same field classes (.ff/.ff-lab) as every box below,
   just laid out as one compact row instead of a card. */
.flag-filter-form { align-items: flex-end; margin-bottom: 16px; }
.flag-filter-form .ff-lab { flex: 0 1 220px; }
.flag-filter-clear { align-self: center; font-size: .78rem; color: var(--muted); text-decoration: none; }
.flag-filter-clear:hover { color: var(--ink-dim); text-decoration: underline; }

.cap-intro { color: var(--muted); font-size: .84rem; line-height: 1.55; margin: 0 0 16px; max-width: 78ch; }
.cap-intro b { color: var(--ink-dim); }
.cap-intro code { font-size: .8rem; color: var(--ink-dim); }

/* live console */
.cap-console-panel .panel-head { flex-wrap: wrap; }
.cap-console-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.cap-stop-form { display: inline; }
.cap-status { font-family: var(--cond); text-transform: uppercase; letter-spacing: .1em; font-size: .72rem; color: var(--faint); border: 1px solid var(--line); border-radius: 20px; padding: 3px 10px; }
.cap-status.is-running { color: var(--gold); border-color: var(--gold-deep); animation: cap-pulse 1.6s ease-in-out infinite; }
.cap-status.is-ok { color: #9ad8a8; border-color: rgba(120,220,150,.3); }
.cap-status.is-bad { color: #ff9a9a; border-color: rgba(255,120,120,.35); }
@keyframes cap-pulse { 50% { opacity: .45; } }
.cap-cmd { font-family: ui-monospace, "Cascadia Code", Consolas, monospace; font-size: .74rem; color: var(--faint); word-break: break-all; margin: 0 0 8px; }
.cap-console {
  background: #0d1219; border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 14px 16px; margin: 0; max-height: 420px; overflow: auto;
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: .78rem; line-height: 1.5; color: var(--ink-dim);
  white-space: pre-wrap; word-break: break-word;
}

/* capture script cards */
/* Cards size to their own content. Stretching them to the tallest sibling is
   the grid default, and with Box Scores carrying five fields it left the other
   three cards over half empty. */
.cap-screens { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; align-items: start; }
.cap-screen { display: flex; flex-direction: column; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2); padding: 13px 15px; }
.cap-screen-head { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.cap-screen-head h3 { font-size: 1.05rem; letter-spacing: .02em; }
.cap-tag { font-family: var(--cond); text-transform: uppercase; letter-spacing: .08em; font-size: .62rem; color: var(--faint); border: 1px solid var(--line); border-radius: 20px; padding: 2px 7px; }
.cap-blurb { font-size: .8rem; color: var(--ink-dim); line-height: 1.45; margin: 0 0 9px; }
.cap-warn { font-size: .74rem; line-height: 1.45; margin: 0 0 9px; color: #e8c98a; }

/* The pre-flight line: where the console must already be. Gold keyline because
   ignoring it costs the whole run, and the caret is the row highlight you are
   being told to park at the top of the list. */
.cap-start {
  display: flex; gap: 8px; align-items: baseline;
  font-size: .75rem; line-height: 1.4; color: var(--ink-dim);
  margin: 0 0 9px; padding: 6px 9px 6px 8px;
  border-left: 2px solid var(--gold-deep);
  background: rgba(214, 169, 78, .05);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.cap-start-k {
  flex: none; font-family: var(--cond); text-transform: uppercase;
  letter-spacing: .1em; font-size: .62rem; color: var(--gold); padding-top: 1px;
}
.cap-start-k::before { content: "▸ "; }

/* Caveats that decide whether a run is trustworthy afterwards — kept on the
   page, folded away so they cost no height until asked for. */
.cap-more { margin: 0 0 9px; }
.cap-more summary {
  cursor: pointer; list-style: none; width: fit-content;
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .1em;
  font-size: .64rem; color: var(--muted);
  border-bottom: 1px dotted var(--line); padding-bottom: 1px;
}
.cap-more summary::-webkit-details-marker { display: none; }
.cap-more summary::after { content: " +"; color: var(--faint); }
.cap-more[open] summary::after { content: " −"; }
.cap-more summary:hover { color: var(--ink-dim); }
.cap-more p { font-size: .74rem; line-height: 1.5; color: var(--muted); margin: 8px 0 0; }

.cap-screen-foot { display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; margin-top: auto; padding-top: 8px; flex-wrap: wrap; }
.cap-args { margin-bottom: 0; flex: 1 1 auto; align-items: flex-start; row-gap: 10px; }
.cap-args--checks { gap: 22px; margin-top: 4px; }
/* Positioned so a tip anchors to the FIELD rather than to the marker: a tip is
   never wider than the field box, so it cannot push page width and give the
   whole layout a horizontal scrollbar even while hidden. */
.cap-arg { position: relative; flex: 0 1 auto; max-width: 260px; }
.cap-arg--check { display: flex; align-items: center; gap: 6px; padding-bottom: 0; }
.cap-arg-lab { display: inline-flex; align-items: center; gap: 5px; }
.cap-run { flex: 0 0 auto; }
.cap-run:disabled { opacity: .4; cursor: not-allowed; }

/* ---- field hints -------------------------------------------------------- */
/* Reference text, off the layout entirely: the tip is absolutely positioned so
   opening one never moves a field, a card, or the cards beside it. */
.cap-hint { display: inline-flex; }
.cap-hint-btn {
  width: 14px; height: 14px; padding: 0; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-size: .6rem; font-weight: 600;
  color: var(--faint); background: transparent;
  border: 1px solid var(--line); border-radius: 50%;
  cursor: help; transition: color .12s ease, border-color .12s ease;
}
.cap-hint-btn:hover, .cap-hint-btn:focus-visible { color: var(--gold); border-color: var(--gold-deep); }
.cap-tip {
  position: absolute; bottom: calc(100% + 7px); left: 0; z-index: 40;
  width: max-content; max-width: 250px;
  font-size: .72rem; line-height: 1.45; letter-spacing: 0; text-transform: none;
  color: var(--ink-dim); background: var(--raised);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 7px 10px; box-shadow: 0 8px 22px rgba(0, 0, 0, .5);
  opacity: 0; visibility: hidden; transform: translateY(3px);
  transition: opacity .12s ease, transform .12s ease, visibility .12s;
}
.cap-hint-btn:hover + .cap-tip,
.cap-hint-btn:focus-visible + .cap-tip {
  opacity: 1; visibility: visible; transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .cap-tip { transition: none; transform: none; }
}

/* ingest form */
.cap-ingest .ff-row { align-items: flex-start; }
.cap-ingest-foot { justify-content: flex-end; margin-top: 12px; }

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

/* The box-score link on a Team Hub schedule row: the result and score together,
   because the outcome is what you reach for when you want the detail behind it.
   A sibling of the opponent anchor, so both are real links. */
.sched-box {
  display: flex; align-items: center; gap: 10px; flex: none;
  padding: 2px 4px; margin: -2px -4px; border-radius: 5px;
}
.sched-box:hover { background: rgba(255,255,255,.06); }
.sched-box:hover .sched-score { color: var(--gold-lite); }
.sched-box:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }

.game-head { margin-bottom: 14px; }

/* The scoreboard: two team-coloured slabs flanking the big score — the shape
   of the game's own postgame screen. Gold is the only winner signal. */
.scoreboard {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: 14px; align-items: stretch;
  margin-bottom: 26px;
}
/* The period band, when there is one, sits close enough to read as one unit. */
.scoreboard:has(+ .ls-strip) { margin-bottom: 14px; }
.gh-side {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 22px 18px 18px; border-radius: var(--radius);
  background: linear-gradient(160deg,
              color-mix(in srgb, var(--team) 42%, var(--surface)) 0%,
              color-mix(in srgb, var(--team) 16%, var(--surface)) 100%);
  border: 1px solid color-mix(in srgb, var(--team) 34%, var(--line));
  text-align: center;
}
/* The slab's own content rides above the watermark. */
.gh-side > * { position: relative; z-index: 1; }
/* The SECONDARY mark as texture behind the crest: oversized, faint, clipped
   by the slab, pushed toward the OUTER edge so the two marks frame the score
   rather than crowd it — the Team Hub banner idiom. */
.gh-wm {
  position: absolute; z-index: 0; width: 115%; max-width: none;
  top: 50%; transform: translateY(-50%); opacity: .09; pointer-events: none;
}
.scoreboard .gh-side:first-child .gh-wm { left: -28%; }
.scoreboard .gh-side:last-child .gh-wm { right: -28%; }
/* The AP rank carried INTO the game, in the slab's outer top corner. */
.gh-rank {
  position: absolute; top: 10px;
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%;
  border: 2px solid var(--gold);
  font-family: var(--cond); font-weight: 700; font-size: .82rem;
  font-variant-numeric: tabular-nums;
}
.scoreboard .gh-side:first-child .gh-rank { left: 12px; }
.scoreboard .gh-side:last-child .gh-rank { right: 12px; }
.gh-crest {
  width: clamp(72px, 9vw, 118px); height: auto;
  filter: drop-shadow(0 4px 10px rgba(0,0,0,.35));
}
.gh-name {
  font-family: var(--cond); font-weight: 700; font-size: 1.55rem;
  letter-spacing: .04em; text-transform: uppercase; line-height: 1.1;
}
a.gh-name:hover { color: var(--gold-lite); }
/* The record THROUGH this game — the postgame screen's own line. */
.gh-record {
  font-family: var(--cond); font-weight: 600; font-size: .84rem;
  letter-spacing: .08em; color: var(--ink-dim); font-variant-numeric: tabular-nums;
}

.scoreboard-mid {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 14px 10px;
}
/* Each side's score over its helmet; the helmets face each other. */
.sb-cols { display: flex; align-items: flex-start; gap: 44px; }
.sb-col { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.sb-score {
  font-family: var(--cond); font-weight: 700; line-height: 1;
  font-size: clamp(2.4rem, 4.5vw, 3.6rem); font-variant-numeric: tabular-nums;
  color: var(--ink-dim);
}
.sb-score.is-winner { color: var(--gold); }
.sb-helmet { flex: none; width: 118px; height: 118px; object-fit: contain; }
.sb-helmet--logo { width: 88px; height: 88px; }
.sb-helmet--none { background: var(--raised); border-radius: 50%; width: 72px; height: 72px; }
.sb-bowl { width: 52px; height: 52px; object-fit: contain; }
.sb-tag {
  font-family: var(--cond); font-size: .8rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted);
}

/* Score by period: full width under the header, the postgame screen's lower
   band. Only rendered when period rows exist (cfb27 captures). */
.ls-strip { margin-bottom: 26px; overflow-x: auto; }
/* Capped and centred: full-bleed the table just opens a field of nothing
   between the team names and the 1 column on a wide screen. The cap holds the
   quarters within reading distance of the names; overtime columns widen it
   toward the cap before the strip starts scrolling. */
.linescore {
  width: 100%; max-width: 720px; margin-inline: auto;
  border-collapse: collapse; font-variant-numeric: tabular-nums;
}
.linescore th, .linescore td {
  padding: 8px 14px; text-align: center; font-size: 1.05rem;
  border-bottom: 1px solid var(--line-soft);
}
.linescore thead th {
  font-family: var(--cond); font-weight: 600; font-size: .78rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); border-bottom: 1px solid var(--line);
}
.linescore .ls-corner { text-align: left; }
.linescore tbody tr:last-child th, .linescore tbody tr:last-child td { border-bottom: 0; }
.linescore .ls-team {
  width: 40%; text-align: left; font-family: var(--cond); font-weight: 600;
  font-size: .95rem; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink); white-space: nowrap;
}
.ls-crest { width: 20px; height: 20px; object-fit: contain; vertical-align: -4px; margin-right: 9px; }
/* Overtime reads as extra time, not another quarter. */
.linescore .ls-ot { background: rgba(214,169,78,.07); }
.linescore .ls-final {
  border-left: 1px solid var(--line); font-family: var(--cond); font-weight: 700;
  font-size: 1.12rem; color: var(--ink);
}
.linescore td.ls-final.is-winner { color: var(--gold); }

/* Kept for the admin box-score editor, which borrows it. */
.tcmp-wrap { overflow-x: auto; }

/* The sheet: away players | team stats | home players. Away/home blocks carry
   explicit inline grid-rows (row 1 is the MVP row when present) so a category
   starts on the same line on both sides; the centre panel spans every row. */
.box-layout {
  display: grid; grid-template-columns: 1fr minmax(230px, 320px) 1fr;
  gap: 18px; align-items: start;
}
.bl-away { grid-column: 1; }
/* min-width + clip: whatever the table's min-content comes to, nothing it
   paints (row rules included) may ever leave the panel. */
.bl-center { grid-column: 2; margin-bottom: 0; min-width: 0; overflow-x: clip; }
.bl-home { grid-column: 3; }
.bl-side {
  min-width: 0; overflow-x: auto;
  border-left: 3px solid var(--team); border-radius: 3px; padding-left: 12px;
}
/* Team stats alone (mid-capture): the centre column without empty flanks. */
.bl-solo { max-width: 420px; margin-inline: auto; }

/* The empty-state head (no team stats captured) still centres. */
.bl-center .panel-head { justify-content: center; }

/* Centre stats table: away value | label | home value. Fixed layout: the
   percentages resolve against the panel, never against the content, so a long
   cell cannot inflate the table (auto layout did — the rules bled out of the
   panel) and the two value columns split the remainder equally, keeping the
   label column dead centre whoever is playing.

   The title is the header row's middle cell, between the two crests, instead of
   a panel head floating over a row of truncated team names. Every stat is TWO
   rows: the values, then a bar row that carries the rule — so the bars read as
   the row's underline, the way the broadcast graphic draws them. */
.cst { width: 100%; border-collapse: collapse; table-layout: fixed; }
.cst th, .cst td { padding: 5px 6px 2px; }
.cst thead th {
  padding: 2px 4px 8px; border-bottom: 1px solid var(--line); vertical-align: middle;
  font-family: var(--cond); font-size: .78rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-dim); white-space: nowrap;
}
/* Crests at the OUTER edges — the values lean in toward the label, the crests
   frame the whole table. */
.cst thead th.cst-away { text-align: left; }
.cst thead th.cst-home { text-align: right; }
.cst-crest { width: 26px; height: 26px; object-fit: contain; vertical-align: middle; }
.cst th.cst-title {
  font-family: var(--cond); font-weight: 700; font-size: 1.05rem; letter-spacing: .04em;
  text-transform: none; color: var(--ink);
}
.cst-away { text-align: right; }
.cst-home { text-align: left; }
.cst td.cst-away, .cst td.cst-home {
  font-family: var(--cond); font-weight: 600; font-size: .98rem;
  font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink-dim);
}
.cst th.cst-label {
  width: 38%; text-align: center; font-family: var(--sans); font-weight: 500;
  font-size: .72rem; letter-spacing: .05em; color: var(--muted); text-transform: none;
}
/* The category winner is the brighter number. No arrow, no green: the bars
   under the row already say by how much. */
.cst td.is-fav { color: var(--ink); }
.cst-bars td { padding: 3px 6px 7px; border-bottom: 1px solid var(--line-soft); }
/* A flex DIV inside the cell, not a flex cell: a td that stops being a
   table-cell loses its colspan and the bar lands in the first column. */
.cst-bar-row { display: flex; gap: 6px; align-items: center; }
.cst tbody tr:last-child td { border-bottom: 0; }
/* Widths are inline percentages of the row's numbers; flex shrinks the pair
   by the gap so they never overrun. min-width keeps a shutout visible. */
.cst-bar {
  display: block; height: 3px; min-width: 3px; border-radius: 2px;
  background: var(--team);
}
.cst-bar.is-void { background: var(--line); }

/* The per-team MVP card, PPR-scored (league/fantasy.py). */
.mvp-card {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px;
  background: linear-gradient(135deg,
              color-mix(in srgb, var(--team) 22%, var(--surface)) 0%,
              var(--surface) 75%);
  border: 1px solid var(--line); border-radius: var(--radius);
}
/* The game-wide top scorer of the two: gold border, gold points. */
.mvp-card.is-top { border-color: var(--gold-deep); }
.mvp-head {
  position: relative; overflow: hidden;
  flex: none; width: 56px; height: 56px; border-radius: 50%;
  background: var(--raised);
  border: 2px solid color-mix(in srgb, var(--team) 55%, var(--line));
}
/* Portrait and team jersey overlay share the 512 canvas, so they take the
   SAME crop — a crop on one and not the other slides the collar off the neck. */
.mvp-head-img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 22%;
}
.mvp-head-jersey { z-index: 2; pointer-events: none; }
.mvp-head--disc {
  display: flex; align-items: center; justify-content: center;
  font-family: var(--cond); font-weight: 700; font-size: 1.05rem;
  color: var(--ink-dim);
  background: color-mix(in srgb, var(--team) 30%, var(--surface-2));
}
.mvp-info { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mvp-tag {
  font-family: var(--cond); font-weight: 700; font-size: .64rem;
  letter-spacing: .22em; text-transform: uppercase; color: var(--gold);
}
.mvp-name {
  font-family: var(--cond); font-weight: 700; font-size: 1.45rem; line-height: 1.05;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
a.mvp-name:hover { color: var(--gold-lite); }
.mvp-meta { font-size: .74rem; color: var(--muted); }

/* Player tables: one category block per side, aligned by the grid rows. The
   category label is the first header cell — it replaced a team name repeated
   down every column, and the label strip that used to sit above each table. */
.box-table { width: 100%; font-size: .82rem; }
/* Dense on purpose: names are already game-style initials and the headers
   three letters, so the padding is what decides how many columns a narrow
   screen shows before the block scrolls. */
.box-table th, .box-table td { padding: 4px 5px; }
.box-table th { letter-spacing: .08em; }
/* THE CATEGORY IS THE FIRST HEADER CELL — box score, Team Hub → Player Stats
   and Player → Stats all print PASSING / RUSHING / … where a team name, "Player"
   or "Yr" used to be, and none of the three carries a panel head any more. It
   stays a working header: on the two stat tabs the button under it is still
   the name / year sort. */
.record-table th.cat-th { font-size: .9rem; letter-spacing: .14em; }
.box-table th.cat-th { padding: 6px 8px 5px; }
.box-player { white-space: nowrap; }
.box-pos { margin-left: 5px; font-size: .7rem; color: var(--muted); }
.box-none { padding: 2px 0 0; }
.box-none .empty-state { padding: 6px 4px; }

/* Phone team switcher: hidden wider than 900px, where both columns are on
   screen and the control would only be a third thing to look at. It sits in
   the grid's DOM but `display: none` keeps it out of the placement. */
.box-switch { display: none; }
.box-switch-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-width: 0; padding: 9px 10px; border: 0; border-bottom: 3px solid transparent;
  border-radius: 6px; background: transparent; color: var(--muted); cursor: pointer;
  font-family: var(--cond); font-weight: 600; font-size: .92rem;
  letter-spacing: .08em; text-transform: uppercase; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; transition: background .14s, color .14s;
}
.box-switch-btn:hover { color: var(--ink-dim); }
/* The team's own colour marks the chosen side — the control's whole job is to
   say WHICH team, so gold (the site's generic "current") would be the wrong
   signal here. */
.box-switch-btn.is-on { background: var(--surface); color: var(--ink); border-bottom-color: var(--team); }
.box-switch-crest { width: 18px; height: 18px; object-fit: contain; flex: none; }

@media (max-width: 900px) {
  /* The header keeps its shape and condenses — slab | scores | slab still
     reads as one scoreboard on a phone. */
  .scoreboard { gap: 8px; }
  /* Block flow: the inline grid-rows go inert and DOM order takes over —
     MVPs, team stats, then away/home per category. */
  .box-layout { display: block; }
  .box-layout .bl-side, .box-layout .bl-center { margin-bottom: 16px; }
  .gh-side { padding: 14px 8px 12px; gap: 5px; }
  .gh-crest { width: 54px; }
  .gh-name { font-size: .95rem; }
  .gh-record { font-size: .68rem; letter-spacing: .05em; }
  .gh-rank { width: 21px; height: 21px; border-width: 1px; font-size: .68rem; top: 6px; }
  .scoreboard .gh-side:first-child .gh-rank { left: 6px; }
  .scoreboard .gh-side:last-child .gh-rank { right: 6px; }
  .scoreboard-mid { padding: 8px 2px; gap: 6px; }
  .sb-cols { gap: 16px; }
  .sb-score { font-size: 1.9rem; }
  .sb-helmet { width: 52px; height: 52px; }
  .sb-helmet--logo { width: 40px; height: 40px; }
  .sb-helmet--none { width: 36px; height: 36px; }
  .sb-bowl { width: 34px; height: 34px; }
  .linescore th, .linescore td { padding: 6px 8px; font-size: .92rem; }
  .linescore .ls-team { font-size: .82rem; }
  .ls-crest { width: 16px; height: 16px; vertical-align: -3px; margin-right: 6px; }
  /* Phone density: with initialed names and 3-letter headers, this is what
     gets a whole passing line on screen without a scroll. */
  .box-table { font-size: .78rem; }
  .box-table th, .box-table td { padding: 4px 4px; }
  .cst td.cst-away, .cst td.cst-home { font-size: .92rem; }
  /* One team at a time: the switcher shows, and the other side's category
     blocks leave the flow. MVP cards are not `.bl-cat` and stay for both. */
  .box-switch {
    display: grid; grid-template-columns: 1fr 1fr; gap: 3px; padding: 3px;
    background: var(--surface-2); border: 1px solid var(--line);
    border-radius: var(--radius-sm); margin-bottom: 16px;
  }
  .box-layout[data-show="away"] .bl-cat.bl-home,
  .box-layout[data-show="home"] .bl-cat.bl-away { display: none; }
}

/* ---- manual box-score editor (admin) ------------------------------------ */
.be-lede { color: var(--ink-dim); font-size: .86rem; margin: 0 0 12px; max-width: 70ch; }
.be-fail { border-color: rgba(224,1,34,.4); }
.be-findings { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.be-findings li { font-size: .84rem; color: var(--ink-dim); }
.be-sub {
  font-family: var(--cond); font-size: .82rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--gold); margin: 20px 0 10px;
}
.be-sub:first-of-type { margin-top: 8px; }
.be-side-head { align-items: center; gap: 10px; }
.be-logo { width: 26px; height: 26px; object-fit: contain; }

.be-cell {
  width: 100%; padding: 5px 7px; background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: 5px;
  font-family: var(--cond); font-size: .92rem; font-variant-numeric: tabular-nums;
  text-align: right;
}
.be-cell:focus { outline: none; border-color: var(--gold); }
.be-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.be-label {
  font-size: .64rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.be-teamgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(94px, 1fr)); gap: 10px;
}
.be-line td.num { padding: 4px 6px; }
.be-line .be-cell { width: 62px; }

.be-player-id {
  font-family: var(--cond); font-weight: 600; font-size: .95rem; margin-bottom: 6px;
}
.be-catrow { display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap; padding: 4px 0; }
.be-catname {
  flex: none; width: 78px; font-size: .68rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--gold-deep); padding-bottom: 6px;
}
.be-field--tight { width: 62px; }

.be-actions {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 16px 0 40px;
}
.be-actions-note { flex: 1 1 240px; font-size: .74rem; color: var(--faint); max-width: 56ch; }

@media (max-width: 700px) {
  .be-catname { width: 100%; padding-bottom: 0; }
}
.be-conf { margin-left: 8px; font-size: .7rem; color: var(--muted); }
.be-field--who { width: 210px; }
.be-pick {
  width: 100%; padding: 5px 7px; background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: 5px; font-size: .82rem;
}
.be-pick:focus { outline: none; border-color: var(--gold); }
/* Thumbnail strip: small enough to scan ten at once, each opening full size. */
.be-shots {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px;
}
.be-shot {
  position: relative; display: block; border: 1px solid var(--line);
  border-radius: 6px; overflow: hidden; background: var(--bg); line-height: 0;
}
.be-shot img { width: 100%; height: auto; display: block; opacity: .88; }
.be-shot:hover { border-color: var(--gold); }
.be-shot:hover img { opacity: 1; }
.be-shot:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.be-shot-n {
  position: absolute; top: 4px; left: 4px; min-width: 18px; padding: 1px 5px;
  border-radius: 4px; background: rgba(0,0,0,.72); color: var(--gold-lite);
  font-family: var(--cond); font-size: .72rem; line-height: 1.5; text-align: center;
}
.be-shots-note { margin: 10px 0 0; font-size: .76rem; color: var(--faint); }
.cap-sweep { display: inline-flex; margin-left: 6px; }

/* Player stats laid out per category: a header row of column names, then one
   aligned row per player, each led by the picker that says who it belongs to. */
.be-cat { margin: 0 0 18px; }
.be-cathead { display: flex; align-items: flex-end; gap: 8px; padding: 0 0 4px; }
.be-catlabel {
  flex: none; width: 210px; font-family: var(--cond); font-weight: 700;
  font-size: .82rem; letter-spacing: .12em; text-transform: uppercase; color: var(--gold);
}
.be-colheads { display: flex; gap: 8px; }
.be-colhead {
  width: 62px; font-size: .64rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); text-align: center;
}
.be-row {
  display: flex; align-items: center; gap: 8px; padding: 4px 6px; margin: 0 -6px;
  border-radius: 5px;
}
.be-row:hover { background: var(--surface-2); }
/* A withheld line is the same kind of thing as the rest, so it sits in the same
   table — marked, not moved somewhere else. */
.be-row.is-withheld { background: rgba(214,169,78,.07); box-shadow: inset 3px 0 0 var(--gold-deep); }
.be-row.is-withheld:hover { background: rgba(214,169,78,.12); }
.be-tag {
  margin-left: 6px; padding: 1px 6px; border-radius: 4px; font-size: .62rem;
  letter-spacing: .08em; text-transform: uppercase;
  background: rgba(214,169,78,.18); color: var(--gold-lite); cursor: help;
}
.be-field--who { flex: none; width: 210px; flex-direction: row; align-items: center; }

@media (max-width: 900px) {
  .be-cathead { display: none; }
  .be-row { flex-wrap: wrap; }
  .be-field--who { width: 100%; }
}
.be-drop {
  display: inline-flex; align-items: center; gap: 5px; flex: none; width: 62px;
  font-size: .66rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); cursor: pointer; justify-content: center;
}
.be-drop input { accent-color: var(--gold); cursor: pointer; }
.be-drop:hover { color: var(--ink-dim); }
.be-colhead--drop { color: var(--faint); }
.be-row.is-blank { opacity: .62; }
.be-row.is-blank:hover, .be-row.is-blank:focus-within { opacity: 1; }
.be-tag--add { background: rgba(120,140,170,.18); color: var(--ink-dim); }
/* Sits under the category's rows, indented to the picker column so it reads as
   part of that table rather than as an action on the page. */
.be-addwrap { padding: 4px 0 0; }
.be-addrow {
  padding: 3px 10px; font-size: .68rem; letter-spacing: .06em;
  text-transform: uppercase;
}

/* --- Data Tracker as an editor --------------------------------------------
   The week ledger stopped being a report: a row expands into its box score, the
   list can be reordered, and games can be added or deleted. These styles exist
   to make the difference between LOOKING at data and CHANGING it visible —
   above all `.is-edited`, which marks a cell the operator typed into but has
   not yet stored. */

.btn-sm { padding: 6px 11px; font-size: .78rem; }

/* the # column doubles as the reorder control */
.wl-n { white-space: nowrap; }
.wl-pos { display: inline-block; min-width: 18px; }
.wl-move { display: inline-flex; flex-direction: column; gap: 2px; margin-left: 6px;
           opacity: 0; transition: opacity .12s; vertical-align: middle; }
.wl-row:hover .wl-move, .wl-row:focus-within .wl-move { opacity: 1; }
.wl-mv { background: none; border: 0; padding: 1px 5px; line-height: .85;
         font-size: .95rem; color: var(--faint); cursor: pointer; }
.wl-mv:hover { color: var(--gold); }
.wl-mv:focus-visible { outline: 1px solid var(--gold-lite); }

/* The whole ROW opens the editor now that the matchup column is gone; the Edit
   button is the visible, keyboard-reachable way to say so. */
.wl-row { cursor: pointer; }
.wl-expand { background: none; border: 0; padding: 2px 4px; margin: 0;
             font-family: var(--cond); font-size: .72rem; text-transform: uppercase;
             letter-spacing: .08em; color: transparent; cursor: pointer; }
.wl-row:hover .wl-expand, .wl-row:focus-within .wl-expand { color: var(--gold-lite); }
.wl-expand:hover { text-decoration: underline; }
.wl-expand:focus-visible { color: var(--gold-lite); outline: 1px solid var(--gold-lite); }
.wl-expand[aria-expanded="true"] { color: var(--gold); }

.wl-act { width: 1%; text-align: right; white-space: nowrap; }
.wl-del { background: none; border: 0; padding: 2px 4px; font-size: .72rem;
          color: transparent; cursor: pointer; }
.wl-row:hover .wl-del, .wl-row:focus-within .wl-del { color: #ff9a9a; }
.wl-del:hover { text-decoration: underline; }
.wl-del:focus-visible { color: #ff9a9a; outline: 1px solid rgba(255,120,120,.5); }

.wl-editrow > td { padding: 0 !important; background: var(--surface-2);
                   border-bottom: 1px solid var(--line) !important; }
.wl-msg { margin: 10px 0 0; }

.wl-orderbar { display: flex; align-items: center; gap: 12px; padding: 10px 14px;
               margin-top: 8px; border-radius: var(--radius-sm);
               background: rgba(214,169,78,.08); border: 1px solid var(--gold-deep); }
.wl-orderbar-note { flex: 1; font-size: .82rem; color: var(--gold-lite); }

.wl-add { margin-top: 14px; border-top: 1px solid var(--line-soft); padding-top: 10px; }
.wl-add > summary { cursor: pointer; font-size: .8rem; color: var(--muted);
                    font-family: var(--cond); letter-spacing: .06em;
                    text-transform: uppercase; }
.wl-add > summary:hover { color: var(--gold); }
.wl-addform { display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: flex-end;
              margin-top: 12px; }
.wl-addnote { flex: 1 1 100%; margin: 0; font-size: .78rem; color: var(--faint); }

/* --- the inline box-score editor ---------------------------------------- */

.tge { padding: 16px 18px 20px; }
.tge-head { display: flex; align-items: center; gap: 12px; margin-bottom: 12px;
            padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.tge-title { font-family: var(--cond); font-size: 1.05rem; letter-spacing: .03em;
             color: var(--ink); }
.tge-meta { font-size: .74rem; color: var(--faint); }
/* Position, typed. Sits with the meta line rather than with the box-score
   fields, because it edits the WEEK's list and not this game's numbers. */
.tge-slot { display: inline-flex; align-items: center; gap: 6px;
            font-size: .74rem; color: var(--faint); }
.tge-slot label { color: var(--muted); }
.tge-pos { width: 3.6em; padding: 2px 4px; text-align: center;
           font-size: .78rem; background: var(--surface); color: var(--ink);
           border: 1px solid var(--line); border-radius: 3px; }
.tge-pos:focus { border-color: var(--gold); outline: none; }
.tge-slot-of { white-space: nowrap; }

.tge-full { margin-left: auto; font-size: .74rem; color: var(--muted); }
.tge-full:hover { color: var(--gold-lite); }
.tge-close { background: none; border: 0; font-size: 1.3rem; line-height: 1;
             color: var(--faint); cursor: pointer; padding: 0 4px; }
.tge-close:hover { color: var(--ink); }
.tge-notice { margin-bottom: 12px; }
.tge .panel { background: var(--surface); }

/* A cell that was typed into but not yet stored. This is the whole point of the
   dirty tracking: an edited number must not be able to hide among stored ones. */
.is-edited { border-color: var(--gold) !important;
             box-shadow: 0 0 0 1px rgba(214,169,78,.35); }

/* Save appears only once there is something to save, so a form at rest offers
   no button that would rewrite the rows with what they already say. */
.tge-form .be-actions [name="save"] { display: none; }
.tge-form.is-dirty .be-actions [name="save"] { display: inline-block; }
.tge-form.is-dirty .be-actions::before {
  content: "Unsaved edits"; font-size: .74rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold); align-self: center;
}

/* --- the roster editor --------------------------------------------------- */

.trk-team { cursor: pointer; font: inherit; text-align: left; }
.trk-team:hover { border-color: var(--gold-deep); }
.trk-team:focus-visible { outline: 2px solid var(--gold-lite); outline-offset: 1px; }
.trk-team.is-open { border-color: var(--gold); background: rgba(214,169,78,.1); }

#trkRosterPanel { margin-top: 14px; }
.rst { border: 1px solid var(--line); border-radius: var(--radius-sm);
       background: var(--surface-2); padding: 14px 16px; }
.rst-head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.rst-title { font-family: var(--cond); font-size: 1rem; letter-spacing: .03em; }
.rst-count { font-size: .74rem; color: var(--faint); margin-right: auto; }
.rst-msg { margin: 0 0 10px; }
.rst-table { width: 100%; border-collapse: collapse; }
.rst-table thead { display: none; }
.rst-row + .rst-row .rst-cell { border-top: 1px solid var(--line-soft); }
.rst-cell { padding: 6px 0; }
.rst-form { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: flex-end; }
.rst-save { visibility: hidden; margin-left: auto; }
.rst-form.is-dirty .rst-save { visibility: visible; }
.rst-form.is-dirty { background: rgba(214,169,78,.06); border-radius: var(--radius-sm); }
.rst-note { margin: 12px 0 0; font-size: .78rem; color: var(--faint); }

/* Season stats, one team: the five categories as their own standings, each
   with its own clear. They are five separate passes over the team and they
   fail independently, so the panel has to say WHICH one is missing and let
   that one be taken back out without the four that were fine. */
.rst-cats { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
            gap: 6px; margin-bottom: 12px; }
.rst-cat { display: flex; align-items: center; gap: 8px; padding: 6px 10px;
           border: 1px solid var(--line); border-left-width: 3px;
           border-radius: var(--radius-sm); background: var(--surface-2); }
.rst-cat.is-full { border-left-color: var(--gold); }
.rst-cat.is-missing { border-left-color: #c9694e; }
.rst-cat-name { font-family: var(--cond); font-size: .82rem; letter-spacing: .03em;
                color: var(--ink-dim); }
.rst-cat-count { font-size: .74rem; color: var(--faint); margin-left: auto;
                 font-variant-numeric: tabular-nums; }
.rst-cat.is-missing .rst-cat-count { color: #d98a66; }
.rst-cat-clear { display: flex; }

/* The same five, league-wide, on the Season stats panel head. */
.trk-catbar { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.trk-cat { display: inline-flex; align-items: baseline; gap: 6px; padding: 3px 9px;
           border: 1px solid var(--line); border-radius: 20px;
           font-size: .74rem; color: var(--gold); font-variant-numeric: tabular-nums; }
.trk-cat b { font-family: var(--cond); font-weight: 500; letter-spacing: .04em;
             color: var(--ink-dim); }
.trk-cat.is-full { border-color: var(--gold-deep); }
.trk-cat.is-missing { border-color: rgba(201,105,78,.5); color: #d98a66; }

/* "not read", beside a field label whose window never showed that column. */
.ff-warn { margin-left: 5px; font-size: .68rem; letter-spacing: .04em;
           color: #d98a66; text-transform: none; }

/* Every window one finding was read on. A category that scrolls sideways is
   several screenshots of one row, and the card can only draw one of them. */
.flag-shots { display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
              margin-bottom: 10px; }
.flag-shots-lab { font-size: .72rem; color: var(--faint); letter-spacing: .03em; }
.flag-shot-link { display: inline-flex; align-items: center; justify-content: center;
                  min-width: 24px; padding: 1px 7px; border: 1px solid var(--line);
                  border-radius: 20px; font-size: .74rem; color: var(--muted);
                  text-decoration: none; font-variant-numeric: tabular-nums; }
.flag-shot-link:hover { border-color: var(--gold); color: var(--gold-lite); }
.flag-shot-link.is-shown { border-color: var(--gold-deep); color: var(--gold); }

/* The merge prompt. The one irreversible thing this panel can do, so it is
   loud, it says what it would cost, and it is never the default action. */
.rst-merge { border: 1px solid rgba(255,120,120,.4); background: rgba(255,120,120,.07);
             border-radius: var(--radius-sm); padding: 12px 14px; margin-bottom: 12px; }
.rst-merge p { margin: 0 0 10px; font-size: .84rem; color: #ffc9c9; line-height: 1.5; }

/* The bulk bar now offers both answers — accept as read, or delete what was
   read — so its explanation drops to its own full-width line beneath them
   rather than fighting the buttons for space. */
.flag-bulk .btn-danger { font-size: .85rem; padding: 9px 16px; }
/* Three actions now, so the two that are not the default sit at the same
   weight as each other and below the gold one. */
.flag-closeall .btn-ghost { font-size: .85rem; padding: 9px 16px; }
.flag-saveall button[disabled] { opacity: .6; cursor: progress; }
/* Two whole-screenshot actions share the right-hand end of the card bar. */
.flag-bar-discard + .flag-bar-discard { margin-left: 8px; }

/* Correcting a roster name while assigning a stat. Folded away by default —
   most assignments need nothing here, and an open text box beside a player
   picker reads as something you must fill in. */
.flag-fix { margin: 8px 0 2px; border-left: 2px solid var(--line); padding-left: 12px; }
.flag-fix > summary { cursor: pointer; font-size: .78rem; color: var(--muted); }
.flag-fix > summary:hover { color: var(--gold-lite); }
.flag-fix[open] > summary { color: var(--gold-lite); margin-bottom: 8px; }
.flag-fix .ff-lab { max-width: 420px; }
.flag-fix .flag-hint { margin-top: 6px; }

/* A game or a stats window pools several captures onto one card; the filmstrip
   is every one of them, clickable, below the shot drawn large. */
.flag-filmstrip { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.flag-thumb {
  width: 72px; height: 72px; object-fit: cover; display: block; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: #000; opacity: .68;
}
.flag-thumb:hover { opacity: 1; border-color: var(--gold-deep); }
.flag-thumb.is-active { opacity: 1; border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
.flag-game-label { color: var(--ink-dim); font-size: .78rem; }

/* The portrait finding: the captured crop (a sliver of the full roster
   screenshot, shown via the standard background-position% cropping trick)
   beside the matcher's candidates, radio-picked. Deliberately LARGE — the
   candidates share one camera, pose and light rig, so telling them apart is
   all hairline and feature detail; at thumbnail size the line-up is
   unanswerable (user, 2026-08-20). The crop's native size is 358px, so ~350
   shows it 1:1; the candidate art is 512px and takes the upscale well. */
.flag-portrait-row { align-items: flex-start; gap: 24px; flex-wrap: wrap; }
.flag-portrait-crop {
  --crop-w: 100%; --crop-h: 100%; --crop-x: 0%; --crop-y: 0%;
  flex: 0 0 auto; width: 350px; height: 350px;
  background-repeat: no-repeat; background-color: #000;
  background-size: var(--crop-w) var(--crop-h);
  background-position: var(--crop-x) var(--crop-y);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.flag-portrait-candidates { display: flex; flex-wrap: wrap; gap: 14px; flex: 1 1 auto; }
.flag-portrait-cand {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  width: 264px; padding: 8px; text-align: center;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: .8rem; color: var(--muted); cursor: pointer;
}
.flag-portrait-cand:has(input:checked) { border-color: var(--gold); color: var(--gold-lite); }
.flag-portrait-cand img { width: 248px; height: 248px; object-fit: cover; border-radius: var(--radius-sm); background: #000; }
.flag-portrait-noimg { font-size: .66rem; color: var(--faint); word-break: break-all; }
.flag-portrait-score { font-variant-numeric: tabular-nums; }

/* ---- identity line-up (roster review) ------------------------------------
   Which player is this card? One row per record the reader weighed, radio
   picked, plus "none of these". A row the calendar rules out is dimmed but
   still pickable: the class it was ruled out on can itself be a misread. */
.flag-picklist { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 4px; }
.flag-pick {
  display: flex; align-items: baseline; gap: 8px; padding: 6px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: .82rem; color: var(--muted); cursor: pointer;
}
.flag-pick:has(input:checked) { border-color: var(--gold); color: var(--gold-lite); }
.flag-pick.is-blocked { opacity: .62; }
.flag-pick-name { color: var(--ink); font-weight: 600; }
.flag-pick-meta { font-size: .76rem; color: var(--muted); }
.flag-pick-link { margin-left: auto; font-size: .74rem; color: var(--faint); flex: 0 0 auto; }

/* ---- all-Americans page editor (admin flags) -----------------------------
   A held all-Americans page is settled as a 25-row lineup rather than a note,
   so this is a dense table of paired pickers read against the screenshot above
   it. Rows are tight on purpose: 25 of them plus the shot has to stay on one
   screen, or the reviewer is scrolling between the answer and the question. */
.aa-page .aa-scope { align-items: flex-end; margin-bottom: 14px; }
.aa-scope .ff-lab { flex: 0 1 220px; }
.aa-scope-read { flex: 1 1 100%; margin-top: 2px; }

.aa-lineup { width: 100%; border-collapse: collapse; margin-bottom: 10px; }
.aa-lineup thead th {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .06em;
  font-size: .66rem; color: var(--muted); text-align: left; font-weight: 400;
  border-bottom: 1px solid var(--line); padding: 0 8px 5px 0;
}
.aa-lineup tbody th {
  font-family: var(--cond); text-transform: uppercase; letter-spacing: .06em;
  font-size: .72rem; color: var(--gold-lite); text-align: left; font-weight: 400;
  width: 30px; padding-right: 8px;
}
.aa-lineup td { padding: 2px 8px 2px 0; vertical-align: middle; }
.aa-lineup .ff { padding: 4px 6px; font-size: .82rem; }
.aa-lineup .aa-team { min-width: 130px; }
.aa-lineup .aa-slotpick { min-width: 96px; }
/* The read column is EVIDENCE, not an input — quiet, and never the widest
   thing in the row. It exists so a slot can be checked without going back to
   the screenshot for every single name. */
.aa-lineup td.aa-read { font-size: .76rem; color: var(--muted); line-height: 1.35; }
.aa-lineup td.aa-read b { color: var(--ink-dim); font-weight: 500; }
.aa-posraw, .aa-conf { color: var(--faint); font-size: .72rem; }
.aa-none { color: var(--faint); font-style: italic; }

/* The highlight is the whole point of the editor: twelve rows the reviewer must
   not have to read evenly. A left bar rather than a fill, so a highlighted row
   still reads as part of the table and the pickers keep their own contrast.
   'empty' gets a bar too — a row of the grid that read nothing is a hole in
   this shot, not a spot the team does not have. */
.aa-slot > th { border-left: 3px solid transparent; padding-left: 7px; }
.aa-slot.aa-ok > th { border-left-color: var(--line); }
.aa-slot.aa-low > th { border-left-color: var(--gold); }
.aa-slot.aa-low { background: rgba(214, 169, 78, .07); }
.aa-slot.aa-unresolved > th { border-left-color: #c9694e; }
.aa-slot.aa-unresolved { background: rgba(201, 105, 78, .09); }
.aa-slot.aa-empty > th { border-left-color: var(--gold-deep); }
.aa-slot.aa-empty td.aa-read { color: var(--faint); }

/* Clear stats — between Edit and Delete, and the way past a "held back" game.
   Revealed on hover like the other row actions, so a week of rows stays a
   ledger rather than a wall of buttons. */
.wl-clear { background: none; border: 0; padding: 2px 4px; font-size: .72rem;
            color: transparent; cursor: pointer; }
.wl-row:hover .wl-clear, .wl-row:focus-within .wl-clear { color: var(--gold-lite); }
.wl-clear:hover { text-decoration: underline; }
.wl-clear:focus-visible { color: var(--gold-lite); outline: 1px solid var(--gold-lite); }
/* Always visible: it explains why a game with gaps is not being re-captured. */
.wl-held { font-family: var(--cond); font-size: .62rem; text-transform: uppercase;
           letter-spacing: .07em; color: var(--gold-lite); cursor: help;
           border: 1px solid var(--gold-deep); border-radius: 4px;
           padding: 1px 5px; margin-right: 6px; }

/* ============================================================================
   MOBILE — the bottom bar, the nav sheet, and the phone breakpoint.

   LAST IN THE FILE ON PURPOSE. Everything here overrides rules defined above
   it, and several of those are late and specific (`.record-table.data-table th`
   at (0,3,1), the admin block at 2900+). Ordering is what keeps the overrides
   from needing `!important` to land.

   TWO BREAKPOINTS, and they answer different questions:
     900px — WHICH NAVIGATION. Matches the responsive block already in this
             file, so the layout flattening and the nav swap happen together.
             Above it the sidebar; below it the bar and the sheet. There is no
             third state.
     560px — HOW DENSE. Type scale, padding, touch targets, and the pinned
             stat block. A tablet at 700px has room for the desktop density;
             a phone at 390px does not.

   THE ACTIVE MARK IS ONE DEVICE, ROTATED. The sidebar marks the current page
   with a gold rule down the link's left edge (`inset 2px 0 0`). The bar wears
   the same rule along the tab's TOP edge, and the sheet's current group wears
   it down the left of its heading. Same 2px of gold, moved to whichever edge
   the layout puts the chrome on.
   ========================================================================== */

.mbar, .msheet { display: none; }

@media (max-width: 900px) {
  /* The sidebar does not exist here. It was `position: static` before, which
     dropped 11 links, 3 headings, the brand and Sign out — some 620px of it —
     into the top of the document, so a phone scrolled a full screen of
     navigation to reach the first word of content. */
  .sidebar { display: none; }
  .layout, .layout.is-collapsed { grid-template-columns: minmax(0, 1fr); }

  /* Room for the fixed bar, plus the home indicator on a notched phone.
     `env()` is 0 everywhere it does not apply, so this is one rule, not two. */
  .main { padding-bottom: calc(var(--mbar-h) + env(safe-area-inset-bottom) + 26px); }

  /* ---- bottom bar ------------------------------------------------------- */
  .mbar {
    display: grid; grid-template-columns: repeat(5, 1fr);
    /* 50 sits UNDER the table filter menus at 60 and the sheet at 70. The bar
       is standing chrome; a menu the reader just opened has to be able to cover
       it, and the sheet has to cover both. */
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    /* Opaque, not translucent: 138 team colors scroll underneath this and a
       frosted bar would take a different tint on every team page. */
    background: linear-gradient(180deg, var(--surface), #0a0d12);
    border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 -8px 20px -12px rgba(0, 0, 0, .8);
  }
  .mbar-tab {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 4px; min-height: var(--mbar-h); padding: 6px 2px 8px;
    background: none; border: 0; cursor: pointer;
    color: var(--muted); font: inherit; text-decoration: none;
    /* Declared transparent so the active rule swaps a color rather than adding
       a shadow — no 1px of reflow between the two states. */
    box-shadow: inset 0 2px 0 transparent;
    transition: color .15s, box-shadow .15s, background .15s;
  }
  .mbar-tab.is-active {
    color: var(--ink); box-shadow: inset 0 2px 0 var(--gold);
    background: linear-gradient(180deg, color-mix(in srgb, var(--gold) 11%, transparent), transparent 62%);
  }
  .mbar-tab.is-active .mbar-ico { color: var(--gold-lite); }
  /* Touch has no hover, so the press is the only feedback there is. */
  .mbar-tab:active { background: var(--surface-2); }
  .mbar-ico { display: grid; place-items: center; width: 22px; height: 22px; }
  .mbar-ico svg { width: 21px; height: 21px; }
  .mbar-name {
    font-family: var(--cond); font-size: .62rem; font-weight: 600;
    letter-spacing: .1em; text-transform: uppercase; line-height: 1;
  }
  .mbar-tab[aria-expanded="true"] { color: var(--ink); }

  /* ---- nav sheet -------------------------------------------------------- */
  .msheet { display: block; position: fixed; inset: 0; z-index: 70; }
  .msheet[hidden] { display: none; }
  .msheet-scrim {
    position: absolute; inset: 0; background: rgba(6, 8, 12, .68);
    opacity: 0; transition: opacity .2s ease;
  }
  .msheet.is-open .msheet-scrim { opacity: 1; }
  .msheet-panel {
    position: absolute; left: 0; right: 0; bottom: 0;
    display: flex; flex-direction: column; max-height: 86vh;
    background: var(--bg); border-top: 1px solid var(--gold-deep);
    border-radius: 16px 16px 0 0;
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 -20px 50px -20px rgba(0, 0, 0, .9);
    translate: 0 100%; transition: translate .24s cubic-bezier(.2, .8, .3, 1);
  }
  .msheet.is-open .msheet-panel { translate: 0 0; }
  .msheet-top {
    display: flex; align-items: center; gap: 10px; flex: none;
    padding: 14px 16px 12px; border-bottom: 1px solid var(--line-soft);
  }
  .msheet-brand { display: flex; align-items: center; gap: 11px; flex: 1; min-width: 0; }
  .msheet-x {
    flex: none; display: grid; place-items: center; width: 40px; height: 40px;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: 50%; color: var(--ink-dim); cursor: pointer;
  }
  .msheet-x:active { background: var(--surface-2); color: var(--ink); }
  /* The sheet scrolls, the panel does not — so the brand row and the close
     button stay put while a long tree moves under them. */
  .msheet-body { overflow-y: auto; overscroll-behavior: contain; padding: 6px 10px 14px; }
  .msheet-group { padding: 6px 0; border-bottom: 1px solid var(--line-soft); }
  .msheet-group:last-child { border-bottom: 0; }
  .msheet-label {
    font-family: var(--cond); font-size: .66rem; font-weight: 600;
    letter-spacing: .2em; text-transform: uppercase; color: var(--muted);
    padding: 8px 12px 6px; border-left: 2px solid transparent;
  }
  .msheet-group.is-here .msheet-label { color: var(--gold); border-left-color: var(--gold); }
  .msheet-link {
    display: flex; align-items: center; gap: 13px;
    min-height: 46px; padding: 8px 12px; border-radius: var(--radius-sm);
    font-size: .95rem; font-weight: 500; color: var(--ink-dim);
  }
  .msheet-link.is-active {
    background: var(--surface-2); color: var(--ink);
    box-shadow: inset 2px 0 0 var(--gold);
  }
  .msheet-link.is-soon { color: var(--faint); }
  a.msheet-link:active { background: var(--surface-2); }
  .msheet-ico { flex: none; display: grid; place-items: center; width: 22px; height: 22px; }
  .msheet-ico svg { width: 20px; height: 20px; }
  .msheet-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  /* Scrolling the page behind an open sheet is the classic phone bug: the sheet
     stays put and the content slides away under it. */
  body.sheet-open { overflow: hidden; }
}

/* ============================================================================
   PHONE — density. Everything above answers "which navigation"; this answers
   "how much of the desktop's breathing room survives a 390px screen".
   ========================================================================== */
/* ---- the coach shrinks to a portrait and a name ------------------------- */
@media (max-width: 760px) {
  /* THE COACH IS A PORTRAIT WITH ONE LINE UNDER IT: "HC: <name>". The label,
     the tenure and the record go (the user's call) — at this width they were
     the reason he and the school could not share a line. He is a column now,
     so he cannot stand on the rule; the portrait fades out at the bottom
     instead of ending in a cut, and the sides feather as they always did.
     No circle — that read as a badge, not a man.

     `display: contents` on the meta wrapper lets the name sit under the face
     without moving anything in the markup. */
  .tb-coach {
    --tb-coach-h: 66px;
    align-self: center; display: flex; flex-direction: column;
    align-items: center; gap: 3px; padding-left: 12px;
  }
  .tb-coach::before { top: 6px; bottom: 6px; }
  .tb-coach-meta { display: contents; }
  .tb-coach-label, .tb-coach-facts { display: none; }
  .tb-coach-name { order: 2; font-size: .78rem; white-space: nowrap; }
  .tb-coach.is-known .tb-coach-name::before { content: "HC: "; opacity: .7; }
  .tb-coach-face {
    order: 1; margin-bottom: 0;
    width: calc(var(--tb-coach-h) * 1.1); height: var(--tb-coach-h);
    -webkit-mask-image:
      linear-gradient(to right, transparent 0, #000 16%, #000 84%, transparent 100%),
      linear-gradient(to top, transparent 0, #000 26%);
    -webkit-mask-composite: source-in;
    mask-image:
      linear-gradient(to right, transparent 0, #000 16%, #000 84%, transparent 100%),
      linear-gradient(to top, transparent 0, #000 26%);
    mask-composite: intersect;
  }
  .tb-coach-none { margin-bottom: 0; }
}

@media (max-width: 560px) {
  .main { padding: 16px 12px calc(var(--mbar-h) + env(safe-area-inset-bottom) + 26px); }

  /* The page head is the first thing on the screen now that no nav precedes it,
     so it gets the tightening rather than the display size it had at 900px. */
  .page-head { margin-bottom: 16px; padding-bottom: 12px; gap: 10px; }
  .page-head h1 { font-size: 1.7rem; margin-top: 4px; }
  .eyebrow { font-size: .62rem; letter-spacing: .14em; }

  .panel { padding: 13px 12px; margin-bottom: 15px; border-radius: 10px; }
  .panel-head { margin-bottom: 12px; flex-wrap: wrap; row-gap: 6px; }
  .panel-head h2 { font-size: 1.12rem; }

  /* 44px is the floor for anything you tap. These sat between 26 and 35. */
  .season-select { min-width: 0; padding: 9px 34px 9px 13px; }
  .scope-rail .season-select { font-size: .95rem; min-width: 0; }
  .sched-week { padding: 8px 12px; }
  .sched-filter .cfilter-tab { height: 40px; }

  /* The schedule's two strips bleed to the panel's edges so they read as
     scrollable rather than as rows that happen to be cut off — the same move
     the banner tab strips make below. */
  .sched-filter .cfilter-tabs, .sched-weeks {
    margin-left: -12px; margin-right: -12px;
    padding-left: 12px; padding-right: 12px;
    scrollbar-width: none;
  }
  .sched-filter .cfilter-tabs::-webkit-scrollbar,
  .sched-weeks::-webkit-scrollbar { display: none; }

  /* The banner tab strips SCROLL SIDEWAYS here (the user's call — wrapping
     them doubled the banner's height). A scrolling strip is what the week rail
     and the conference picker already do on this site.

     The scrollbar is THIN, NOT HIDDEN, unlike theirs. Hidden was fine under a
     thumb and useless under a mouse: a row of tabs with nothing on screen
     saying there is more, and RECORDS could not be reached on a narrow desktop
     window. A 6px bar in the banner's own ink is the affordance. */
  .tb-tabs, .pb-tabs {
    flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, currentColor 34%, transparent) transparent;
    /* Bleed to the banner's edges so the strip reads as scrollable rather than
       as a row that happens to be cut off. */
    margin-left: -12px; margin-right: -12px; padding: 0 12px 4px;
  }
  .tb-tabs::-webkit-scrollbar, .pb-tabs::-webkit-scrollbar { height: 6px; }
  .tb-tabs::-webkit-scrollbar-thumb, .pb-tabs::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, currentColor 34%, transparent); border-radius: 3px;
  }
  .tb-tab, .pb-tab { flex: none; font-size: .88rem; padding: 11px 13px; }

  /* ---- the frozen stat block -------------------------------------------
     Player Stats pins four columns (120 + 108 + 58 + 60 = 346px) and Team
     Stats' per-game grain pins seven (~540px). Both are right at 1600px and
     both are wider than the phone: the frozen block filled the screen and the
     stats it exists to anchor had nowhere left to scroll.

     ONE COLUMN STAYS PINNED — the identity, which is the whole point of the
     block — and the rest go back to scrolling with their numbers. `position`
     is what is unset, not the class: app.js counts `.stat-fix` headers to
     measure the offsets, and taking the class off would leave it measuring a
     block that no longer exists. A static cell simply ignores `--sl-N`.

     (0,5,0) — four classes plus the one inside `:not()`, which counts — so it
     outranks `.record-table.data-table th.stat-fix` at (0,3,1), the rule that
     pins the HEADER cells. Miss that one and the headers stay frozen over
     columns that have already started to move. */
  .record-table.data-table.stat-table .stat-fix:not(.stat-fix-1) {
    position: static; z-index: auto;
  }
  /* The shadow marks the edge of the frozen block, so it moves to the column
     that is still frozen. Both rules can hit the same cell where column 1 is
     also the last pinned one; they paint the same shadow. */
  .record-table.data-table.stat-table .stat-fix-last { box-shadow: none; }
  .record-table.data-table.stat-table .stat-fix-1 {
    box-shadow: 6px 0 8px -6px rgba(0, 0, 0, .55);
  }
  /* A name is worth less width when it is the only thing being bought — but
     NOT AS A BLANKET `.stat-table` RULE. Six variants declare their own --sf-1
     at that same specificity, so a blanket placed later in the file would win
     against all of them and RAISE the narrow ones: pseason and plog pin a 54px
     Year, hub a 62px one, and none of the three is the problem here. Only the
     blocks genuinely too wide for a 390px screen are named. Player Stats' base
     120px is a third of the content width and is left alone. */
  .stat-table--team { --sf-1: 128px; --clip-1: 112px; }
  .stat-table--team.stat-table--season { --sf-1: 148px; }
  /* The schedule table is NOT a `data-table` — it has no funnels and nothing
     worth sorting — so the three rules above, which all name that class, miss
     it entirely. Same treatment spelled for this table: one column stays
     frozen, the other three go back to scrolling. 432px of pinned block on a
     390px screen left the stats nowhere to go. */
  .stat-table--sched .stat-fix:not(.stat-fix-1) { position: static; z-index: auto; }
  .stat-table--sched .stat-fix-last { box-shadow: none; }
  .stat-table--sched .stat-fix-1 { box-shadow: 6px 0 8px -6px rgba(0, 0, 0, .55); }
  .panel--sched { --sf-2: 154px; --clip-2: 138px; }
  /* And the tabs come back to the left edge — there is no pinned block left for
     them to start after, and `--sl-fix` is still measuring the four columns
     app.js can see in the header. */
  .sched-tabs { margin-left: 0; }
  .stat-table--hubps { --sf-1: 124px; --clip-1: 108px; }
  /* Vertical everywhere, horizontal only where nothing more specific has an
     opinion: `.record-table.data-table.stat-table th` at (0,3,1) keeps its 16px
     of funnel clearance, which is width a stat header actually needs. This
     tightens the plain tables — Ratings, League History — and the row height of
     all of them. */
  .record-table th, .record-table td { padding: 8px 9px; }

  /* ---- banners ----------------------------------------------------------
     The 900px block already wrapped the coach onto his own line and turned the
     team page's band into full-height ground. At 390px the headline type is
     what is left to give: a 1.7rem school name over a 320px column wrapped to
     three lines and pushed the strip off the first screen. */
  .team-banner, .player-banner { padding: 13px 12px 0; }
  .tb-school { font-size: 1.35rem; }
  .tb-year { font-size: 1.05rem; }
  .tb-crest { width: 46px; height: 46px; }
  /* Row gap stays zero at every width: the stat strip's own margin is the only
     distance between line one and its rule, and the coach bleeds by exactly it. */
  .tb-head { gap: 0 11px; min-height: 0; }
  /* The subline does not wrap, so at this width the stars give first — the
     record and the finish pills are the words, and this is a picture. */
  .tb-stars { gap: 2px; }
  .tb-stars .star-ico { width: 13px; height: 13px; }
  /* 366px of content less four 8px gaps is 66px a column — the ring is 50 of
     it, so the five stay on one line with no scroll. */
  .team-banner { --tb-dial: 38px; }
  .tb-stats { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px 8px; }
  /* The group break comes down with everything else, but it does not go: it is
     the only thing saying the ratings and the averages are two kinds of fact. */
  .tb-stat:nth-child(4) { margin-left: 14px; }
  .tb-stat-val { font-size: 1.05rem; }
  .tb-dial-val { font-size: .92rem; }
  .tb-dial-label { font-size: .5rem; }
  .tb-stat-label { font-size: .5rem; letter-spacing: .07em; }
  /* The ring keeps its weight: the stroke is viewBox units, so it scales with
     the circle instead of thinning as the dial shrinks. */
  .tb-conf { height: 24px; }
  /* Smaller again: 390px is 342px of content and the school needs the rest. */
  .tb-coach { --tb-coach-h: 56px; padding-left: 10px; }
  .tb-coach-name { font-size: .72rem; }

  /* THE CARD IS A THIRD OF THE SCREEN (the user's call), which at 390px is
     130px. What that leaves is a real budget rather than a round number: 366px
     of content less the card and a 12px gap gives the name 224px, and it now
     gets ALL of it — the OVR badge that used to take 68 of those is a line of
     type under the name. `Ca'Lil Valentine` at 1.6rem condensed is about 150 of
     the 224, so it fits on one line here for the first time.

     Nothing sizes the card's crest here: it is a fraction of `--pb-card` and
     follows it down on its own. `.pb-head` is NOT in the shared `.tb-head` rule
     above — `gap` on a grid sets the row gap too, and an 11px one stacked on
     the body's own gap double-spaced the two lines.

     THE FACT LINE GOES FULL WIDTH, and only here. It sits beside the card at
     every width above this one, which is where the user wanted it; at 390 the
     column beside a 130px card is 224px and four facts in it wrap to three
     lines, taller than the card they were meant to sit beside. Under the card
     they take 366 and wrap to two. */
  /* The shared `.team-banner, .player-banner` rule above sets 13px of top
     padding here as a literal; --pb-pad-t has to follow it for the same reason
     it follows at 900. */
  .player-banner { --pb-card: 130px; --pb-pad-t: 13px; }
  .pb-head {
    column-gap: 12px; row-gap: 10px;
    grid-template-areas:
      "card body"
      "meta meta";
  }
  /* Wraps rather than ellipsing the name away: a long name takes the line and
     the class drops under it, which costs a row and keeps the whole name. That
     is the trade the desktop ellipsis is NOT worth making at 224px. */
  .pb-name { font-size: 1.6rem; white-space: normal; overflow: visible; }
  .pb-class { font-size: 1.02rem; }
  .pb-namebar { gap: 1px 10px; }
  .pb-rating { gap: 4px 11px; }
  .pb-ovr-val { font-size: 1.6rem; }
  .pb-ovr { gap: 6px; }
  .pb-ovr--dev .pb-ovr-val { font-size: 1.1rem; }
  .pb-dev-ico { height: 1.7rem; }
  .pb-pos { font-size: 1.1rem; letter-spacing: .07em; }
  .pb-jersey-art { height: 1.9rem; }
  .pb-stint { font-size: .74rem; padding: 2px 8px 2px 5px; }
  .pb-metabar { column-gap: 8px; }
  .pb-fact { font-size: .82rem; gap: 5px; }
  .pb-fact:not(:last-child)::after { margin-left: 3px; }
  .pb-under { gap: 8px 14px; margin-top: 11px; padding-top: 11px; }

  /* ---- awards, all-americans -------------------------------------------- */
  .tc-shelf { gap: 18px; }
  /* The hero's own sizes live in the 620 block above — it sizes the trophy and
     the face off the viewport, so a fixed override here would undo it. */
  .tc-th { min-width: 132px; }
  .tc-th--1 { min-width: 168px; }
  .aa-cell { min-width: 118px; }
  .aa-picker { padding-left: 10px; padding-right: 10px; }
  .aa-pick-tab { width: 62px; }

  /* ---- dashboard, standings, box score ----------------------------------- */
  .lead-grid { gap: 14px; }
  .sgame-grid { gap: 8px; }
  .box-layout, .scoreboard { gap: 12px; }
  /* The bracket is already a scroll box; what it lacked was a hint that it
     scrolls, since the phone hides the bar until you touch it. */
  .bracket-scroll { padding-left: 2px; padding-right: 2px; }
}

/* The global reduced-motion rule near the top of the responsive section kills
   every `transition` with `!important`, the sheet's included — it snaps open
   instead of sliding, which is the correct outcome and needs nothing here. */

/* The flagged-field frame on a review card: the whole read is on the card,
   this is the part the finding is actually about. */
.flag-suspect { position: relative; border: 1px dashed var(--gold); border-radius: var(--radius-sm); padding: 14px 12px 8px; margin: 10px 0 14px; }
.flag-suspect-tag { position: absolute; top: -9px; left: 10px; padding: 0 6px; background: var(--surface-2); font-size: .68rem; letter-spacing: .04em; text-transform: uppercase; color: var(--gold); }

/* The polls review card: three columns of 25 rank slots each. A slot the
   ingest could not settle gets the gold dashed frame the suspect wrapper
   uses elsewhere, per cell — 75 selects can't each afford the full frame. */
.polls-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin: 10px 0 14px; }
.polls-col-head { font-size: .72rem; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-2); margin: 0 0 6px 26px; }
.polls-col-absent { color: var(--ink-3); text-transform: none; letter-spacing: 0; }
.polls-slot { display: flex; align-items: center; gap: 6px; margin: 2px 0; }
.polls-slot-rank { flex: 0 0 20px; text-align: right; font-variant-numeric: tabular-nums; font-size: .78rem; color: var(--ink-2); }
.polls-slot .ff-team { min-width: 0; flex: 1 1 auto; }
.polls-slot--flag .ff-team { border: 1px dashed var(--gold); }
.polls-slot--flag .polls-slot-rank { color: var(--gold); font-weight: 700; }
@media (max-width: 900px) { .polls-grid { grid-template-columns: 1fr; } }

/* ---- Team Hub → Roster → Summary ----------------------------------------- */
/* Three sections that never share a rule with the roster TABLE. The table skin
   (`.roster-table`) is deliberately shared with Player Ratings and must stay
   that way — this view is a different shape entirely, so it takes its own
   `.rsum-` namespace rather than widening theirs. */

/* --- Impact Players ------------------------------------------------------- */
/* TWO COLUMNS WHILE THERE IS ROOM FOR TWO, then one. A card needs about 430px
   to hold a portrait, a name with its archetype, a stat line and the rail, and
   the main column is the viewport less the 244px sidebar and 68px of padding —
   so two of them stop fitting at 1180px and the section drops to a single
   column rather than squeezing both (user's call).

   `minmax(0, 1fr)` and not `1fr`: a grid track's default minimum is its
   CONTENT, so one long stat line would push its column past half the panel and
   squash the other. */
.rsum-sides {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px;
}
@media (max-width: 1180px) {
  .rsum-sides { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the two headings stop being column labels and start being
     section headings, so they read from the left like every other one. */
  .rsum-side + .rsum-side { margin-top: 20px; }
  .rsum-side-head { text-align: left; }
}
/* Centred over its own column, because the two columns are what it is
   labelling — a left-aligned heading over a full-width column reads as the
   start of the card under it rather than as the name of the four. */
.rsum-side-head {
  font-family: var(--cond); font-size: .78rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; text-align: center;
  color: var(--muted); margin: 0 0 10px;
}

/* ONE FIXED HEIGHT FOR EVERY CARD, which is what makes eight cards read as
   four ROWS rather than two lists. Offence and defence are separate columns,
   so nothing aligns them but this: a stat line that wrapped or an honour
   nobody won would otherwise pull one column out of step with the other for
   the rest of its length.

   The height is DERIVED from the three rows rather than guessed, so changing a
   row changes the card and the portrait with it. */
.rsum-card {
  --rsum-face: 78px;
  --rsum-row-1: 24px;      /* name + archetype  |  class          */
  --rsum-row-2: 30px;      /* the stat line     |  the rating     */
  --rsum-row-3: 24px;      /* the honour pill   |  the dev badge  */
  --rsum-row-gap: 5px;
  --rsum-pad: 12px;
  --rsum-card-h: calc(var(--rsum-row-1) + var(--rsum-row-2) + var(--rsum-row-3)
                      + 2 * var(--rsum-row-gap) + 2 * var(--rsum-pad));
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 14px; align-items: start;
  height: var(--rsum-card-h);
  padding: var(--rsum-pad) 14px; margin-bottom: 12px;
  background: var(--surface-2); border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}
.rsum-card:last-child { margin-bottom: 0; }

/* The player page's three-layer stack at card size. Every rule that governs
   fit or position is shared by BOTH art layers — the jersey is painted on the
   portrait's own 512-square canvas, so a crop applied to one and not the other
   slides the collar off the neck. See `.pb-card-img` for the long version. */
.rsum-face {
  position: relative; width: var(--rsum-face); height: 100%;
  border-radius: 12px; overflow: hidden;
  background: rgba(8, 10, 14, .5);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .11);
}
.rsum-face-img {
  position: absolute; top: 0; left: 0; z-index: 2;
  width: 100%; height: 100%; object-fit: cover; display: block;
  object-position: 50% 100%;
}
.rsum-face-jersey { pointer-events: none; }
/* Behind the shoulder and bleeding off the right edge, sized off ONE axis so
   it stays square in a portrait-shaped box. */
.rsum-face-mark {
  position: absolute; z-index: 1;
  top: calc(var(--rsum-face) * .06); right: calc(var(--rsum-face) * -.1);
  width: calc(var(--rsum-face) * .58); height: calc(var(--rsum-face) * .58);
  object-fit: contain; opacity: .9;
}
.rsum-face-mark--solo {
  top: 50%; right: 50%; transform: translate(50%, -50%);
  width: calc(var(--rsum-face) * .62); height: calc(var(--rsum-face) * .62);
  opacity: .55;
}
/* The narrow layout's rating, in the corner the art never uses. Hidden until
   the rail goes; see the 900px block at the end of this section. */
.rsum-face-ovr { display: none; }

/* --- the two text columns, three rows each -------------------------------- */
/* Body and rail share a row template so the halves stay level: class beside the
   name, rating beside the stat, dev badge beside the honour. Rows are PLACED,
   not flowed — auto placement would slide the honour up into the stat's row for
   a player with no stat line and break that pairing. */
.rsum-body, .rsum-rail {
  display: grid; align-content: start; row-gap: var(--rsum-row-gap);
  grid-template-rows: var(--rsum-row-1) var(--rsum-row-2) var(--rsum-row-3);
}
.rsum-body { min-width: 0; }
.rsum-rail { justify-items: end; text-align: right; }

/* Nothing in a card wraps. A second line would cost every card below it its
   place in the grid, so a row too long for its column trails off and is
   clipped — the user's call, and the reason there is no ellipsis: a fading
   run of numbers reads as "there is more", which is true, while a "…" reads
   as a value that was shortened. */
.rsum-name, .rsum-stat, .rsum-honor {
  white-space: nowrap; overflow: hidden; text-overflow: clip;
}

.rsum-name {
  grid-row: 1; align-self: center;
  display: flex; align-items: baseline; flex-wrap: nowrap; gap: 8px;
  margin: 0; font-size: 1.02rem; font-family: var(--sans); font-weight: 700;
}
/* NOTHING IN THIS ROW GROWS. `flex-grow: 1` on the name is what pushed the
   archetype out to the middle of a wide card — the name swallowed the free
   space and carried the plaque along with it. With every item at `flex-grow:
   0` the row packs left and the slack falls off the end, which is where slack
   belongs when the three parts are one phrase.

   `min-width: 0` is what lets a flex item shrink below its own text at all,
   and the archetype shrinks three times as fast (see `.rsum-arch`), so a long
   name eats the plaque rather than the plaque pushing the name off its card. */
.rsum-name-txt {
  min-width: 0; flex: 0 1 auto; overflow: hidden; white-space: nowrap;
  color: var(--ink); text-decoration: none;
}
.rsum-name-txt:hover { color: var(--gold-lite); }
.rsum-pos {
  flex: none; font-family: var(--cond); letter-spacing: .04em;
  color: var(--ink-dim);
}
/* The archetype is context, not identity — a plaque, at the weight of one. */
.rsum-arch {
  flex: 0 3 auto; min-width: 0; overflow: hidden;
  font-size: .74rem; font-weight: 600; color: var(--muted);
  padding: 2px 8px; border-radius: 6px; background: var(--raised);
}
/* The narrow layout's class chip, pinned right so the NAME is what trails off
   behind it — which of the two matters more at 200px was the user's call. */
.rsum-name-class { display: none; }

/* ONE line, its own plaque, only as wide as its text — a full-width bar would
   read as a table with no columns. */
.rsum-stat {
  grid-row: 2; align-self: center; justify-self: start; max-width: 100%;
  margin: 0; padding: 4px 9px; border-radius: 7px;
  background: var(--raised); color: var(--ink-dim);
  font-size: .8rem; line-height: 1.25; font-variant-numeric: tabular-nums;
}
.rsum-honor {
  grid-row: 3; align-self: center; justify-self: start; max-width: 100%;
  display: inline-flex; align-items: center; text-decoration: none;
}
/* The mark never shrinks and the words never wrap: the pill clips its own text
   at the column's edge, which is the whole card's rule. */
.rsum-honor .pill-ico { flex: none; }
.rsum-honor-txt { min-width: 0; overflow: hidden; white-space: nowrap; }

.rsum-class {
  grid-row: 1; align-self: center;
  font-family: var(--cond); font-size: .88rem; letter-spacing: .04em;
  color: var(--muted); white-space: nowrap;
}
/* The player banner's `.pb-ovr`: number and label on ONE baseline, which is
   what frees the row under it for the badge. */
.rsum-rating {
  grid-row: 2; align-self: center;
  display: inline-flex; align-items: baseline; gap: 6px;
}
/* Unboxed, unlike the table's badge: on a card nothing sits beside it
   competing for the eye, so the border is noise the row needed and this does
   not. The tier COLOUR still carries the meaning. */
.rsum-ovr {
  font-family: var(--cond); font-weight: 700; font-size: 1.55rem; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--ink-dim);
}
.rsum-ovr.ovr-elite { color: var(--gold-lite); }
.rsum-ovr.ovr-good  { color: #7bc47f; }
.rsum-ovr.ovr-low, .rsum-ovr.ovr-none { color: var(--muted); }
.rsum-ovr-label {
  font-family: var(--sans); font-size: .62rem; letter-spacing: .16em;
  font-weight: 600; text-transform: uppercase; color: var(--faint);
}
.rsum-dev-slot {
  grid-row: 3; align-self: center;
  display: flex; align-items: center; justify-content: flex-end;
  height: var(--rsum-row-3); min-width: 26px;
}
.rsum-dev { width: 24px; height: 24px; object-fit: contain; display: block; }
.rsum-dev-txt { font-size: .72rem; color: var(--muted); }

/* --- the pies ------------------------------------------------------------- */
.rsum-pies {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: 18px;
}
.rsum-pie { display: flex; flex-direction: column; }
.rsum-pie-svg {
  display: block; width: 100%; max-width: 230px; height: auto;
  margin: 6px auto 0;
  /* A hairline between neighbouring slices, so two adjacent colours of similar
     weight still read as two. Painted by the stroke rather than a gap: a gap
     would leave the panel ground showing through in a wedge. */
  stroke: var(--surface); stroke-width: .6;
}
.rsum-pie-pct {
  fill: #fff; font-family: var(--cond); font-size: 5.2px; font-weight: 700;
  text-anchor: middle; dominant-baseline: middle; stroke: none;
  paint-order: stroke; text-shadow: 0 0 2px rgba(0, 0, 0, .6);
}
.rsum-legend {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 14px;
  list-style: none; margin: 14px 0 0; padding: 13px 0 0;
  border-top: 1px solid var(--line-soft);
}
.rsum-legend li {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .78rem; color: var(--ink-dim);
}
.rsum-swatch {
  width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: none;
}

/* --- the breakdown grid --------------------------------------------------- */
.rsum-table { font-size: .82rem; }
.rsum-table th, .rsum-table td { padding: 8px 10px; vertical-align: top; }
.rsum-table thead th {
  font-family: var(--cond); font-weight: 600; letter-spacing: .06em;
  color: var(--muted); text-align: center; white-space: nowrap;
}
.rsum-pos-head { width: 52px; }
.rsum-pos-cell {
  font-family: var(--cond); letter-spacing: .05em; color: var(--ink-dim);
  text-align: center; white-space: nowrap;
}
/* THE STACK IS THE DIV, NOT THE CELL. `display: flex` on a `<td>` takes it out
   of the table's column layout: the row's four cells stop being cells, stack
   down the page, and every player on the roster lands under Freshman. The `td`
   stays a `td` and the flex column lives one level in.

   Pills stay RIGHT-aligned, so the OVR figures form a readable column inside
   each class instead of ragging with the names. */
.rsum-cell { vertical-align: top; }
.rsum-cell-in {
  display: flex; flex-direction: column; align-items: flex-end; gap: 5px;
}

.rsum-pill {
  display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 3px 8px 3px 3px; border-radius: 16px;
  background: var(--raised); border: 1px solid var(--line-soft);
  color: var(--ink-dim); text-decoration: none; white-space: nowrap;
}
.rsum-pill:hover { border-color: var(--gold-deep); color: var(--ink); }
/* The head ALONE — no crest, no jersey. At 22px the three-layer stack reads as
   a smudge, and the crest is already the banner over the whole page. */
.rsum-pill-face {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  object-fit: cover; object-position: 50% 15%;
  background: rgba(8, 10, 14, .5);
}
.rsum-pill-rs { display: inline-flex; color: var(--faint); flex: none; }
.rs-ico { width: 13px; height: 13px; display: block; }
.rsum-pill-name { overflow: hidden; text-overflow: ellipsis; }
.rsum-pill-ovr {
  font-family: var(--cond); font-weight: 700; font-size: .86rem;
  color: var(--ink-dim); flex: none;
}
.rsum-pill-ovr.ovr-elite { color: var(--gold-lite); }
.rsum-pill-ovr.ovr-good  { color: #7bc47f; }
.rsum-pill-ovr.ovr-low, .rsum-pill-ovr.ovr-none { color: var(--muted); }
.rsum-pill-dev { width: 16px; height: 16px; object-fit: contain; flex: none; }

/* One column all the way down means a card is nearly the whole page wide, so
   the rail keeps fitting far longer than the viewport suggests. It stops
   fitting at about a 430px CARD — a phone held upright, and nothing above it.
   That is what this breakpoint is: 480px of viewport, less 32px of padding.

   THE RAIL'S THREE FACTS ARE RE-HOMED RATHER THAN DROPPED (user's call):

     class  -> beside the name, pinned right, so the NAME trails off behind it
     rating -> the portrait's top-left corner, without its label
     dev    -> dropped, along with the archetype

   The card keeps its fixed height. Four cards reading as four rows is the
   point of the section and it holds at every width. */
@media (max-width: 480px) {
  .rsum-card { grid-template-columns: auto minmax(0, 1fr); --rsum-face: 62px; }
  .rsum-rail, .rsum-arch { display: none; }

  .rsum-face-ovr {
    position: absolute; z-index: 3; top: 3px; left: 4px;
    display: block; font-family: var(--cond); font-weight: 700;
    font-size: 1rem; line-height: 1; font-variant-numeric: tabular-nums;
    color: var(--ink); text-shadow: 0 1px 3px rgba(0, 0, 0, .95);
  }
  .rsum-face-ovr.ovr-elite { color: var(--gold-lite); }
  .rsum-face-ovr.ovr-good  { color: #a9e8b0; }

  .rsum-name-class {
    display: block; flex: none; margin-left: auto;
    font-family: var(--cond); font-size: .82rem; letter-spacing: .04em;
    color: var(--muted); white-space: nowrap;
  }
  .rsum-name { font-size: .95rem; gap: 6px; }
  .rsum-stat { font-size: .75rem; padding: 3px 7px; }
}

/* --- Box-score dataset card: finding highlights (flag_spec docstring). A
   flagged control carries .is-flagged and its explanation as the title, the
   same pattern as .polls-slot--flag. Multiple highlights per card are the
   normal case, not an error state. */
.be-cell.is-flagged {
  border: 1px dashed var(--gold); background: rgba(214,169,78,.10);
}
.be-field.is-flagged .be-label { color: var(--gold); font-weight: 700; }
.be-row.is-flagged { background: rgba(255,120,120,.07); box-shadow: inset 3px 0 0 #c4574a; }
.be-row.is-flagged:hover { background: rgba(255,120,120,.12); }
.be-row.is-withheld.is-flagged {
  background: rgba(214,169,78,.07); box-shadow: inset 3px 0 0 var(--gold-deep);
}
.be-cathead.is-flagged .be-catlabel { color: var(--gold-lite); }
.be-flagnote {
  margin-left: 6px; padding: 1px 6px; border-radius: 4px; font-size: .62rem;
  letter-spacing: .08em; text-transform: uppercase;
  background: rgba(196,87,74,.22); color: #e8a196; cursor: help; flex: none;
}
.be-unplaced { margin: 0 0 12px; display: flex; flex-direction: column; gap: 6px; }
.be-blocked-note { font-size: .78rem; color: #e8a196; max-width: 48ch; }
/* The per-row create/rename escape hatch drops to its own line under the row. */
.be-row { flex-wrap: wrap; }
.be-rowfix { flex: 1 1 100%; margin: 2px 0 4px 6px; border-left: 2px solid var(--line); padding-left: 12px; }
.be-rowfix > summary { cursor: pointer; font-size: .72rem; color: var(--faint); }
.be-rowfix > summary:hover { color: var(--gold-lite); }
.be-rowfix[open] > summary { color: var(--gold-lite); margin-bottom: 6px; }
.be-rowfix .ff-lab { max-width: 420px; }
/* The dataset card inside the flag queue: the editor's panels sit inside the
   card body, so trim their outer chrome to read as one card. */
.flag-boxes .be-card .panel { border: 0; padding: 10px 0; margin: 0; background: transparent; }
.flag-boxes .be-card .be-actions { padding: 12px 0 8px; }

/* --- Dataset card: frozen captures over a scrolling box score. The image and
   its filmstrip keep their place at the top of the card while the form
   scrolls in its own region, so the last kicking row is checked against the
   screenshot without a round trip up the page. The image gives up width for
   height (42vh) to leave the form room; clicking a thumbnail still swaps it
   in place. */
.flag-card.is-dataset .flag-img {
  width: auto; max-width: 100%; max-height: 42vh; margin: 0 auto;
}
.flag-card.is-dataset .flag-boxes {
  max-height: 60vh; overflow-y: auto; overscroll-behavior: contain;
  padding-right: 8px; border-top: 1px solid var(--line);
  scrollbar-width: thin; scrollbar-color: var(--gold-deep) var(--surface-2);
}
.flag-card.is-dataset .flag-boxes::-webkit-scrollbar { width: 11px; }
.flag-card.is-dataset .flag-boxes::-webkit-scrollbar-track { background: var(--surface-2); }
.flag-card.is-dataset .flag-boxes::-webkit-scrollbar-thumb {
  background: var(--gold-deep); border-radius: 6px;
}
/* On a short viewport the split leaves the form a slit; fall back to the
   normal full-page flow rather than two fighting scrollbars. */
@media (max-height: 700px) {
  .flag-card.is-dataset .flag-boxes { max-height: none; overflow-y: visible; }
  .flag-card.is-dataset .flag-img { max-height: none; width: 100%; }
}

/* --- the coach page (coach.html + _coach_shell.html + its tab partials) ----
   The banner reuses the player banner's classes wholesale (see the shell's
   header comment); these are only the slots the player page does not have. */

/* The banner link on the Team Hub's coach pill: the name was a plain span and
   is an anchor on PC-save leagues now — same ink, underline only on hover, so
   the pill's geometry is untouched on both kinds of league. */
a.tb-coach-name { color: inherit; text-decoration: none; }
a.tb-coach-name:hover { text-decoration: underline; }

/* "Hired by X for 2029" — the carousel, worn as a quiet chip so it reads as a
   fact about next season rather than as this season's job. */
.cb-hired {
  display: inline-flex; align-items: center;
  padding: 2px 10px; border-radius: 999px;
  font-size: .78rem; letter-spacing: .02em;
  background: rgba(12, 15, 21, .28);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22);
  white-space: nowrap;
}
.cb-hired-line { margin: 0 0 12px; }
.cb-hired-line .cb-hired {
  background: color-mix(in srgb, var(--gold) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gold) 45%, transparent);
  color: var(--ink);
}

/* The current school on the rating line: a stint pill with the name written
   out — a coach page has room for the word where a player's year-pills do not. */
.cb-school .pb-stint-years { text-transform: none; letter-spacing: 0; }

/* Overview: label/value pairs in a responsive grid. Labels come from the
   coach_attributes vocabulary; values dash when empty, so the grid keeps its
   shape from coach to coach. */
.cb-facts {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 10px 22px; padding: 4px 2px;
}
.cb-fact { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.cb-fact-label {
  font-size: .72rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted);
}
.cb-fact-val { font-size: .95rem; overflow-wrap: anywhere; }

/* History: the title chips, and the pre-dynasty footnote. */
.cb-title-chip {
  display: inline-flex; align-items: center; gap: 4px;
  margin-right: 8px; font-size: .82rem; white-space: nowrap;
}
.cb-before { margin: 10px 2px 0; font-size: .84rem; color: var(--muted); }

/* Coach Tree: the "where they went" note rides the name's line. */
.cb-went { margin-left: 8px; font-size: .84rem; color: var(--muted); }
.cb-went a { color: inherit; }

/* Abilities: 13 subtree cards, node cells styled by status. Unlabeled cells
   are the design (the save holds no node names); a filled label renders
   inside the cell when league/coach_talents.py's lookup gains it. */
.cb-ability-note { margin: 0 2px 12px; font-size: .84rem; color: var(--muted); }
.cb-tree-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 12px;
}
.cb-tree-card {
  border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
}
.cb-tree-card--idle { opacity: .55; }
.cb-tree-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; margin-bottom: 8px;
}
.cb-tree-name { font-weight: 600; font-size: .92rem; }
.cb-tree-meta { font-size: .76rem; color: var(--muted); white-space: nowrap; }
.cb-nodes { display: flex; flex-wrap: wrap; gap: 5px; }
.cb-node {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; border-radius: 4px;
  box-shadow: inset 0 0 0 1px var(--line);
  background: transparent;
}
.cb-node--o { background: var(--gold); box-shadow: none; }
.cb-node--p { box-shadow: inset 0 0 0 1px var(--gold); }
.cb-node--l { background: color-mix(in srgb, var(--muted) 22%, transparent); opacity: .5; }
.cb-node--n { background: color-mix(in srgb, var(--muted) 10%, transparent); }
.cb-node--u { box-shadow: inset 0 0 0 1px #c0392b; }
.cb-node-label { padding: 0 5px; font-size: .68rem; white-space: nowrap; }

/* ============================================================================
   Game History (/game-history) and Matchup History (/matchups).
   Both seat their pickers in the ledger head on the overlaid-select control
   and share the score-link idiom of the Team Hub schedule (`.sched-box`).
   Prefix `ser-` (series): `gh-` is the box-score scoreboard's, and the two
   pages once shared it — its crest slab and rank disc landed in this table.
   ========================================================================== */
.ser-head-right { display: flex; align-items: center; gap: 14px; min-width: 0; }
.ser-head-art { flex: none; height: 46px; width: auto; max-width: 92px; object-fit: contain; }
.ser-head-trophy { flex: none; height: 60px; width: auto; max-width: 70px; object-fit: contain; }
.ser-pick-label { max-width: 46vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- the wins-by-team bars --- */
.ser-chart-panel .panel-head { margin-bottom: 8px; }
.ser-chart-scroll { overflow-x: auto; padding-bottom: 4px; }
.ser-chart {
  display: flex; align-items: stretch; gap: 8px; min-width: max-content;
  height: 230px; padding: 0 4px;
}
.ser-bar {
  flex: 1 0 40px; min-width: 40px; max-width: 72px;
  display: flex; flex-direction: column; align-items: center;
  color: inherit; text-decoration: none;
}
/* The track carries the gridlines, so every bar draws the same rule and the
   lines end where the labels begin. `--grid` is the spacing of one whole win
   (or two, five, ten) as a share of the tallest bar. */
.ser-track {
  position: relative; flex: 1; width: 100%; margin-top: 22px;
  background: repeating-linear-gradient(to top, var(--line-soft) 0 1px, transparent 1px var(--grid));
  border-bottom: 1px solid var(--line);
}
.ser-bar-fill {
  position: absolute; left: 4px; right: 4px; bottom: 0; height: var(--h);
  min-height: 3px; background: var(--team); border-radius: 3px 3px 0 0;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.14);
}
.ser-bar-n {
  position: absolute; left: 0; right: 0; bottom: calc(var(--h) + 4px);
  text-align: center; font-family: var(--cond); font-weight: 700; font-size: .95rem;
  line-height: 1; font-variant-numeric: tabular-nums;
}
.ser-bar-logo { width: 22px; height: 22px; object-fit: contain; margin-top: 7px; }
.ser-bar-abbr {
  margin-top: 4px; font-family: var(--cond); font-size: .66rem; letter-spacing: .06em;
  color: var(--muted); white-space: nowrap;
}
a.ser-bar:hover .ser-bar-abbr { color: var(--gold-lite); }
a.ser-bar:hover .ser-bar-fill { filter: brightness(1.15); }

/* --- the results table ---
   One line a game. Each side reads toward the score: rank + name + record,
   then the flat helmet against the score, the two helmets facing off. The
   side columns share one width so the score sits on the table's centre line. */
.ser-table td { padding-top: 7px; padding-bottom: 7px; }
/* The year is a label, not the story: shrunk to its content, regular weight,
   muted, so the teams beside it carry the row. */
.ser-table th.ser-th-year { width: 1%; }
.ser-table .season-cell { font-size: 1rem; font-weight: 400; color: var(--muted); padding-right: 6px; }
.ser-table .season-cell a { color: inherit; }
.ser-table .season-cell a:hover { color: var(--gold-lite); }
.ser-game-line { display: inline-flex; align-items: center; gap: 6px; }
.ser-game .pill { font-size: .8rem; padding: 3px 10px; }
.ser-seeds { font-family: var(--cond); font-size: .85rem; color: var(--faint); font-variant-numeric: tabular-nums; }
.ser-venue { display: block; margin-top: 3px; font-size: .74rem; color: var(--muted); }
/* The side columns size to their longest name rather than to a share of the
   table: a fixed share left the winner's slack as dead space between the year
   and the name. What is left over lands in the trailing Game column. */
.ser-th-side { width: auto; }
/* The Game column trails the result; its pill must not be the thing the
   Winner column stretches to make room for. */
.ser-game { padding-left: 14px; }
.ser-th-w { text-align: right; }
/* Record-table heads sit right by default; the score is a centred pair. The
   letter-spacing trails the last glyph, so a hair of padding-left evens it. */
.record-table th.ser-th-score { text-align: center; padding-left: 14px; }
.ser-cell { padding-left: 6px; padding-right: 6px; }
.ser-side { display: flex; align-items: center; gap: 12px; min-width: 0; }
.ser-side--w { justify-content: flex-end; }
.ser-helmet { flex: none; width: 48px; height: 48px; object-fit: contain; }
.ser-team {
  font-size: 1.05rem; white-space: nowrap; min-width: 0;
  overflow: hidden; text-overflow: ellipsis;
}
.ser-team a { color: inherit; }
.ser-team a:hover { color: var(--gold-lite); }
/* A side that knows whether it won (the matchup list, where both teams are
   the same every row): the winner lit a touch, the loser turned down. */
.ser-side.is-winner .ser-team { color: var(--ink); font-weight: 600; }
.ser-side.is-loser .ser-team { color: var(--muted); }
.ser-side.is-loser .ser-helmet { opacity: .7; }
.ser-rank { font-family: var(--cond); font-weight: 700; color: var(--gold-lite); font-size: .9em; }
.ser-rec { font-family: var(--cond); font-size: .92em; color: var(--muted); font-variant-numeric: tabular-nums; }
.ser-score { text-align: center; padding-left: 4px; padding-right: 4px; }
.ser-box { display: inline-flex; justify-content: center; gap: 16px; }
.ser-pts {
  min-width: 32px; text-align: center; font-family: var(--cond); font-weight: 700;
  font-size: 1.55rem; line-height: 1; font-variant-numeric: tabular-nums; color: var(--faint);
}
.ser-pts.is-winner { color: var(--ink); }
.ser-box:hover .ser-pts { color: var(--gold-lite); }

/* --- the hero band: crests flanking the rivalry's mark, tiles beneath --- */
.mu-top { padding-bottom: 14px; }
.mu-hero {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px 20px;
  padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--line-soft);
}
.mu-hero-side { display: flex; align-items: center; gap: 14px; min-width: 0; }
.mu-hero-side--b { flex-direction: row-reverse; text-align: right; }
.mu-hero-logo { flex: none; width: 84px; height: 84px; object-fit: contain; }
/* The school's name is the picker: the overlaid select covers name and caret. */
.mu-school-pick { display: inline-flex; align-items: center; gap: 8px; color: var(--ink); min-width: 0; }
.mu-school-pick .tb-caret { opacity: .6; }
.mu-school-text { display: flex; flex-direction: column; min-width: 0; }
.mu-hero-side--b .mu-school-text { align-items: flex-end; }
.mu-mascot {
  margin-top: 3px; font-family: var(--cond); font-style: italic; font-size: .95rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted); white-space: nowrap;
}
.mu-hero-side--b .mu-school-pick { flex-direction: row-reverse; }
.mu-school {
  font-family: var(--cond); font-weight: 700; font-size: 1.5rem; line-height: 1.1;
  color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mu-school-pick:hover .mu-school, .mu-school-pick:focus-within .mu-school { color: var(--gold-lite); }
.mu-hero-mid { display: flex; align-items: center; justify-content: center; gap: 16px; min-width: 0; }
.mu-riv-logo { height: 76px; width: auto; max-width: 150px; object-fit: contain; }
.mu-riv-trophy { height: 84px; width: auto; max-width: 90px; object-fit: contain; }
.mu-riv-name {
  font-family: var(--cond); font-weight: 700; font-size: 1.25rem; line-height: 1.15;
  letter-spacing: .02em; text-align: center; color: var(--gold-lite); max-width: 220px;
}
.mu-hero-vs { font-family: var(--cond); font-size: 1.4rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }

.mu-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; }
.mu-tile {
  padding: 8px 12px 9px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2); display: flex; flex-direction: column; gap: 6px; min-width: 0;
}
.mu-tile-label {
  font-size: .58rem; letter-spacing: .14em; text-transform: uppercase; font-weight: 600;
  color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mu-tile-row { display: flex; gap: 10px; }
.mu-tile-side {
  flex: 1; min-width: 0; text-align: center; padding-top: 5px;
  border-top: 3px solid var(--team, var(--line));
}
.mu-tile-side--one { border-top-color: var(--team, var(--line)); }
.mu-tile-val {
  display: block; font-family: var(--cond); font-weight: 700; font-size: 1.45rem;
  line-height: 1.05; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.mu-tile-val a { color: inherit; }
.mu-tile-val a:hover { color: var(--gold-lite); }
.mu-tile-sub {
  display: block; margin-top: 2px; font-family: var(--cond); font-size: .7rem;
  letter-spacing: .06em; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mu-tile-foot { font-size: .68rem; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* --- the game list: the Game History row shape (`.ser-*`), the game named
   on the left and the type pill trailing --- */
.mu-table td { padding-top: 7px; padding-bottom: 7px; }
.mu-th-crest { width: 34px; }
.mu-crest-cell { text-align: center; padding-right: 0; }
.mu-crest { width: 28px; height: 28px; object-fit: contain; vertical-align: middle; }
.mu-game { width: 1%; }
.mu-game-name { font-weight: 600; color: inherit; white-space: nowrap; }
.mu-site { width: 0; min-width: 100%; overflow: hidden; white-space: nowrap; }
.mu-game-name:hover { color: var(--gold-lite); }
.mu-type { padding-left: 14px; }
.mu-type .pill { font-size: .8rem; padding: 3px 10px; }
.pill--reg { border-color: var(--line); color: var(--muted); }

@media (max-width: 900px) {
  .ser-head-right { width: 100%; }
  .ser-pick-label { max-width: 60vw; }
  .ser-head-trophy { height: 46px; }
  .ser-chart { height: 190px; gap: 6px; }
  .ser-bar { flex-basis: 36px; min-width: 36px; }
  .ser-helmet { width: 38px; height: 38px; }
  .ser-side { gap: 8px; }
  .ser-team { font-size: .95rem; }
  .ser-pts { font-size: 1.3rem; }
  .mu-hero { grid-template-columns: 1fr 1fr; }
  .mu-hero-mid { grid-column: 1 / -1; order: 1; }
  .mu-hero-logo { width: 60px; height: 60px; }
  .mu-school { font-size: 1.2rem; }
  .mu-riv-logo { height: 60px; }
  .mu-riv-trophy { height: 64px; }
  .mu-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 480px) {
  .ser-venue { display: none; }
  .mu-tiles { grid-template-columns: 1fr; }
}

/* --- Recruiting: the hub tab and /recruiting ---------------------------- */
/* The star run is emoji, which carries its own colour; kept on one line and
   tightened so five of them read as a rating rather than a row of icons. The
   gem mark rides after a recruit's name, the way a honour mark rides a roster
   name. */
.rec-stars { white-space: nowrap; letter-spacing: -0.12em; font-size: 0.92em; }
.rec-quality { font-size: 0.8em; margin-left: 2px; vertical-align: middle; }
.rec-class-table td.num { font-variant-numeric: tabular-nums; }
.rec-kind { white-space: nowrap; }
.rec-from { display: block; font-size: 0.78em; color: var(--ink-dim); }
.rec-from .cell-logo { width: 14px; height: 14px; margin-right: 4px; }

/* ---- Leaderboard ---------------------------------------------------------- */
/* Counted cells with a mark beside the number. The mark is sized off the type
   so the column keeps its rhythm, and a ZERO is muted rather than shouted —
   the number goes --faint and the mark drops to a grey ghost — so the rows
   that have one read first. */
.lb-mark { white-space: nowrap; }
.lb-mark-ico { display: inline-block; width: 1.3em; height: 1.3em; object-fit: contain;
               vertical-align: -.35em; margin-right: 5px; }
.lb-mark-ico--cc { color: #8fe4a6; }            /* the CC pill's green */
.lb-mark-ico--cc svg { width: 1.1em; height: 1.1em; vertical-align: -.15em; }
.lb-mark.is-zero, .lb-zero { color: var(--faint); }
.lb-mark.is-zero .lb-mark-ico { opacity: .26; filter: grayscale(1); }
.lb-table td.team-cell a { color: var(--ink); }
.lb-table td.team-cell a:hover { color: var(--gold-lite); }

/* The Years control. Closed it is one pill on the panel head's right; open it
   adds a full-width row of its own UNDER the head, so nothing reflows
   sideways and there is no dead space when it is shut. */
.lb-years { margin-left: auto; align-self: center; }
.lb-years-btn { display: inline-flex; align-items: center; gap: 6px; }
.lb-years-btn svg { width: 14px; height: 14px; }
.lb-years-read { color: var(--muted); font-variant-numeric: tabular-nums; }
.lb-years.is-open .lb-years-btn { color: var(--ink); border-color: var(--gold-deep); background: var(--raised); }
.lb-years-body { display: flex; align-items: flex-start; gap: 18px;
                 padding: 8px 10px 0; margin: -4px 0 16px; }
.lb-years-body[hidden] { display: none; }
.lb-years-cap { font-family: var(--cond); font-size: .7rem; letter-spacing: .12em;
                text-transform: uppercase; color: var(--faint); flex: none; padding-top: 7px; }

/* THE DUAL SLIDER. Two <input type=range> stacked on one track — the standard
   technique, and the only one that keeps both handles keyboard-reachable. The
   inputs are transparent and pointer-blind EXCEPT at their thumbs, so a press
   on the track reaches whichever handle app.js has put on top (`is-top`, the
   nearer one). The track, the fill and the ticks are inset by half a thumb so
   they line up with where the thumb's centre can actually travel. */
.lb-slider { position: relative; flex: 1; height: 46px; min-width: 240px; }
.lb-slider-track { position: absolute; left: 8px; right: 8px; top: 13px; height: 4px;
                   background: var(--line); border-radius: 2px; }
.lb-slider-fill { position: absolute; top: 0; bottom: 0;
                  left: var(--lo, 0%); right: calc(100% - var(--hi, 100%));
                  background: var(--gold-deep); border-radius: 2px; }
.lb-range { position: absolute; left: 0; top: 0; width: 100%; height: 30px; margin: 0;
            -webkit-appearance: none; appearance: none; background: none;
            pointer-events: none; z-index: 2; }
.lb-range.is-top { z-index: 3; }
.lb-range::-webkit-slider-runnable-track { height: 30px; background: none; }
.lb-range::-moz-range-track { background: none; }
.lb-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; pointer-events: auto;
  width: 16px; height: 16px; margin-top: 7px; border-radius: 50%; cursor: grab;
  background: var(--gold); border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--gold-deep); }
.lb-range::-moz-range-thumb { pointer-events: auto; width: 12px; height: 12px; border-radius: 50%;
  cursor: grab; background: var(--gold); border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--gold-deep); }
.lb-range:active::-webkit-slider-thumb { cursor: grabbing; background: var(--gold-lite); }
.lb-range:focus { outline: none; }
.lb-range:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--gold-lite); outline-offset: 2px; }
.lb-range:focus-visible::-moz-range-thumb { outline: 2px solid var(--gold-lite); outline-offset: 2px; }
.lb-ticks { position: absolute; left: 8px; right: 8px; top: 24px; height: 20px; }
.lb-tick { position: absolute; top: 0; width: 1px; height: 4px; background: var(--line);
           transform: translateX(-.5px); }
.lb-tick.is-major { height: 7px; background: var(--faint); }
.lb-tick i { position: absolute; top: 9px; left: 50%; transform: translateX(-50%);
             font: 600 .62rem/1 var(--cond); font-style: normal; letter-spacing: .04em;
             color: var(--faint); white-space: nowrap; }
@media (max-width: 700px) {
  .lb-years-body { flex-direction: column; gap: 4px; }
  .lb-slider { width: 100%; }
  .lb-tick.is-major:not(:first-child):not(:last-child) i { display: none; }
}


/* =============================================================================
   MEDIA — one week's headlines, computed live from the database.
   Art is built off the item's own away/home sides: flat helmets face each
   other across the score (away looks right, home looks left — the
   scoreboard's own arrangement), and a face is the portrait + jersey/gear
   stack the Record Book uses, falling to the winner's crest when no portrait
   is on disk.
   ============================================================================= */
.media-panel { padding-top: 14px; }
.mw-weeks { margin-bottom: 18px; }
.mw-h2 {
  margin: 0 0 12px; font-family: var(--cond); font-size: 1.05rem; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-dim);
}
.mw-kicker {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  font-family: var(--cond); font-size: .82rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold);
}
.mw-note { margin: 4px 0 0; font-size: .74rem; line-height: 1.4; color: var(--faint); }
.mw-lede { margin: 6px 0 16px; font-size: 1.15rem; line-height: 1.4; color: var(--ink); max-width: 68ch; }
.mw-head { margin-bottom: 30px; }

/* --- the hero grid: three cards, the first twice as wide ---------------- */
.mh-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.mh-card {
  position: relative; display: grid; grid-template-rows: auto 1fr; gap: 10px;
  padding: 14px 14px 12px; border-radius: var(--radius); background: var(--surface-2);
  border: 1px solid var(--line); overflow: hidden; min-width: 0;
}
.mh-card--lead { grid-column: span 2; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto 1fr; }
.mh-card--lead .mh-match { grid-column: 1 / -1; }
.mh-card--lead .mh-text { font-size: 1.05rem; }
.mh-match { display: block; text-decoration: none; }
.mh-body { min-width: 0; }
.mh-text { margin: 0; font-size: .92rem; line-height: 1.45; color: var(--ink-dim); }
.mh-text strong { color: var(--ink); font-weight: 700; }
.mh-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }
.mh-tag {
  font-family: var(--cond); font-size: .66rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); border: 1px solid var(--line); border-radius: 20px; padding: 1px 7px;
}
.mh-tag--upset, .mh-tag--shutout { color: var(--gold); border-color: var(--gold-deep); }
.mh-tag--user { color: var(--ink); border-color: var(--ink-dim); }

/* the helmet matchup */
.hm { display: inline-flex; align-items: center; gap: 6px; }
.hm-helmet { display: block; height: 52px; width: auto; max-width: 96px; object-fit: contain; }
.hm-score { display: inline-flex; align-items: baseline; gap: 4px; font-family: var(--cond); font-weight: 700; font-size: 1.5rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.hm-pts.is-win { color: var(--ink); }
.hm-dash { color: var(--faint); font-weight: 500; }
.hm-at { font-size: .9rem; letter-spacing: .08em; text-transform: uppercase; color: var(--faint); }
.hm--mh { gap: 10px; }
.hm--mr .hm-helmet { height: 30px; max-width: 56px; }
.hm--mr .hm-score { font-size: .95rem; gap: 2px; }

/* the face */
.mface {
  --mface: 96px; position: relative; display: block; flex: none;
  width: var(--mface); height: var(--mface); border-radius: 10px; overflow: hidden;
  background: rgba(8, 10, 14, .5); box-shadow: inset 0 0 0 1px var(--line);
}
.mface a { display: block; width: 100%; height: 100%; }
.mface-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 100%; display: block; }
.mface-layer { pointer-events: none; }
.mface--crest .mface-img { object-fit: contain; object-position: 50% 50%; padding: 12%; }
.mh-card .mface { align-self: end; justify-self: end; }
.mh-card:not(.mh-card--lead) .mface { --mface: 64px; position: absolute; right: 12px; top: 12px; }
.mh-card:not(.mh-card--lead) .mh-match { padding-right: 72px; }
.mface--mr { --mface: 44px; border-radius: 8px; }

/* --- the rest of the headlines, as rows -------------------------------- */
.mh-list { list-style: none; margin: 16px 0 0; padding: 0; border-top: 1px solid var(--line-soft); }
.mh-row { display: grid; grid-template-columns: 150px minmax(0, 1fr) auto; gap: 12px; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
.mh-row-match { display: block; text-decoration: none; }
.mh-row-text { margin: 0; font-size: .9rem; line-height: 1.45; color: var(--ink-dim); }
.mh-row-text strong { color: var(--ink); }
.mh-also { margin: 12px 0 0; font-size: .84rem; line-height: 1.5; color: var(--muted); }
.mh-also-label { font-family: var(--cond); font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; color: var(--gold); margin-right: 6px; }
.mh-list--full .mh-row-text { font-size: .95rem; }

/* --- the season preview (the chip ahead of Week 0) ----------------------
   Tables are the site's own `.record-table` / `.num` / `.tl`; cards exist
   ONLY for players (a portrait is why a card exists) — teams, coaches and
   the poll stay tables. `.mp-` is the whole prefix. */
.mp-head { margin-bottom: 22px; }
.mp-sec { margin: 0 0 26px; }
.mp-sec .mw-h2 {
  margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--line-soft);
}
.mp-sec-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
.mp-sec-head .mw-h2 { flex: 1 1 auto; margin-bottom: 0; padding-bottom: 0; border-bottom: none; }
.mp-tw { overflow-x: auto; }
.mp-table { width: 100%; }
.mp-none, .mp-foot { margin: 0; font-size: .84rem; color: var(--muted); }
.mp-foot { margin-top: 8px; }
.mp-dim { color: var(--muted); }
.mp-p, .mp-tcell { white-space: nowrap; }
.mp-meta {
  font-family: var(--cond); font-size: .7rem; letter-spacing: .06em; text-transform: uppercase;
  color: var(--faint); margin-left: 6px; white-space: nowrap;
}
.mp-prior { margin-left: 6px; font-size: .8rem; }
.mp-flag {
  font-family: var(--cond); font-size: .64rem; letter-spacing: .09em; text-transform: uppercase;
  color: var(--gold); border: 1px solid var(--gold-deep); border-radius: 20px;
  padding: 1px 6px; margin: 0 5px; white-space: nowrap;
}
.mp-list { list-style: none; margin: 0; padding: 0; }
.mp-list li { padding: 4px 0; font-size: .9rem; color: var(--ink-dim); border-bottom: 1px solid var(--line-soft); }
.mp-list--wrap { columns: 2; column-gap: 26px; }
.mp-list--wrap li { break-inside: avoid; }
.mp-crest-sm { width: 16px; height: 16px; object-fit: contain; vertical-align: middle; margin: 0 4px; }

/* --- the top: poll left, tables right, from 1400px up ------------------- */
.mp-top { display: grid; grid-template-columns: minmax(0, 1fr); column-gap: 28px; }
.mp-top-poll, .mp-top-side { min-width: 0; }
.mp-poll td, .mp-poll th { padding-left: 6px; padding-right: 6px; }
.mp-poll td.tc { text-align: center; }
.mp-conf-logo { display: block; margin: 0 auto; width: 22px; height: 22px; object-fit: contain; }
.mp-meta--bare { margin-left: 0; }
.mp-last { white-space: nowrap; }
.mp-last-rank { font-family: var(--cond); font-weight: 700; color: var(--ink); margin-right: 6px; }
.mp-last-rank::before { content: "No. "; font-weight: 500; color: var(--muted); }
.mp-last-rec { color: var(--ink-dim); margin-right: 6px; font-variant-numeric: tabular-nums; }
.mp-last .pill-row { vertical-align: middle; }
@media (min-width: 1400px) {
  .mp-top { grid-template-columns: minmax(360px, 440px) minmax(0, 1fr); }
}

/* --- numbers, right-aligned, tabular the whole page over --------------- */
.mp-table td.num, .mp-table td.tl { font-variant-numeric: tabular-nums; }

/* the P4/G6 segmented toggle over the coaching table */
.mp-seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.mp-seg-btn {
  font-family: var(--cond); font-size: .74rem; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); background: var(--surface-2); border: none; padding: 6px 12px;
  cursor: pointer; transition: background .15s ease, color .15s ease;
}
.mp-seg-btn + .mp-seg-btn { border-left: 1px solid var(--line); }
.mp-seg-btn b { font-weight: 700; margin-left: 4px; }
.mp-seg-btn[aria-pressed="true"] { background: var(--raised); color: var(--gold-lite); }
.mp-seg-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }

/* a coach's letter grade, in the five fills the game itself prints */
.mp-grade {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.6em; height: 1.6em; border-radius: 4px; margin-right: 6px;
  font-family: var(--cond); font-weight: 700; font-size: .78rem; color: #14100a;
}
.mp-grade--a { background: #3fa35b; }
.mp-grade--b { background: #9ec23c; }
.mp-grade--c { background: #e0c33a; }
.mp-grade--d { background: #e58a2f; }
.mp-grade--f { background: #d0463b; }

/* "Show all"/"Show 5" — the coaching table and the portal share this. */
.mp-more {
  margin-top: 8px; font-family: var(--cond); font-size: .74rem; letter-spacing: .07em; text-transform: uppercase;
  color: var(--gold); background: none; border: 1px solid var(--gold-deep); border-radius: 6px;
  padding: 5px 12px; cursor: pointer; transition: background .15s ease;
}
.mp-more:hover { background: rgba(214, 169, 78, .1); }
.mp-more:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* FPI rank + trend arrow — the poll table's own column and the team panel's
   rating line both use it. Up is the projection liking a team MORE than the
   voters (a lower number); same collapses the number away and reads as an
   em dash; none (no projection at all) reads "nr". */
.mp-fpi { display: inline-flex; align-items: center; gap: 2px; font-variant-numeric: tabular-nums; }
.mp-fpi-arrow { width: 8px; height: 8px; fill: currentColor; }
.mp-fpi.is-up { color: #4bb56a; }
.mp-fpi.is-down { color: #d2553f; }
.mp-fpi.is-down .mp-fpi-arrow { transform: rotate(180deg); }
.mp-fpi.is-same { color: var(--muted); }
.mp-fpi.is-same .mp-fpi-arrow { display: none; }
.mp-fpi.is-same::after { content: "—"; margin-left: 2px; }
.mp-fpi.is-none { color: var(--faint); }
.mp-fpi.is-none .mp-fpi-arrow { display: none; }
.mp-fpi.is-none::before { content: "nr"; }

.mp-extra { margin: 10px 0 0; font-size: .82rem; color: var(--ink-dim); display: flex; flex-wrap: wrap; align-items: center; gap: 10px 6px; }
.mp-extra-label { font-family: var(--cond); font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-right: 4px; }
.mp-extra-rank { font-family: var(--cond); font-size: .72rem; color: var(--faint); margin: 0 10px 0 -4px; }

/* --- the star cards ------------------------------------------------------ */
.mp-cards { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
.mp-card {
  display: flex; flex-direction: column; min-width: 0;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden;
}
.mp-card-face {
  position: relative; display: block; aspect-ratio: 1 / 1; width: 100%;
  background: rgba(8, 10, 14, .5); overflow: hidden;
}
.mp-face-solo {
  position: absolute; inset: 0; margin: auto; width: 46%; height: 46%;
  object-fit: contain; opacity: .9; z-index: 1;
}
.mp-face-img {
  position: absolute; inset: 0; z-index: 2;
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 100%;
}
/* the game's own tag: position over OVR, gold-edged, top-left */
.mp-card-tag {
  position: absolute; top: 6px; left: 6px; z-index: 3;
  display: inline-flex; flex-direction: column; align-items: center; gap: 0;
  min-width: 2.2em; padding: 2px 5px 3px; border-radius: 6px;
  background: rgba(12, 15, 21, .82); border: 1px solid var(--gold-deep);
  box-shadow: inset 0 0 0 1px rgba(214, 169, 78, .18);
}
.mp-card-tag-pos {
  font-family: var(--cond); font-size: .58rem; font-style: italic; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--gold-lite); line-height: 1.1;
}
.mp-card-tag-ovr {
  font-family: var(--cond); font-weight: 700; font-size: 1rem; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.mp-card-crest {
  position: absolute; top: 6px; right: 6px; z-index: 3;
  width: 44px; height: 44px; object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .7));
}
.mp-card-from {
  position: absolute; bottom: 6px; left: 6px; z-index: 3;
  width: 30px; height: 30px; object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6));
}
.mp-card-new {
  position: absolute; bottom: 6px; left: 6px; z-index: 3;
  font-family: var(--cond); font-size: .6rem; font-weight: 700; letter-spacing: .07em;
  color: #14100a; background: var(--gold); border-radius: 4px; padding: 1px 5px;
}
.mp-card-body { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px 10px; min-width: 0; }
.mp-card-name {
  font-family: var(--cond); font-size: .92rem; font-weight: 700; letter-spacing: .01em;
  color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mp-card-meta {
  font-family: var(--cond); font-size: .68rem; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mp-card-meta .team-cell { display: inline-flex; align-items: center; }
.mp-card-line {
  font-size: .78rem; color: var(--ink-dim); white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; margin-top: 2px;
}

/* --- the conference/team picker (the ledger's own cfilter-* markup, under
   a page-owned id so app.js — not the global filter — wires it up) ------- */
.mp-pick { justify-content: flex-start; margin-bottom: 4px; }
.mp-pick-panels .cfilter-teams { margin: 4px 0 18px; }

/* --- one team's panel ---------------------------------------------------- */
.mp-team { margin-top: 8px; }
.mp-team--empty { padding: 30px 0; }
.mp-team-head {
  display: flex; align-items: flex-start; gap: 14px; padding: 16px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background-color: var(--surface); background-position: center; background-repeat: no-repeat;
  margin-bottom: 18px;
}
.mp-team-crest { width: 56px; height: 56px; object-fit: contain; flex: none; }
.mp-team-id { min-width: 0; flex: 1 1 auto; }
.mp-team-name { margin: 0; font-family: var(--cond); font-size: 1.3rem; letter-spacing: .02em; text-transform: uppercase; }
.mp-team-name a { color: var(--ink); }
.mp-team-line { margin: 4px 0 0; font-size: .84rem; line-height: 1.5; color: var(--ink-dim); }
.mp-team-stats { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 10px; }
.mp-team-stat { display: flex; flex-direction: column; gap: 1px; }
.mp-team-stat-label {
  font-family: var(--cond); font-size: .64rem; letter-spacing: .09em; text-transform: uppercase; color: var(--muted);
}
.mp-team-stat-val { font-family: var(--cond); font-size: 1.05rem; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }

.mp-depth { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px; }
.mp-depth-col { min-width: 0; }
.mp-dg-label {
  margin: 14px 0 4px; font-family: var(--cond); font-size: .74rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--gold);
}
.mp-depth-col .mp-dg-label:first-of-type { margin-top: 0; }
.mp-dp {
  display: flex; align-items: center; gap: 8px; padding: 4px 0;
  font-size: .84rem; color: var(--ink-dim); white-space: nowrap; overflow: hidden;
}
.mp-dp a { color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; }
.mp-dp.is-starter + .mp-dp:not(.is-starter) { border-top: 1px solid var(--line-soft); margin-top: 2px; padding-top: 6px; }
.mp-dp.is-new { border-left: 2px solid var(--gold); padding-left: 6px; margin-left: -8px; }
.mp-dp.is-new a { color: var(--ink); }
.mp-dp-slot { flex: none; width: 2.6em; font-family: var(--cond); font-size: .72rem; color: var(--muted); }
.mp-dp-cls { flex: none; font-size: .7rem; color: var(--faint); }
.mp-dp-ovr { flex: none; font-family: var(--cond); font-weight: 700; font-size: .78rem; font-variant-numeric: tabular-nums; }
.mp-chip {
  flex: none; font-family: var(--cond); font-size: .6rem; font-weight: 700; letter-spacing: .06em;
  color: var(--ink-dim); border: 1px solid var(--line); border-radius: 4px; padding: 1px 4px;
}
.mp-left { margin-top: 22px; }

/* --- Empty state ----------------------------------------------------------- */
.media-soon { text-align: center; padding: 48px 20px; }
.media-soon-mark { display: inline-grid; place-items: center; width: 56px; height: 56px; border-radius: 50%; background: var(--surface-2); color: var(--gold); margin-bottom: 10px; }
.media-soon-mark svg { width: 26px; height: 26px; }
.media-soon h2 { margin: 0 0 6px; font-family: var(--cond); font-size: 1.6rem; letter-spacing: .08em; text-transform: uppercase; }
.media-soon p { margin: 0 auto; max-width: 46ch; color: var(--muted); line-height: 1.5; }

@media (max-width: 1180px) {
  .mh-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mh-card--lead { grid-column: span 2; }
  .mp-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .mh-grid { grid-template-columns: 1fr; }
  .mh-card--lead { grid-column: auto; grid-template-columns: 1fr; }
  .mh-card--lead .mface { --mface: 72px; position: absolute; right: 12px; top: 12px; }
  .mh-card--lead .mh-match { padding-right: 80px; }
  .mh-card--lead .mh-text { font-size: .95rem; }
  .mh-row { grid-template-columns: 1fr; gap: 6px; }
  .mh-row .mface { display: none; }
  .mp-list--wrap { columns: 1; }
  .mp-depth { grid-template-columns: 1fr; gap: 4px; }
  .mp-tw .mp-table { min-width: 640px; }
}
@media (max-width: 620px) {
  .hm-helmet { height: 40px; max-width: 72px; }
  .hm-score { font-size: 1.2rem; }
  .mw-lede { font-size: 1.02rem; }
  .mp-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mp-team-head { flex-wrap: wrap; }
}
